summary refs log tree commit diff
path: root/src/platform/nm-linux-platform.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
committerMichael Biebl <biebl@debian.org>2016-01-20 16:26:51 +0100
commit494f296a3baab08522617b24b1f126d8f9a17502 (patch)
treec8ef32fb0dd1c4ff35a0b38e787abb58692de0cd /src/platform/nm-linux-platform.c
parent54f6333410ffd570e62717d9e77c5c987175e397 (diff)
Imported Upstream version 1.1.90 upstream/1.1.90
Diffstat (limited to 'src/platform/nm-linux-platform.c')
-rw-r--r--src/platform/nm-linux-platform.c6473
1 files changed, 3766 insertions, 2707 deletions
diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c
index ee6ba421..f73c228b 100644
--- a/src/platform/nm-linux-platform.c
+++ b/src/platform/nm-linux-platform.c
@@ -22,8 +22,10 @@
 #include <errno.h>
 #include <unistd.h>
 #include <sys/socket.h>
+#include <sys/ioctl.h>
 #include <fcntl.h>
 #include <dlfcn.h>
+#include <arpa/inet.h>
 #include <netinet/icmp6.h>
 #include <netinet/in.h>
 #include <linux/ip.h>
@@ -40,24 +42,13 @@
 #include <netlink/route/route.h>
 #include <gudev/gudev.h>
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE || HAVE_LIBNL_INET6_TOKEN
-#include <netlink/route/link/inet6.h>
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE && HAVE_KERNEL_INET6_ADDR_GEN_MODE
-#include <linux/if_link.h>
-#else
-#define IN6_ADDR_GEN_MODE_EUI64 0
-#define IN6_ADDR_GEN_MODE_NONE  1
-#endif
-#endif
-
-#include "gsystem-local-alloc.h"
 #include "nm-core-internal.h"
 #include "NetworkManagerUtils.h"
 #include "nm-linux-platform.h"
 #include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
-#include "nm-logging.h"
+#include "nm-default.h"
 #include "wifi/wifi-utils.h"
 #include "wifi/wifi-utils-wext.h"
 #include "nmp-object.h"
@@ -65,11 +56,67 @@
 /* This is only included for the translation of VLAN flags */
 #include "nm-setting-vlan.h"
 
+#define VLAN_FLAG_MVRP 0x8
+
+/*********************************************************************************************/
+
+#define IFQDISCSIZ                      32
+
+/*********************************************************************************************/
+
+#ifndef IFLA_PROMISCUITY
+#define IFLA_PROMISCUITY                30
+#endif
+#define IFLA_NUM_TX_QUEUES              31
+#define IFLA_NUM_RX_QUEUES              32
+#define IFLA_CARRIER                    33
+#define IFLA_PHYS_PORT_ID               34
+#define IFLA_LINK_NETNSID               37
+#define __IFLA_MAX                      39
+
+#define IFLA_INET6_TOKEN                7
+#define IFLA_INET6_ADDR_GEN_MODE        8
+#define __IFLA_INET6_MAX                9
+
+#define IFLA_VLAN_PROTOCOL              5
+#define __IFLA_VLAN_MAX                 6
+
+#define IFA_FLAGS                       8
+#define __IFA_MAX                       9
+
+#define IFLA_MACVLAN_FLAGS              2
+#define __IFLA_MACVLAN_MAX              3
+
+#define IFLA_IPTUN_LINK                 1
+#define IFLA_IPTUN_LOCAL                2
+#define IFLA_IPTUN_REMOTE               3
+#define IFLA_IPTUN_TTL                  4
+#define IFLA_IPTUN_TOS                  5
+#define IFLA_IPTUN_ENCAP_LIMIT          6
+#define IFLA_IPTUN_FLOWINFO             7
+#define IFLA_IPTUN_FLAGS                8
+#define IFLA_IPTUN_PROTO                9
+#define IFLA_IPTUN_PMTUDISC             10
+#define __IFLA_IPTUN_MAX                19
+#ifndef IFLA_IPTUN_MAX
+#define IFLA_IPTUN_MAX                  (__IFLA_IPTUN_MAX - 1)
+#endif
+
+#ifndef MACVLAN_FLAG_NOPROMISC
+#define MACVLAN_FLAG_NOPROMISC          1
+#endif
+
+#define IP6_FLOWINFO_TCLASS_MASK        0x0FF00000
+#define IP6_FLOWINFO_TCLASS_SHIFT       20
+#define IP6_FLOWINFO_FLOWLABEL_MASK     0x000FFFFF
+
 /*********************************************************************************************/
 
-#define _NMLOG_DOMAIN                     LOGD_PLATFORM
 #define _NMLOG_PREFIX_NAME                "platform-linux"
-#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__)
+#define _NMLOG_DOMAIN                     LOGD_PLATFORM
+#define _NMLOG2_DOMAIN                    LOGD_PLATFORM
+#define _NMLOG(level, ...)                _LOG(level, _NMLOG_DOMAIN,  platform, __VA_ARGS__)
+#define _NMLOG2(level, ...)               _LOG(level, _NMLOG2_DOMAIN, NULL,     __VA_ARGS__)
 
 #define _LOG(level, domain, self, ...) \
     G_STMT_START { \
@@ -91,11 +138,7 @@
         } \
     } G_STMT_END
 
-#define trace(...)      _LOG (LOGL_TRACE, _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define debug(...)      _LOG (LOGL_DEBUG, _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define info(...)       _LOG (LOGL_INFO,  _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define warning(...)    _LOG (LOGL_WARN , _NMLOG_DOMAIN, NULL, __VA_ARGS__)
-#define error(...)      _LOG (LOGL_ERR  , _NMLOG_DOMAIN, NULL, __VA_ARGS__)
+#define LOG_FMT_IP_TUNNEL "adding %s '%s' parent %u local %s remote %s"
 
 /******************************************************************
  * Forward declarations and enums
@@ -111,6 +154,7 @@ typedef enum {
 	DELAYED_ACTION_TYPE_REFRESH_LINK                = (1LL << 5),
 	DELAYED_ACTION_TYPE_MASTER_CONNECTED            = (1LL << 6),
 	DELAYED_ACTION_TYPE_READ_NETLINK                = (1LL << 7),
+	DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE        = (1LL << 8),
 	__DELAYED_ACTION_TYPE_MAX,
 
 	DELAYED_ACTION_TYPE_REFRESH_ALL                 = DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
@@ -122,494 +166,108 @@ typedef enum {
 	DELAYED_ACTION_TYPE_MAX                         = __DELAYED_ACTION_TYPE_MAX -1,
 } DelayedActionType;
 
-static gboolean tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props);
+typedef enum {
+	/* Negative values are errors from kernel. Add dummy member to
+	 * make enum signed. */
+	_WAIT_FOR_NL_RESPONSE_RESULT_SYSTEM_ERROR = -1,
+
+	WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN = 0,
+	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK,
+	WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT,
+	WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING,
+} WaitForNlResponseResult;
+
+typedef void (*WaitForNlResponseCallback) (NMPlatform *platform,
+                                           guint32 seq_number,
+                                           WaitForNlResponseResult seq_result,
+                                           gpointer user_data);
+
 static void delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gpointer user_data);
 static gboolean delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink);
-static void do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action);
-static void do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action);
+static void 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 gboolean event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks);
-static NMPCacheOpsType cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason);
-
-/******************************************************************
- * libnl unility functions and wrappers
- ******************************************************************/
-
-struct libnl_vtable
-{
-	void *handle;
-	void *handle_route;
-
-	int (*f_nl_has_capability) (int capability);
-	int (*f_rtnl_link_get_link_netnsid) (const struct rtnl_link *link, gint32 *out_link_netnsid);
-};
-
-static int
-_nl_f_nl_has_capability (int capability)
-{
-	return FALSE;
-}
-
-static const struct libnl_vtable *
-_nl_get_vtable (void)
-{
-	static struct libnl_vtable vtable;
-
-	if (G_UNLIKELY (!vtable.f_nl_has_capability)) {
-		vtable.handle = dlopen ("libnl-3.so.200", RTLD_LAZY | RTLD_NOLOAD);
-		if (vtable.handle) {
-			vtable.f_nl_has_capability = dlsym (vtable.handle, "nl_has_capability");
-		}
-		vtable.handle_route = dlopen ("libnl-route-3.so.200", RTLD_LAZY | RTLD_NOLOAD);
-		if (vtable.handle_route) {
-			vtable.f_rtnl_link_get_link_netnsid = dlsym (vtable.handle_route, "rtnl_link_get_link_netnsid");
-		}
-
-		if (!vtable.f_nl_has_capability)
-			vtable.f_nl_has_capability = &_nl_f_nl_has_capability;
-
-		trace ("libnl: rtnl_link_get_link_netnsid() %s", vtable.f_rtnl_link_get_link_netnsid ? "supported" : "not supported");
-
-		g_return_val_if_fail (vtable.handle, &vtable);
-		g_return_val_if_fail (vtable.handle_route, &vtable);
-	}
-
-	return &vtable;
-}
-
-static gboolean
-_nl_has_capability (int capability)
-{
-	return (_nl_get_vtable ()->f_nl_has_capability) (capability);
-}
-
-static int
-_rtnl_link_get_link_netnsid (const struct rtnl_link *link, gint32 *out_link_netnsid)
-{
-	const struct libnl_vtable *vtable;
-
-	g_return_val_if_fail (link, -NLE_INVAL);
-	g_return_val_if_fail (out_link_netnsid, -NLE_INVAL);
-
-	vtable = _nl_get_vtable ();
-	return vtable->f_rtnl_link_get_link_netnsid
-	    ? vtable->f_rtnl_link_get_link_netnsid (link, out_link_netnsid)
-	    : -NLE_OPNOTSUPP;
-}
-
-gboolean
-nm_platform_check_support_libnl_link_netnsid (void)
-{
-	return !!(_nl_get_vtable ()->f_rtnl_link_get_link_netnsid);
-}
-
-/* Automatic deallocation of local variables */
-#define auto_nl_object __attribute__((cleanup(_nl_auto_nl_object)))
-static void
-_nl_auto_nl_object (void *ptr)
-{
-	struct nl_object **object = ptr;
-
-	if (object && *object) {
-		nl_object_put (*object);
-		*object = NULL;
-	}
-}
-
-#define auto_nl_addr __attribute__((cleanup(_nl_auto_nl_addr)))
-static void
-_nl_auto_nl_addr (void *ptr)
-{
-	struct nl_addr **object = ptr;
-
-	if (object && *object) {
-		nl_addr_put (*object);
-		*object = NULL;
-	}
-}
-
-/* wrap the libnl alloc functions and abort on out-of-memory*/
-
-static struct nl_addr *
-_nl_addr_build (int family, const void *buf, size_t size)
-{
-	struct nl_addr *addr;
-
-	addr = nl_addr_build (family, (void *) buf, size);
-	if (!addr)
-		g_error ("nl_addr_build() failed with out of memory");
-
-	return addr;
-}
+static void cache_prune_candidates_prune (NMPlatform *platform);
+static gboolean event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks);
 
-static struct rtnl_link *
-_nl_rtnl_link_alloc (int ifindex, const char*name)
-{
-	struct rtnl_link *rtnllink;
-
-	rtnllink = rtnl_link_alloc ();
-	if (!rtnllink)
-		g_error ("rtnl_link_alloc() failed with out of memory");
-
-	if (ifindex > 0)
-		rtnl_link_set_ifindex (rtnllink, ifindex);
-	if (name)
-		rtnl_link_set_name (rtnllink, name);
-	return rtnllink;
-}
-
-static struct rtnl_addr *
-_nl_rtnl_addr_alloc (int ifindex)
-{
-	struct rtnl_addr *rtnladdr;
-
-	rtnladdr = rtnl_addr_alloc ();
-	if (!rtnladdr)
-		g_error ("rtnl_addr_alloc() failed with out of memory");
-	if (ifindex > 0)
-		rtnl_addr_set_ifindex (rtnladdr, ifindex);
-	return rtnladdr;
-}
-
-static struct rtnl_route *
-_nl_rtnl_route_alloc (void)
-{
-	struct rtnl_route *rtnlroute = rtnl_route_alloc ();
-
-	if (!rtnlroute)
-		g_error ("rtnl_route_alloc() failed with out of memory");
-	return rtnlroute;
-}
-
-static struct rtnl_nexthop *
-_nl_rtnl_route_nh_alloc (void)
-{
-	struct rtnl_nexthop *nexthop;
-
-	nexthop = rtnl_route_nh_alloc ();
-	if (!nexthop)
-		g_error ("rtnl_route_nh_alloc () failed with out of memory");
-	return nexthop;
-}
-
-/* rtnl_addr_set_prefixlen fails to update the nl_addr prefixlen */
-static void
-_nl_rtnl_addr_set_prefixlen (struct rtnl_addr *rtnladdr, int plen)
-{
-	struct nl_addr *nladdr;
-
-	rtnl_addr_set_prefixlen (rtnladdr, plen);
-
-	nladdr = rtnl_addr_get_local (rtnladdr);
-	if (nladdr)
-		nl_addr_set_prefixlen (nladdr, plen);
-}
+/*****************************************************************************/
 
 static const char *
-_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+wait_for_nl_response_to_string (WaitForNlResponseResult seq_result, char *buf, gsize buf_size)
 {
-	const char *str_type = NULL;
+	char *buf0 = buf;
 
-	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;
+	switch (seq_result) {
+	case WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "unknown");
+		break;
+	case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "success");
+		break;
+	case WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN:
+		nm_utils_strbuf_append_str (&buf, &buf_size, "failure");
+		break;
+	default:
+		if (seq_result < 0)
+			nm_utils_strbuf_append (&buf, &buf_size, "failure %d (%s)", -((int) seq_result), g_strerror (-((int) seq_result)));
+		else
+			nm_utils_strbuf_append (&buf, &buf_size, "internal failure %d", (int) seq_result);
+		break;
 	}
-	if (str_type)
-		g_strlcpy (buf, str_type, len);
-	else
-		g_snprintf (buf, len, "(%d)", type);
-	return buf;
+	return buf0;
 }
 
-/******************************************************************/
-
-/* _nl_link_parse_info_data(): Re-fetches a link from the kernel
- * and parses its IFLA_INFO_DATA using a caller-provided parser.
- *
- * Code is stolen from rtnl_link_get_kernel(), nl_pickup(), and link_msg_parser().
- */
-
-typedef int (*NMNLInfoDataParser) (struct nlattr *info_data, gpointer parser_data);
-
-typedef struct {
-	NMNLInfoDataParser parser;
-	gpointer parser_data;
-} NMNLInfoDataClosure;
-
-static struct nla_policy info_data_link_policy[IFLA_MAX + 1] = {
-	[IFLA_LINKINFO] = { .type = NLA_NESTED },
-};
-
-static struct nla_policy info_data_link_info_policy[IFLA_INFO_MAX + 1] = {
-	[IFLA_INFO_DATA] = { .type = NLA_NESTED },
-};
-
-static int
-_nl_link_parse_info_data_cb (struct nl_msg *msg, void *arg)
-{
-	NMNLInfoDataClosure *closure = arg;
-	struct nlmsghdr *n = nlmsg_hdr (msg);
-	struct nlattr *tb[IFLA_MAX + 1];
-	struct nlattr *li[IFLA_INFO_MAX + 1];
-	int err;
-
-	if (!nlmsg_valid_hdr (n, sizeof (struct ifinfomsg)))
-		return -NLE_MSG_TOOSHORT;
-
-	err = nlmsg_parse (n, sizeof (struct ifinfomsg), tb, IFLA_MAX, info_data_link_policy);
-	if (err < 0)
-		return err;
-
-	if (!tb[IFLA_LINKINFO])
-		return -NLE_MISSING_ATTR;
-
-	err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], info_data_link_info_policy);
-	if (err < 0)
-		return err;
-
-	if (!li[IFLA_INFO_DATA])
-		return -NLE_MISSING_ATTR;
-
-	return closure->parser (li[IFLA_INFO_DATA], closure->parser_data);
-}
-
-static int
-_nl_link_parse_info_data (struct nl_sock *sk, int ifindex,
-                          NMNLInfoDataParser parser, gpointer parser_data)
-{
-	NMNLInfoDataClosure data = { .parser = parser, .parser_data = parser_data };
-	struct nl_msg *msg = NULL;
-	struct nl_cb *cb;
-	struct nl_cb *cb0;
-	int err;
-
-	err = rtnl_link_build_get_request (ifindex, NULL, &msg);
-	if (err < 0)
-		return err;
-
-	err = nl_send_auto (sk, msg);
-	nlmsg_free (msg);
-	if (err < 0)
-		return err;
-
-	cb0 = nl_socket_get_cb (sk);
-	cb = nl_cb_clone (cb0);
-	nl_cb_put (cb0);
-	if (cb == NULL)
-		return -NLE_NOMEM;
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_CUSTOM, _nl_link_parse_info_data_cb, &data);
-
-	err = nl_recvmsgs (sk, cb);
-	nl_cb_put (cb);
-	if (err < 0)
-		return err;
-
-	nl_wait_for_ack (sk);
-	return 0;
-}
-
-/******************************************************************/
-
-static int
-_nl_sock_flush_data (struct nl_sock *sk)
-{
-	int nle;
-	struct nl_cb *cb;
-	struct nl_cb *cb0;
-
-	cb0 = nl_socket_get_cb (sk);
-	cb = nl_cb_clone (cb0);
-	nl_cb_put (cb0);
-	if (cb == NULL)
-		return -NLE_NOMEM;
-
-	nl_cb_set (cb, NL_CB_VALID, NL_CB_DEFAULT, NULL, NULL);
-	nl_cb_set (cb, NL_CB_SEQ_CHECK, NL_CB_DEFAULT, NULL, NULL);
-	nl_cb_err (cb, NL_CB_DEFAULT, NULL, NULL);
-	do {
-		errno = 0;
-
-		nle = nl_recvmsgs (sk, cb);
-
-		/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-		if (nle == 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
-			nle = -NLE_AGAIN;
-	} while (nle != -NLE_AGAIN);
-
-	nl_cb_put (cb);
-	return nle;
-}
-
-static void
-_nl_msg_set_seq (struct nl_sock *sk, struct nl_msg *msg, guint32 *out_seq)
-{
-	guint32 seq;
-
-	/* choose our own sequence number, because libnl does not ensure that
-	 * it isn't zero -- which would confuse our checking for outstanding
-	 * messages. */
-	seq = nl_socket_use_seq (sk);
-	if (seq == 0)
-		seq = nl_socket_use_seq (sk);
-
-	nlmsg_hdr (msg)->nlmsg_seq = seq;
-	if (out_seq)
-		*out_seq = seq;
-}
-
-static int
-_nl_sock_request_link (NMPlatform *platform, struct nl_sock *sk, int ifindex, const char *name, guint32 *out_seq)
-{
-	struct nl_msg *msg = NULL;
-	int err;
-
-	if (name && !name[0])
-		name = NULL;
-
-	g_return_val_if_fail (ifindex > 0 || name, -NLE_INVAL);
-
-	_LOGt ("sock: request-link %d%s%s%s", ifindex, name ? ", \"" : "", name ? name : "", name ? "\"" : "");
-
-	if ((err = rtnl_link_build_get_request (ifindex, name, &msg)) < 0)
-		return err;
-
-	_nl_msg_set_seq (sk, msg, out_seq);
-
-	err = nl_send_auto (sk, msg);
-	nlmsg_free(msg);
-	if (err < 0)
-		return err;
-
-	return 0;
-}
-
-static int
-_nl_sock_request_all (NMPlatform *platform, struct nl_sock *sk, NMPObjectType obj_type, guint32 *out_seq)
-{
-	const NMPClass *klass;
-	struct rtgenmsg gmsg = { 0 };
-	struct nl_msg *msg;
-	int err;
-
-	klass = nmp_class_from_type (obj_type);
-
-	_LOGt ("sock: request-all-%s", klass->obj_type_name);
-
-	/* reimplement
-	 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
-	 * because we need the sequence number.
-	 */
-	msg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
-	if (!msg)
-		return -NLE_NOMEM;
-
-	gmsg.rtgen_family = klass->addr_family;
-	err = nlmsg_append (msg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
-	if (err < 0)
-		goto errout;
-
-	_nl_msg_set_seq (sk, msg, out_seq);
-
-	err = nl_send_auto (sk, msg);
-errout:
-	nlmsg_free(msg);
-
-	return err >= 0 ? 0 : err;
-}
-
-/******************************************************************/
+/******************************************************************
+ * Support IFLA_INET6_ADDR_GEN_MODE
+ ******************************************************************/
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
 static int _support_user_ipv6ll = 0;
 #define _support_user_ipv6ll_still_undecided() (G_UNLIKELY (_support_user_ipv6ll == 0))
-#else
-#define _support_user_ipv6ll_still_undecided() (FALSE)
-#endif
 
 static gboolean
 _support_user_ipv6ll_get (void)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
 	if (_support_user_ipv6ll_still_undecided ()) {
 		_support_user_ipv6ll = -1;
-		nm_log_warn (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support");
-	} else
-		return _support_user_ipv6ll > 0;
-#endif
+		_LOG2W ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "failed to detect; assume no support");
+		return FALSE;
+	}
+	return _support_user_ipv6ll > 0;
 
-	return FALSE;
 }
 
 static void
-_support_user_ipv6ll_detect (const struct rtnl_link *rtnl_link)
+_support_user_ipv6ll_detect (struct nlattr **tb)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	/* If we ever see a link with valid IPv6 link-local address
-	 * generation modes, the kernel supports it.
-	 */
 	if (_support_user_ipv6ll_still_undecided ()) {
-		uint8_t mode;
-
-		if (rtnl_link_inet6_get_addr_gen_mode ((struct rtnl_link *) rtnl_link, &mode) == 0) {
+		if (tb[IFLA_INET6_ADDR_GEN_MODE]) {
 			_support_user_ipv6ll = 1;
-			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected");
+			_LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "detected");
 		} else {
 			_support_user_ipv6ll = -1;
-			nm_log_dbg (LOGD_PLATFORM, "kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected");
+			_LOG2D ("kernel support for IFLA_INET6_ADDR_GEN_MODE %s", "not detected");
 		}
 	}
-#endif
 }
 
-/******************************************************************/
-
-static int _support_kernel_extended_ifa_flags = 0;
-
-#define _support_kernel_extended_ifa_flags_still_undecided() (G_UNLIKELY (_support_kernel_extended_ifa_flags == 0))
-
-static void
-_support_kernel_extended_ifa_flags_detect (struct nl_msg *msg)
-{
-	struct nlmsghdr *msg_hdr;
+/******************************************************************
+ * Various utilities
+ ******************************************************************/
 
-	if (!_support_kernel_extended_ifa_flags_still_undecided ())
-		return;
+const NMIPAddr nm_ip_addr_zero = NMIPAddrInit;
 
-	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), 8 /* IFA_FLAGS */)
-	    ? 1 : -1;
-}
+#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
+#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
 
 static gboolean
-_support_kernel_extended_ifa_flags_get (void)
+ip4_address_is_link_local (in_addr_t addr)
 {
-	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
-		nm_log_warn (LOGD_PLATFORM, "Unable to detect kernel support for extended IFA_FLAGS. Assume no kernel support.");
-		_support_kernel_extended_ifa_flags = -1;
-	}
-	return _support_kernel_extended_ifa_flags > 0;
+	return (addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
 }
 
-/******************************************************************
- * Object type specific utilities
- ******************************************************************/
-
 static guint
 _nm_ip_config_source_to_rtprot (NMIPConfigSource source)
 {
@@ -649,7 +307,46 @@ _nm_ip_config_source_from_rtprot (guint rtprot)
 	}
 }
 
-/******************************************************************/
+static void
+clear_host_address (int family, const void *network, int plen, void *dst)
+{
+	g_return_if_fail (plen == (guint8)plen);
+	g_return_if_fail (network);
+
+	switch (family) {
+	case AF_INET:
+		*((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen);
+		break;
+	case AF_INET6:
+		nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen);
+		break;
+	default:
+		g_assert_not_reached ();
+	}
+}
+
+static int
+_vlan_qos_mapping_cmp_from (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	const NMVlanQosMapping *map_a = a;
+	const NMVlanQosMapping *map_b = b;
+
+	if (map_a->from != map_b->from)
+		return map_a->from < map_b->from ? -1 : 1;
+	return 0;
+}
+
+static int
+_vlan_qos_mapping_cmp_from_ptr (gconstpointer a, gconstpointer b, gpointer user_data)
+{
+	return _vlan_qos_mapping_cmp_from (*((const NMVlanQosMapping **) a),
+	                                   *((const NMVlanQosMapping **) b),
+	                                   NULL);
+}
+
+/******************************************************************
+ * NMLinkType functions
+ ******************************************************************/
 
 typedef struct {
 	const NMLinkType nm_type;
@@ -687,10 +384,13 @@ static const LinkDesc linktypes[] = {
 	{ NM_LINK_TYPE_GRE,           "gre",         "gre",         NULL },
 	{ NM_LINK_TYPE_GRETAP,        "gretap",      "gretap",      NULL },
 	{ NM_LINK_TYPE_IFB,           "ifb",         "ifb",         NULL },
+	{ NM_LINK_TYPE_IP6TNL,        "ip6tnl",      "ip6tnl",      NULL },
+	{ NM_LINK_TYPE_IPIP,          "ipip",        "ipip",        NULL },
 	{ NM_LINK_TYPE_LOOPBACK,      "loopback",    NULL,          NULL },
 	{ NM_LINK_TYPE_MACVLAN,       "macvlan",     "macvlan",     NULL },
 	{ NM_LINK_TYPE_MACVTAP,       "macvtap",     "macvtap",     NULL },
 	{ NM_LINK_TYPE_OPENVSWITCH,   "openvswitch", "openvswitch", NULL },
+	{ NM_LINK_TYPE_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 },
@@ -728,125 +428,172 @@ nm_link_type_to_string (NMLinkType type)
 }
 
 /******************************************************************
- * NMPlatform types and functions
+ * Utilities
  ******************************************************************/
 
-typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
-
-struct _NMLinuxPlatformPrivate {
-	struct nl_sock *nlh;
-	struct nl_sock *nlh_event;
-	guint32 nlh_seq_expect;
-	guint32 nlh_seq_last;
-	NMPCache *cache;
-	GIOChannel *event_channel;
-	guint event_id;
-
-	GUdevClient *udev_client;
-
-	struct {
-		DelayedActionType flags;
-		GPtrArray *list_master_connected;
-		GPtrArray *list_refresh_link;
-		gint is_handling;
-		guint idle_id;
-	} delayed_action;
-
-	GHashTable *prune_candidates;
+/* _timestamp_nl_to_ms:
+ * @timestamp_nl: a timestamp from ifa_cacheinfo.
+ * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current
+ * uptime and how often timestamp_nl wrapped.
+ *
+ * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds.
+ * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts
+ * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However,
+ * the uint32 counter wraps every 497 days of uptime, so we have to compensate
+ * for that. */
+static gint64
+_timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
+{
+	const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100);
+	gint64 timestamp_nl_ms;
 
-	GHashTable *wifi_data;
-};
+	/* convert timestamp from 1/100th of a second to msec. */
+	timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100);
 
-static inline NMLinuxPlatformPrivate *
-NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
-{
-	nm_assert (NM_IS_LINUX_PLATFORM (self));
+	/* timestamp wraps every 497 days. Try to compensate for that.*/
+	if (timestamp_nl_ms > monotonic_ms) {
+		/* timestamp_nl_ms is in the future. Truncate it to *now* */
+		timestamp_nl_ms = monotonic_ms;
+	} else if (monotonic_ms >= WRAP_INTERVAL) {
+		timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL;
+		if (timestamp_nl_ms > monotonic_ms)
+			timestamp_nl_ms -= WRAP_INTERVAL;
+	}
 
-	return ((NMLinuxPlatform *) self)->priv;
+	return timestamp_nl_ms;
 }
 
-G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
-
-void
-nm_linux_platform_setup (void)
+static guint32
+_addrtime_timestamp_to_nm (guint32 timestamp, gint32 *out_now_nm)
 {
-	g_object_new (NM_TYPE_LINUX_PLATFORM,
-	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
-	              NULL);
-}
+	struct timespec tp;
+	gint64 now_nl, now_nm, result;
+	int err;
 
-/******************************************************************/
+	/* timestamp is unset. Default to 1. */
+	if (!timestamp) {
+		if (out_now_nm)
+			*out_now_nm = 0;
+		return 1;
+	}
 
-NMPObjectType
-_nlo_get_object_type (const struct nl_object *object)
-{
-	const char *type_str;
+	/* do all the calculations in milliseconds scale */
 
-	if (!object || !(type_str = nl_object_get_type (object)))
-		return NMP_OBJECT_TYPE_UNKNOWN;
+	err = clock_gettime (CLOCK_MONOTONIC, &tp);
+	g_assert (err == 0);
+	now_nm = nm_utils_get_monotonic_timestamp_ms ();
+	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
+	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
 
-	if (!strcmp (type_str, "route/link"))
-		return NMP_OBJECT_TYPE_LINK;
-	else if (!strcmp (type_str, "route/addr")) {
-		switch (rtnl_addr_get_family ((struct rtnl_addr *) object)) {
-		case AF_INET:
-			return NMP_OBJECT_TYPE_IP4_ADDRESS;
-		case AF_INET6:
-			return NMP_OBJECT_TYPE_IP6_ADDRESS;
-		default:
-			return NMP_OBJECT_TYPE_UNKNOWN;
-		}
-	} else if (!strcmp (type_str, "route/route")) {
-		switch (rtnl_route_get_family ((struct rtnl_route *) object)) {
-		case AF_INET:
-			return NMP_OBJECT_TYPE_IP4_ROUTE;
-		case AF_INET6:
-			return NMP_OBJECT_TYPE_IP6_ROUTE;
-		default:
-			return NMP_OBJECT_TYPE_UNKNOWN;
-		}
-	} else
-		return NMP_OBJECT_TYPE_UNKNOWN;
-}
+	result = now_nm - (now_nl - _timestamp_nl_to_ms (timestamp, now_nl));
 
-/******************************************************************/
+	if (out_now_nm)
+		*out_now_nm = now_nm / 1000;
 
-static gboolean
-check_support_kernel_extended_ifa_flags (NMPlatform *platform)
-{
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	/* converting the timestamp into nm_utils_get_monotonic_timestamp_ms() scale is
+	 * a good guess but fails in the following situations:
+	 *
+	 * - If the address existed before start of the process, the timestamp in nm scale would
+	 *   be negative or zero. In this case we default to 1.
+	 * - during hibernation, the CLOCK_MONOTONIC/timestamp drifts from
+	 *   nm_utils_get_monotonic_timestamp_ms() scale.
+	 */
+	if (result <= 1000)
+		return 1;
 
-	return _support_kernel_extended_ifa_flags_get ();
+	if (result > now_nm)
+		return now_nm / 1000;
+
+	return result / 1000;
 }
 
-static gboolean
-check_support_user_ipv6ll (NMPlatform *platform)
+static guint32
+_addrtime_extend_lifetime (guint32 lifetime, guint32 seconds)
 {
-	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+	guint64 v;
 
-	return _support_user_ipv6ll_get ();
+	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
+	    || seconds == 0)
+		return lifetime;
+
+	v = (guint64) lifetime + (guint64) seconds;
+	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
 }
 
+/* The rtnl_addr object contains relative lifetimes @valid and @preferred
+ * that count in seconds, starting from the moment when the kernel constructed
+ * the netlink message.
+ *
+ * There is also a field rtnl_addr_last_update_time(), which is the absolute
+ * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
+ * was modified (wrapping every 497 days).
+ * Immediately at the time when the address was last modified, #NOW and @last_update_time
+ * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
+ * However, this is not true in general. As time goes by, whenever kernel sends a new address
+ * via netlink, the lifetimes keep counting down.
+ **/
 static void
-process_events (NMPlatform *platform)
+_addrtime_get_lifetimes (guint32 timestamp,
+                         guint32 lifetime,
+                         guint32 preferred,
+                         guint32 *out_timestamp,
+                         guint32 *out_lifetime,
+                         guint32 *out_preferred)
 {
-	delayed_action_handle_all (platform, TRUE);
+	gint32 now;
+
+	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+		if (preferred > lifetime)
+			preferred = lifetime;
+		timestamp = _addrtime_timestamp_to_nm (timestamp, &now);
+
+		if (now == 0) {
+			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
+			nm_assert (timestamp == 1);
+			now = nm_utils_get_monotonic_timestamp_s ();
+		}
+		if (timestamp < now) {
+			guint32 diff = now - timestamp;
+
+			lifetime = _addrtime_extend_lifetime (lifetime, diff);
+			preferred = _addrtime_extend_lifetime (preferred, diff);
+		} else
+			nm_assert (timestamp == now);
+	} else
+		timestamp = 0;
+	*out_timestamp = timestamp;
+	*out_lifetime = lifetime;
+	*out_preferred = preferred;
 }
 
 /******************************************************************/
 
-#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
-	((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
-	                                              nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \
-	                                              NULL))
+static const NMPObject *
+_lookup_cached_link (const NMPCache *cache, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
+{
+	const NMPObject *obj;
 
+	nm_assert (completed_from_cache && link_cached);
+
+	if (!*completed_from_cache) {
+		obj = ifindex > 0 && cache ? nmp_cache_lookup_link (cache, ifindex) : NULL;
+
+		if (obj && !obj->_link.netlink.is_in_netlink)
+			*link_cached = obj;
+		else
+			*link_cached = NULL;
+		*completed_from_cache = TRUE;
+	}
+	return *link_cached;
+}
 
 /******************************************************************/
 
 #define DEVTYPE_PREFIX "DEVTYPE="
 
 static char *
-read_devtype (const char *sysfs_path)
+_linktype_read_devtype (const char *sysfs_path)
 {
 	gs_free char *uevent = g_strdup_printf ("%s/uevent", sysfs_path);
 	char *contents = NULL;
@@ -868,69 +615,63 @@ read_devtype (const char *sysfs_path)
 	return NULL;
 }
 
-static const NMPObject *
-_lookup_link_cached (NMPlatform *platform, int ifindex, gboolean *completed_from_cache, const NMPObject **link_cached)
-{
-	const NMPObject *obj;
-
-	nm_assert (completed_from_cache && link_cached);
-
-	if (!*completed_from_cache) {
-		obj = ifindex > 0 ? nmp_cache_lookup_link (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, ifindex) : NULL;
-
-		if (obj && !obj->_link.netlink.is_in_netlink)
-			*link_cached = obj;
-		else
-			*link_cached = NULL;
-		*completed_from_cache = TRUE;
-	}
-	return *link_cached;
-}
-
 static NMLinkType
-link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *completed_from_cache, const NMPObject **link_cached, const char **out_kind)
-{
-	const char *rtnl_type, *ifname;
-	int i, arptype;
-
-	if (!rtnllink) {
-		if (out_kind)
-			*out_kind = NULL;
-		return NM_LINK_TYPE_NONE;
-	}
-
-	rtnl_type = rtnl_link_get_type (rtnllink);
-	if (!rtnl_type && completed_from_cache) {
+_linktype_get_type (NMPlatform *platform,
+                    const NMPCache *cache,
+                    const char *kind,
+                    int ifindex,
+                    const char *ifname,
+                    unsigned flags,
+                    unsigned arptype,
+                    gboolean *completed_from_cache,
+                    const NMPObject **link_cached,
+                    const char **out_kind)
+{
+	guint i;
+
+	if (completed_from_cache) {
 		const NMPObject *obj;
 
-		obj = _lookup_link_cached (platform, rtnl_link_get_ifindex (rtnllink), completed_from_cache, link_cached);
-		if (obj && obj->link.kind) {
-			rtnl_type = obj->link.kind;
-			_LOGt ("link_extract_type(): complete kind from cache: ifindex=%d, kind=%s", rtnl_link_get_ifindex (rtnllink), rtnl_type);
+		obj = _lookup_cached_link (cache, ifindex, completed_from_cache, link_cached);
+
+		/* If we detected the link type before, we stick to that
+		 * decision unless the "kind" changed.
+		 *
+		 * This way, we save edditional ethtool/sysctl lookups, but moreover,
+		 * we keep the linktype stable and don't change it as long as the link
+		 * exists.
+		 *
+		 * Note that kernel *can* reuse the ifindex (on integer overflow, and
+		 * when moving interfce to other netns). Thus here there is a tiny potential
+		 * of messing stuff up. */
+		if (   obj
+		    && !NM_IN_SET (obj->link.type, NM_LINK_TYPE_UNKNOWN, NM_LINK_TYPE_NONE)
+		    && (   !kind
+		        || !g_strcmp0 (kind, obj->link.kind))) {
+			nm_assert (obj->link.kind == g_intern_string (obj->link.kind));
+			*out_kind = obj->link.kind;
+			return obj->link.type;
 		}
 	}
-	if (out_kind)
-		*out_kind = rtnl_type;
-	if (rtnl_type) {
+
+	*out_kind = g_intern_string (kind);
+
+	if (kind) {
 		for (i = 0; i < G_N_ELEMENTS (linktypes); i++) {
-			if (g_strcmp0 (rtnl_type, linktypes[i].rtnl_type) == 0)
+			if (g_strcmp0 (kind, linktypes[i].rtnl_type) == 0)
 				return linktypes[i].nm_type;
 		}
 
-		if (!strcmp (rtnl_type, "tun")) {
+		if (!strcmp (kind, "tun")) {
 			NMPlatformTunProperties props;
-			guint flags;
 
-			if (tun_get_properties_ifname (platform, rtnl_link_get_name (rtnllink), &props)) {
+			if (   platform
+			    && nm_platform_link_tun_get_properties_ifname (platform, ifname, &props)) {
 				if (!g_strcmp0 (props.mode, "tap"))
 					return NM_LINK_TYPE_TAP;
 				if (!g_strcmp0 (props.mode, "tun"))
 					return NM_LINK_TYPE_TUN;
 			}
-			flags = rtnl_link_get_flags (rtnllink);
-
-			nm_log_dbg (LOGD_PLATFORM, "Failed to read tun properties for interface %d (link flags: %X)",
-			            rtnl_link_get_ifindex (rtnllink), flags);
 
 			/* try guessing the type using the link flags instead... */
 			if (flags & IFF_POINTOPOINT)
@@ -939,13 +680,15 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		}
 	}
 
-	arptype = rtnl_link_get_arptype (rtnllink);
 	if (arptype == ARPHRD_LOOPBACK)
 		return NM_LINK_TYPE_LOOPBACK;
 	else if (arptype == ARPHRD_INFINIBAND)
 		return NM_LINK_TYPE_INFINIBAND;
+	else if (arptype == ARPHRD_SIT)
+		return NM_LINK_TYPE_SIT;
+	else if (arptype == ARPHRD_TUNNEL6)
+		return NM_LINK_TYPE_IP6TNL;
 
-	ifname = rtnl_link_get_name (rtnllink);
 	if (ifname) {
 		gs_free char *driver = NULL;
 		gs_free char *sysfs_path = NULL;
@@ -971,7 +714,7 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		if (g_file_test (anycast_mask, G_FILE_TEST_EXISTS))
 			return NM_LINK_TYPE_OLPC_MESH;
 
-		devtype = read_devtype (sysfs_path);
+		devtype = _linktype_read_devtype (sysfs_path);
 		for (i = 0; devtype && i < G_N_ELEMENTS (linktypes); i++) {
 			if (g_strcmp0 (devtype, linktypes[i].devtype) == 0) {
 				if (linktypes[i].nm_type == NM_LINK_TYPE_BNEP) {
@@ -994,366 +737,1138 @@ link_extract_type (NMPlatform *platform, struct rtnl_link *rtnllink, gboolean *c
 		 * prevent future virtual network drivers from being treated as Ethernet
 		 * when they should be Generic instead.
 		 */
-		if (arptype == ARPHRD_ETHER && !rtnl_type && !devtype)
+		if (arptype == ARPHRD_ETHER && !kind && !devtype)
 			return NM_LINK_TYPE_ETHERNET;
 	}
 
 	return NM_LINK_TYPE_UNKNOWN;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/******************************************************************
+ * libnl unility functions and wrappers
+ ******************************************************************/
+
+#define nm_auto_nlmsg __attribute__((cleanup(_nm_auto_nl_msg_cleanup)))
+static void
+_nm_auto_nl_msg_cleanup (void *ptr)
 {
-	NMPlatformLink *obj = (NMPlatformLink *) _obj;
-	NMPObjectLink *obj_priv = (NMPObjectLink *) _obj;
-	struct rtnl_link *nlo = (struct rtnl_link *) _nlo;
-	const char *name;
-	struct nl_addr *nladdr;
-	const char *kind;
-	gboolean completed_from_cache_val = FALSE;
-	gboolean *completed_from_cache = complete_from_cache ? &completed_from_cache_val : NULL;
-	const NMPObject *link_cached = NULL;
-	int parent;
+	nlmsg_free (*((struct nl_msg **) ptr));
+}
 
-	nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
+static const char *
+_nl_nlmsg_type_to_str (guint16 type, char *buf, gsize len)
+{
+	const char *str_type = NULL;
 
-	if (_LOGt_ENABLED () && !NM_IN_SET (rtnl_link_get_family (nlo), AF_UNSPEC, AF_BRIDGE))
-		_LOGt ("netlink object for ifindex %d has unusual family %d", rtnl_link_get_ifindex (nlo), rtnl_link_get_family (nlo));
+	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;
+	}
+	if (str_type)
+		g_strlcpy (buf, str_type, len);
+	else
+		g_snprintf (buf, len, "(%d)", type);
+	return buf;
+}
 
-	obj->ifindex = rtnl_link_get_ifindex (nlo);
+/******************************************************************
+ * NMPObject/netlink functions
+ ******************************************************************/
 
-	if (id_only)
-		return TRUE;
+#define _check_addr_or_errout(tb, attr, addr_len) \
+	({ \
+	    const struct nlattr *__t = (tb)[(attr)]; \
+		\
+	    if (__t) { \
+			if (nla_len (__t) != (addr_len)) { \
+				goto errout; \
+			} \
+		} \
+		!!__t; \
+	})
 
-	name = rtnl_link_get_name (nlo);
-	if (name)
-		g_strlcpy (obj->name, name, sizeof (obj->name));
-	obj->type = link_extract_type (platform, nlo, completed_from_cache, &link_cached, &kind);
-	obj->kind = g_intern_string (kind);
-	obj->flags = rtnl_link_get_flags (nlo);
-	obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP);
-	obj->master = rtnl_link_get_master (nlo);
-	parent = rtnl_link_get_link (nlo);
-	if (parent > 0) {
-		gint32 link_netnsid;
-
-		if (_rtnl_link_get_link_netnsid (nlo, &link_netnsid) == 0)
-			obj->parent = NM_PLATFORM_LINK_OTHER_NETNS;
-		else
-			obj->parent = parent;
-	}
-	obj->mtu = rtnl_link_get_mtu (nlo);
-	obj->arptype = rtnl_link_get_arptype (nlo);
-
-	if (obj->type == NM_LINK_TYPE_VLAN) {
-		if (!g_strcmp0 (rtnl_link_get_type (nlo), "vlan"))
-			obj->vlan_id = rtnl_link_vlan_get_id (nlo);
-		else if (completed_from_cache) {
-			_lookup_link_cached (platform, obj->ifindex, completed_from_cache, &link_cached);
-			if (link_cached)
-				obj->vlan_id = link_cached->link.vlan_id;
-		}
-	}
+/*****************************************************************************/
 
-	if ((nladdr = rtnl_link_get_addr (nlo))) {
-		unsigned int l = 0;
+/* Copied and heavily modified from libnl3's inet6_parse_protinfo(). */
+static gboolean
+_parse_af_inet6 (NMPlatform *platform,
+                 struct nlattr *attr,
+                 NMUtilsIPv6IfaceId *out_iid,
+                 guint8 *out_iid_is_valid,
+                 guint8 *out_addr_gen_mode_inv)
+{
+	static struct nla_policy policy[IFLA_INET6_MAX+1] = {
+		[IFLA_INET6_FLAGS]              = { .type = NLA_U32 },
+		[IFLA_INET6_CACHEINFO]          = { .minlen = sizeof(struct ifla_cacheinfo) },
+		[IFLA_INET6_CONF]               = { .minlen = 4 },
+		[IFLA_INET6_STATS]              = { .minlen = 8 },
+		[IFLA_INET6_ICMP6STATS]         = { .minlen = 8 },
+		[IFLA_INET6_TOKEN]              = { .minlen = sizeof(struct in6_addr) },
+		[IFLA_INET6_ADDR_GEN_MODE]      = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_INET6_MAX+1];
+	int err;
+	struct in6_addr i6_token;
+	gboolean iid_is_valid = FALSE;
+	guint8 i6_addr_gen_mode_inv = 0;
+	gboolean success = FALSE;
 
-		l = nl_addr_get_len (nladdr);
-		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
-			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data));
-			memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l);
-			obj->addr.len = l;
-		}
-	}
+	err = nla_parse_nested (tb, IFLA_INET6_MAX, attr, policy);
+	if (err < 0)
+		goto errout;
 
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (_support_user_ipv6ll_get ()) {
-		guint8 mode = 0;
+	if (tb[IFLA_INET6_CONF] && nla_len(tb[IFLA_INET6_CONF]) % 4)
+		goto errout;
+	if (tb[IFLA_INET6_STATS] && nla_len(tb[IFLA_INET6_STATS]) % 8)
+		goto errout;
+	if (tb[IFLA_INET6_ICMP6STATS] && nla_len(tb[IFLA_INET6_ICMP6STATS]) % 8)
+		goto errout;
 
-		if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
-			obj->inet6_addr_gen_mode_inv = _nm_platform_uint8_inv (mode);
+	if (_check_addr_or_errout (tb, IFLA_INET6_TOKEN, sizeof (struct in6_addr))) {
+		nla_memcpy (&i6_token, tb[IFLA_INET6_TOKEN], sizeof (struct in6_addr));
+		if (!IN6_IS_ADDR_UNSPECIFIED (&i6_token))
+			iid_is_valid = TRUE;
 	}
-#endif
 
-#if HAVE_LIBNL_INET6_TOKEN
-	if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) {
-		if (   nl_addr_get_family (nladdr) == AF_INET6
-		    && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) {
-			struct in6_addr *addr;
-			NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid;
-
-			addr = nl_addr_get_binary_addr (nladdr);
-			iid->id_u8[7] = addr->s6_addr[15];
-			iid->id_u8[6] = addr->s6_addr[14];
-			iid->id_u8[5] = addr->s6_addr[13];
-			iid->id_u8[4] = addr->s6_addr[12];
-			iid->id_u8[3] = addr->s6_addr[11];
-			iid->id_u8[2] = addr->s6_addr[10];
-			iid->id_u8[1] = addr->s6_addr[9];
-			iid->id_u8[0] = addr->s6_addr[8];
-			obj->inet6_token.is_valid = TRUE;
+	/* 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 (tb[IFLA_INET6_ADDR_GEN_MODE]) {
+		i6_addr_gen_mode_inv = _nm_platform_uint8_inv (nla_get_u8 (tb[IFLA_INET6_ADDR_GEN_MODE]));
+		if (i6_addr_gen_mode_inv == 0) {
+			/* an inverse addrgenmode of zero is unexpected. We need to reserve zero
+			 * to signal "unset". */
+			goto errout;
 		}
-		nl_addr_put (nladdr);
 	}
-#endif
 
-	obj_priv->netlink.is_in_netlink = TRUE;
+	success = TRUE;
+	if (iid_is_valid) {
+		out_iid->id_u8[7] = i6_token.s6_addr[15];
+		out_iid->id_u8[6] = i6_token.s6_addr[14];
+		out_iid->id_u8[5] = i6_token.s6_addr[13];
+		out_iid->id_u8[4] = i6_token.s6_addr[12];
+		out_iid->id_u8[3] = i6_token.s6_addr[11];
+		out_iid->id_u8[2] = i6_token.s6_addr[10];
+		out_iid->id_u8[1] = i6_token.s6_addr[9];
+		out_iid->id_u8[0] = i6_token.s6_addr[8];
+		*out_iid_is_valid = TRUE;
+	}
+	*out_addr_gen_mode_inv = i6_addr_gen_mode_inv;
+errout:
+	return success;
+}
 
-	return TRUE;
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_gre (const char *kind, struct nlattr *info_data)
+{
+	static 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 },
+		[IFLA_GRE_IKEY]     = { .type = NLA_U32 },
+		[IFLA_GRE_OKEY]     = { .type = NLA_U32 },
+		[IFLA_GRE_LOCAL]    = { .type = NLA_U32 },
+		[IFLA_GRE_REMOTE]   = { .type = NLA_U32 },
+		[IFLA_GRE_TTL]      = { .type = NLA_U8 },
+		[IFLA_GRE_TOS]      = { .type = NLA_U8 },
+		[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_GRE_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkGre *props;
+
+	if (!info_data || g_strcmp0 (kind, "gre"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_GRE, NULL);
+	props = &obj->lnk_gre;
+
+	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
+	props->input_flags = tb[IFLA_GRE_IFLAGS] ? ntohs (nla_get_u16 (tb[IFLA_GRE_IFLAGS])) : 0;
+	props->output_flags = tb[IFLA_GRE_OFLAGS] ? ntohs (nla_get_u16 (tb[IFLA_GRE_OFLAGS])) : 0;
+	props->input_key = tb[IFLA_GRE_IKEY] ? ntohl (nla_get_u32 (tb[IFLA_GRE_IKEY])) : 0;
+	props->output_key = tb[IFLA_GRE_OKEY] ? ntohl (nla_get_u32 (tb[IFLA_GRE_OKEY])) : 0;
+	props->local = tb[IFLA_GRE_LOCAL] ? nla_get_u32 (tb[IFLA_GRE_LOCAL]) : 0;
+	props->remote = tb[IFLA_GRE_REMOTE] ? nla_get_u32 (tb[IFLA_GRE_REMOTE]) : 0;
+	props->tos = tb[IFLA_GRE_TOS] ? nla_get_u8 (tb[IFLA_GRE_TOS]) : 0;
+	props->ttl = tb[IFLA_GRE_TTL] ? nla_get_u8 (tb[IFLA_GRE_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_GRE_PMTUDISC] || !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+
+	return obj;
 }
 
-/* _timestamp_nl_to_ms:
- * @timestamp_nl: a timestamp from ifa_cacheinfo.
- * @monotonic_ms: *now* in CLOCK_MONOTONIC. Needed to estimate the current
- * uptime and how often timestamp_nl wrapped.
- *
- * Convert the timestamp from ifa_cacheinfo to CLOCK_MONOTONIC milliseconds.
- * The ifa_cacheinfo fields tstamp and cstamp contains timestamps that counts
- * with in 1/100th of a second of clock_gettime(CLOCK_MONOTONIC). However,
- * the uint32 counter wraps every 497 days of uptime, so we have to compensate
- * for that. */
-static gint64
-_timestamp_nl_to_ms (guint32 timestamp_nl, gint64 monotonic_ms)
+/*****************************************************************************/
+
+/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
+ * we're building against might not have those properties even though the
+ * running kernel might.
+ */
+#define IFLA_IPOIB_UNSPEC 0
+#define IFLA_IPOIB_PKEY   1
+#define IFLA_IPOIB_MODE   2
+#define IFLA_IPOIB_UMCAST 3
+#undef IFLA_IPOIB_MAX
+#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
+
+#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
+#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+
+static NMPObject *
+_parse_lnk_infiniband (const char *kind, struct nlattr *info_data)
 {
-	const gint64 WRAP_INTERVAL = (((gint64) G_MAXUINT32) + 1) * (1000 / 100);
-	gint64 timestamp_nl_ms;
+	static 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 },
+	};
+	struct nlattr *tb[IFLA_IPOIB_MAX + 1];
+	NMPlatformLnkInfiniband *info;
+	NMPObject *obj;
+	int err;
+	const char *mode;
 
-	/* convert timestamp from 1/100th of a second to msec. */
-	timestamp_nl_ms = ((gint64) timestamp_nl) * (1000 / 100);
+	if (!info_data || g_strcmp0 (kind, "ipoib"))
+		return NULL;
 
-	/* timestamp wraps every 497 days. Try to compensate for that.*/
-	if (timestamp_nl_ms > monotonic_ms) {
-		/* timestamp_nl_ms is in the future. Truncate it to *now* */
-		timestamp_nl_ms = monotonic_ms;
-	} else if (monotonic_ms >= WRAP_INTERVAL) {
-		timestamp_nl_ms += (monotonic_ms / WRAP_INTERVAL) * WRAP_INTERVAL;
-		if (timestamp_nl_ms > monotonic_ms)
-			timestamp_nl_ms -= WRAP_INTERVAL;
+	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
+		return NULL;
+
+	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
+	case IPOIB_MODE_DATAGRAM:
+		mode = "datagram";
+		break;
+	case IPOIB_MODE_CONNECTED:
+		mode = "connected";
+		break;
+	default:
+		return NULL;
 	}
 
-	return timestamp_nl_ms;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_INFINIBAND, NULL);
+	info = &obj->lnk_infiniband;
+
+	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+	info->mode = mode;
+
+	return obj;
 }
 
-static guint32
-_rtnl_addr_last_update_time_to_nm (const struct rtnl_addr *rtnladdr, gint32 *out_now_nm)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_ip6tnl (const char *kind, struct nlattr *info_data)
 {
-	guint32 last_update_time = rtnl_addr_get_last_update_time ((struct rtnl_addr *) rtnladdr);
-	struct timespec tp;
-	gint64 now_nl, now_nm, result;
+	static 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)},
+		[IFLA_IPTUN_REMOTE]      = { .type = NLA_UNSPEC,
+		                             .minlen = sizeof (struct in6_addr)},
+		[IFLA_IPTUN_TTL]         = { .type = NLA_U8 },
+		[IFLA_IPTUN_ENCAP_LIMIT] = { .type = NLA_U8 },
+		[IFLA_IPTUN_FLOWINFO]    = { .type = NLA_U32 },
+		[IFLA_IPTUN_PROTO]       = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
 	int err;
+	NMPObject *obj;
+	NMPlatformLnkIp6Tnl *props;
+	guint32 flowinfo;
 
-	/* timestamp is unset. Default to 1. */
-	if (!last_update_time) {
-		if (out_now_nm)
-			*out_now_nm = 0;
-		return 1;
+	if (!info_data || g_strcmp0 (kind, "ip6tnl"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IP6TNL, NULL);
+	props = &obj->lnk_ip6tnl;
+
+	if (tb[IFLA_IPTUN_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_IPTUN_LINK]);
+	if (tb[IFLA_IPTUN_LOCAL])
+		memcpy (&props->local, nla_data (tb[IFLA_IPTUN_LOCAL]), sizeof (props->local));
+	if (tb[IFLA_IPTUN_REMOTE])
+		memcpy (&props->remote, nla_data (tb[IFLA_IPTUN_REMOTE]), sizeof (props->remote));
+	if (tb[IFLA_IPTUN_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_IPTUN_TTL]);
+	if (tb[IFLA_IPTUN_ENCAP_LIMIT])
+		props->encap_limit = nla_get_u8 (tb[IFLA_IPTUN_ENCAP_LIMIT]);
+	if (tb[IFLA_IPTUN_FLOWINFO]) {
+		flowinfo = ntohl (nla_get_u32 (tb[IFLA_IPTUN_FLOWINFO]));
+		props->flow_label = flowinfo & IP6_FLOWINFO_FLOWLABEL_MASK;
+		props->tclass = (flowinfo & IP6_FLOWINFO_TCLASS_MASK) >> IP6_FLOWINFO_TCLASS_SHIFT;
 	}
+	if (tb[IFLA_IPTUN_PROTO])
+		props->proto = nla_get_u8 (tb[IFLA_IPTUN_PROTO]);
 
-	/* do all the calculations in milliseconds scale */
+	return obj;
+}
 
-	err = clock_gettime (CLOCK_MONOTONIC, &tp);
-	g_assert (err == 0);
-	now_nm = nm_utils_get_monotonic_timestamp_ms ();
-	now_nl = (((gint64) tp.tv_sec) * ((gint64) 1000)) +
-	         (tp.tv_nsec / (NM_UTILS_NS_PER_SECOND/1000));
+/*****************************************************************************/
 
-	result = now_nm - (now_nl - _timestamp_nl_to_ms (last_update_time, now_nl));
+static NMPObject *
+_parse_lnk_ipip (const char *kind, struct nlattr *info_data)
+{
+	static 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 },
+		[IFLA_IPTUN_TTL]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkIpIp *props;
 
-	if (out_now_nm)
-		*out_now_nm = now_nm / 1000;
+	if (!info_data || g_strcmp0 (kind, "ipip"))
+		return NULL;
 
-	/* converting the last_update_time into nm_utils_get_monotonic_timestamp_ms() scale is
-	 * a good guess but fails in the following situations:
-	 *
-	 * - If the address existed before start of the process, the timestamp in nm scale would
-	 *   be negative or zero. In this case we default to 1.
-	 * - during hibernation, the CLOCK_MONOTONIC/last_update_time drifts from
-	 *   nm_utils_get_monotonic_timestamp_ms() scale.
-	 */
-	if (result <= 1000)
-		return 1;
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
 
-	if (result > now_nm)
-		return now_nm / 1000;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_IPIP, NULL);
+	props = &obj->lnk_ipip;
 
-	return result / 1000;
+	props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32 (tb[IFLA_IPTUN_LINK]) : 0;
+	props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32 (tb[IFLA_IPTUN_LOCAL]) : 0;
+	props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32 (tb[IFLA_IPTUN_REMOTE]) : 0;
+	props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8 (tb[IFLA_IPTUN_TOS]) : 0;
+	props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8 (tb[IFLA_IPTUN_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8 (tb[IFLA_IPTUN_PMTUDISC]);
+
+	return obj;
 }
 
-static guint32
-_extend_lifetime (guint32 lifetime, guint32 seconds)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_macvlan (const char *kind, struct nlattr *info_data)
 {
-	guint64 v;
+	static struct nla_policy policy[IFLA_MACVLAN_MAX + 1] = {
+		[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
+		[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
+	};
+	NMPlatformLnkMacvlan *props;
+	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	gboolean tap;
 
-	if (   lifetime == NM_PLATFORM_LIFETIME_PERMANENT
-	    || seconds == 0)
-		return lifetime;
+	if (!info_data)
+		return NULL;
 
-	v = (guint64) lifetime + (guint64) seconds;
-	return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
+	if (!g_strcmp0 (kind, "macvlan"))
+		tap = FALSE;
+	else if (!g_strcmp0 (kind, "macvtap"))
+		tap = TRUE;
+	else
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	if (!tb[IFLA_MACVLAN_MODE])
+		return NULL;
+
+	obj = nmp_object_new (tap ? NMP_OBJECT_TYPE_LNK_MACVTAP : NMP_OBJECT_TYPE_LNK_MACVLAN, NULL);
+	props = &obj->lnk_macvlan;
+	props->mode = nla_get_u32 (tb[IFLA_MACVLAN_MODE]);
+	props->tap = tap;
+
+	if (tb[IFLA_MACVLAN_FLAGS])
+		props->no_promisc = NM_FLAGS_HAS (nla_get_u16 (tb[IFLA_MACVLAN_FLAGS]), MACVLAN_FLAG_NOPROMISC);
+
+	return obj;
 }
 
-/* The rtnl_addr object contains relative lifetimes @valid and @preferred
- * that count in seconds, starting from the moment when the kernel constructed
- * the netlink message.
- *
- * There is also a field rtnl_addr_last_update_time(), which is the absolute
- * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
- * was modified (wrapping every 497 days).
- * Immediately at the time when the address was last modified, #NOW and @last_update_time
- * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
- * However, this is not true in general. As time goes by, whenever kernel sends a new address
- * via netlink, the lifetimes keep counting down.
- **/
-static void
-_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr,
-                              guint32 *out_timestamp,
-                              guint32 *out_lifetime,
-                              guint32 *out_preferred)
+/*****************************************************************************/
+
+static NMPObject *
+_parse_lnk_sit (const char *kind, struct nlattr *info_data)
 {
-	guint32 timestamp = 0;
-	gint32 now;
-	guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
-	guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
+	static 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 },
+		[IFLA_IPTUN_TTL]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_TOS]      = { .type = NLA_U8 },
+		[IFLA_IPTUN_PMTUDISC] = { .type = NLA_U8 },
+		[IFLA_IPTUN_FLAGS]    = { .type = NLA_U16 },
+		[IFLA_IPTUN_PROTO]    = { .type = NLA_U8 },
+	};
+	struct nlattr *tb[IFLA_IPTUN_MAX + 1];
+	int err;
+	NMPObject *obj;
+	NMPlatformLnkSit *props;
 
-	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
-	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
-		if (preferred > lifetime)
-			preferred = lifetime;
-		timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now);
+	if (!info_data || g_strcmp0 (kind, "sit"))
+		return NULL;
 
-		if (now == 0) {
-			/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
-			nm_assert (timestamp == 1);
-			now = nm_utils_get_monotonic_timestamp_s ();
-		}
-		if (timestamp < now) {
-			guint32 diff = now - timestamp;
+	err = nla_parse_nested (tb, IFLA_IPTUN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
 
-			lifetime = _extend_lifetime (lifetime, diff);
-			preferred = _extend_lifetime (preferred, diff);
-		} else
-			nm_assert (timestamp == now);
-	}
-	*out_timestamp = timestamp;
-	*out_lifetime = lifetime;
-	*out_preferred = preferred;
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_SIT, NULL);
+	props = &obj->lnk_sit;
+
+	props->parent_ifindex = tb[IFLA_IPTUN_LINK] ? nla_get_u32 (tb[IFLA_IPTUN_LINK]) : 0;
+	props->local = tb[IFLA_IPTUN_LOCAL] ? nla_get_u32 (tb[IFLA_IPTUN_LOCAL]) : 0;
+	props->remote = tb[IFLA_IPTUN_REMOTE] ? nla_get_u32 (tb[IFLA_IPTUN_REMOTE]) : 0;
+	props->tos = tb[IFLA_IPTUN_TOS] ? nla_get_u8 (tb[IFLA_IPTUN_TOS]) : 0;
+	props->ttl = tb[IFLA_IPTUN_TTL] ? nla_get_u8 (tb[IFLA_IPTUN_TTL]) : 0;
+	props->path_mtu_discovery = !tb[IFLA_IPTUN_PMTUDISC] || !!nla_get_u8 (tb[IFLA_IPTUN_PMTUDISC]);
+	props->flags = tb[IFLA_IPTUN_FLAGS] ? nla_get_u16 (tb[IFLA_IPTUN_FLAGS]) : 0;
+	props->proto = tb[IFLA_IPTUN_PROTO] ? nla_get_u8 (tb[IFLA_IPTUN_PROTO]) : 0;
+
+	return obj;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/*****************************************************************************/
+
+static gboolean
+_vlan_qos_mapping_from_nla (struct nlattr *nlattr,
+                            const NMVlanQosMapping **out_map,
+                            guint *out_n_map)
 {
-	NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj;
-	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
-	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
-	const char *label;
+	struct nlattr *nla;
+	int remaining;
+	gs_unref_ptrarray GPtrArray *array = NULL;
 
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
-		g_return_val_if_reached (FALSE);
+	G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == sizeof (struct ifla_vlan_qos_mapping));
+	G_STATIC_ASSERT (sizeof (((NMVlanQosMapping *) 0)->to) == sizeof (((struct ifla_vlan_qos_mapping *) 0)->to));
+	G_STATIC_ASSERT (sizeof (((NMVlanQosMapping *) 0)->from) == sizeof (((struct ifla_vlan_qos_mapping *) 0)->from));
+	G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == sizeof (((NMVlanQosMapping *) 0)->from) + sizeof (((NMVlanQosMapping *) 0)->to));
 
-	obj->ifindex = rtnl_addr_get_ifindex (nlo);
-	obj->plen = rtnl_addr_get_prefixlen (nlo);
-	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+	nm_assert (out_map && !*out_map);
+	nm_assert (out_n_map && !*out_n_map);
 
-	if (id_only)
+	if (!nlattr)
 		return TRUE;
 
-	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	_nlo_rtnl_addr_get_lifetimes (nlo,
-	                              &obj->timestamp,
-	                              &obj->lifetime,
-	                              &obj->preferred);
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+	array = g_ptr_array_new ();
+	nla_for_each_nested (nla, nlattr, remaining) {
+		if (nla_len (nla) < sizeof(NMVlanQosMapping))
+			return FALSE;
+		g_ptr_array_add (array, nla_data (nla));
+	}
+
+	if (array->len > 0) {
+		NMVlanQosMapping *list;
+		guint i, j;
+
+		/* The sorting is necessary, because for egress mapping, kernel
+		 * doesn't sent the items strictly sorted by the from field. */
+		g_ptr_array_sort_with_data (array, _vlan_qos_mapping_cmp_from_ptr, NULL);
+
+		list = g_new (NMVlanQosMapping,  array->len);
+
+		for (i = 0, j = 0; i < array->len; i++) {
+			NMVlanQosMapping *map;
+
+			map = array->pdata[i];
+
+			/* kernel doesn't really send us duplicates. Just be extra cautious
+			 * because we want strong guarantees about the sort order and uniqueness
+			 * of our mapping list (for simpler equality comparison). */
+			if (   j > 0
+			    && list[j - 1].from == map->from)
+				list[j - 1] = *map;
+			else
+				list[j++] = *map;
+		}
+
+		*out_n_map = j;
+		*out_map = list;
 	}
-	label = rtnl_addr_get_label (nlo);
-	/* Check for ':'; we're only interested in labels used as interface aliases */
-	if (label && strchr (label, ':'))
-		g_strlcpy (obj->label, label, sizeof (obj->label));
 
 	return TRUE;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/* Copied and heavily modified from libnl3's vlan_parse() */
+static NMPObject *
+_parse_lnk_vlan (const char *kind, struct nlattr *info_data)
 {
-	NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj;
-	struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
-	struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
-	struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
+	static struct nla_policy policy[IFLA_VLAN_MAX+1] = {
+		[IFLA_VLAN_ID]          = { .type = NLA_U16 },
+		[IFLA_VLAN_FLAGS]       = { .minlen = sizeof(struct ifla_vlan_flags) },
+		[IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
+		[IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
+		[IFLA_VLAN_PROTOCOL]    = { .type = NLA_U16 },
+	};
+	struct nlattr *tb[IFLA_VLAN_MAX+1];
+	int err;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result;
 
-	if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
-		g_return_val_if_reached (FALSE);
+	if (!info_data || g_strcmp0 (kind, "vlan"))
+		return NULL;
+
+	if ((err = nla_parse_nested (tb, IFLA_VLAN_MAX, info_data, policy)) < 0)
+		return NULL;
+
+	if (!tb[IFLA_VLAN_ID])
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL);
+	obj->lnk_vlan.id = nla_get_u16 (tb[IFLA_VLAN_ID]);
+
+	if (tb[IFLA_VLAN_FLAGS]) {
+		struct ifla_vlan_flags flags;
+
+		nla_memcpy (&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
+
+		obj->lnk_vlan.flags = flags.flags;
+	}
+
+	if (!_vlan_qos_mapping_from_nla (tb[IFLA_VLAN_INGRESS_QOS],
+	                                 &obj->_lnk_vlan.ingress_qos_map,
+	                                 &obj->_lnk_vlan.n_ingress_qos_map))
+		return NULL;
+
+	if (!_vlan_qos_mapping_from_nla (tb[IFLA_VLAN_EGRESS_QOS],
+	                                 &obj->_lnk_vlan.egress_qos_map,
+	                                 &obj->_lnk_vlan.n_egress_qos_map))
+		return NULL;
 
-	obj->ifindex = rtnl_addr_get_ifindex (nlo);
-	obj->plen = rtnl_addr_get_prefixlen (nlo);
-	memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
+
+	obj_result = obj;
+	obj = NULL;
+	return obj_result;
+}
+
+/*****************************************************************************/
+
+/* The installed kernel headers might not have VXLAN stuff at all, or
+ * they might have the original properties, but not PORT, GROUP6, or LOCAL6.
+ * So until we depend on kernel >= 3.11, we just ignore the actual enum
+ * in if_link.h and define the values ourselves.
+ */
+#define IFLA_VXLAN_UNSPEC      0
+#define IFLA_VXLAN_ID          1
+#define IFLA_VXLAN_GROUP       2
+#define IFLA_VXLAN_LINK        3
+#define IFLA_VXLAN_LOCAL       4
+#define IFLA_VXLAN_TTL         5
+#define IFLA_VXLAN_TOS         6
+#define IFLA_VXLAN_LEARNING    7
+#define IFLA_VXLAN_AGEING      8
+#define IFLA_VXLAN_LIMIT       9
+#define IFLA_VXLAN_PORT_RANGE 10
+#define IFLA_VXLAN_PROXY      11
+#define IFLA_VXLAN_RSC        12
+#define IFLA_VXLAN_L2MISS     13
+#define IFLA_VXLAN_L3MISS     14
+#define IFLA_VXLAN_PORT       15
+#define IFLA_VXLAN_GROUP6     16
+#define IFLA_VXLAN_LOCAL6     17
+#undef IFLA_VXLAN_MAX
+#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
+
+/* older kernel header might not contain 'struct ifla_vxlan_port_range'.
+ * Redefine it. */
+struct nm_ifla_vxlan_port_range {
+	guint16 low;
+	guint16 high;
+};
+
+static NMPObject *
+_parse_lnk_vxlan (const char *kind, struct nlattr *info_data)
+{
+	static 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,
+		                            .minlen = sizeof (struct in6_addr) },
+		[IFLA_VXLAN_LINK]       = { .type = NLA_U32 },
+		[IFLA_VXLAN_LOCAL]      = { .type = NLA_U32 },
+		[IFLA_VXLAN_LOCAL6]     = { .type = NLA_UNSPEC,
+		                            .minlen = sizeof (struct in6_addr) },
+		[IFLA_VXLAN_TOS]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_TTL]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_LEARNING]   = { .type = NLA_U8 },
+		[IFLA_VXLAN_AGEING]     = { .type = NLA_U32 },
+		[IFLA_VXLAN_LIMIT]      = { .type = NLA_U32 },
+		[IFLA_VXLAN_PORT_RANGE] = { .type = NLA_UNSPEC,
+		                            .minlen  = sizeof (struct nm_ifla_vxlan_port_range) },
+		[IFLA_VXLAN_PROXY]      = { .type = NLA_U8 },
+		[IFLA_VXLAN_RSC]        = { .type = NLA_U8 },
+		[IFLA_VXLAN_L2MISS]     = { .type = NLA_U8 },
+		[IFLA_VXLAN_L3MISS]     = { .type = NLA_U8 },
+		[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
+	};
+	NMPlatformLnkVxlan *props;
+	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
+	struct nm_ifla_vxlan_port_range *range;
+	int err;
+	NMPObject *obj;
+
+	if (!info_data || g_strcmp0 (kind, "vxlan"))
+		return NULL;
+
+	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data, policy);
+	if (err < 0)
+		return NULL;
+
+	obj = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL);
+
+	props = &obj->lnk_vxlan;
+
+	if (tb[IFLA_VXLAN_LINK])
+		props->parent_ifindex = nla_get_u32 (tb[IFLA_VXLAN_LINK]);
+	if (tb[IFLA_VXLAN_ID])
+		props->id = nla_get_u32 (tb[IFLA_VXLAN_ID]);
+	if (tb[IFLA_VXLAN_GROUP])
+		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
+	if (tb[IFLA_VXLAN_LOCAL])
+		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
+	if (tb[IFLA_VXLAN_GROUP6])
+		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
+	if (tb[IFLA_VXLAN_LOCAL6])
+		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+
+	if (tb[IFLA_VXLAN_AGEING])
+		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
+	if (tb[IFLA_VXLAN_LIMIT])
+		props->limit = nla_get_u32 (tb[IFLA_VXLAN_LIMIT]);
+	if (tb[IFLA_VXLAN_TOS])
+		props->tos = nla_get_u8 (tb[IFLA_VXLAN_TOS]);
+	if (tb[IFLA_VXLAN_TTL])
+		props->ttl = nla_get_u8 (tb[IFLA_VXLAN_TTL]);
+
+	if (tb[IFLA_VXLAN_PORT])
+		props->dst_port = ntohs (nla_get_u16 (tb[IFLA_VXLAN_PORT]));
+
+	if (tb[IFLA_VXLAN_PORT_RANGE]) {
+		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
+		props->src_port_min = ntohs (range->low);
+		props->src_port_max = ntohs (range->high);
+	}
+
+	if (tb[IFLA_VXLAN_LEARNING])
+		props->learning = !!nla_get_u8 (tb[IFLA_VXLAN_LEARNING]);
+	if (tb[IFLA_VXLAN_PROXY])
+		props->proxy = !!nla_get_u8 (tb[IFLA_VXLAN_PROXY]);
+	if (tb[IFLA_VXLAN_RSC])
+		props->rsc = !!nla_get_u8 (tb[IFLA_VXLAN_RSC]);
+	if (tb[IFLA_VXLAN_L2MISS])
+		props->l2miss = !!nla_get_u8 (tb[IFLA_VXLAN_L2MISS]);
+	if (tb[IFLA_VXLAN_L3MISS])
+		props->l3miss = !!nla_get_u8 (tb[IFLA_VXLAN_L3MISS]);
+
+	return obj;
+}
+
+/*****************************************************************************/
+
+/* Copied and heavily modified from libnl3's link_msg_parser(). */
+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] = {
+		[IFLA_IFNAME]           = { .type = NLA_STRING,
+		                            .maxlen = IFNAMSIZ },
+		[IFLA_MTU]              = { .type = NLA_U32 },
+		[IFLA_TXQLEN]           = { .type = NLA_U32 },
+		[IFLA_LINK]             = { .type = NLA_U32 },
+		[IFLA_WEIGHT]           = { .type = NLA_U32 },
+		[IFLA_MASTER]           = { .type = NLA_U32 },
+		[IFLA_OPERSTATE]        = { .type = NLA_U8 },
+		[IFLA_LINKMODE]         = { .type = NLA_U8 },
+		[IFLA_LINKINFO]         = { .type = NLA_NESTED },
+		[IFLA_QDISC]            = { .type = NLA_STRING,
+		                            .maxlen = IFQDISCSIZ },
+		[IFLA_STATS]            = { .minlen = sizeof(struct rtnl_link_stats) },
+		[IFLA_STATS64]          = { .minlen = sizeof(struct rtnl_link_stats64)},
+		[IFLA_MAP]              = { .minlen = sizeof(struct rtnl_link_ifmap) },
+		[IFLA_IFALIAS]          = { .type = NLA_STRING, .maxlen = IFALIASZ },
+		[IFLA_NUM_VF]           = { .type = NLA_U32 },
+		[IFLA_AF_SPEC]          = { .type = NLA_NESTED },
+		[IFLA_PROMISCUITY]      = { .type = NLA_U32 },
+		[IFLA_NUM_TX_QUEUES]    = { .type = NLA_U32 },
+		[IFLA_NUM_RX_QUEUES]    = { .type = NLA_U32 },
+		[IFLA_GROUP]            = { .type = NLA_U32 },
+		[IFLA_CARRIER]          = { .type = NLA_U8 },
+		[IFLA_PHYS_PORT_ID]     = { .type = NLA_UNSPEC },
+		[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] = {
+		[IFLA_INFO_KIND]        = { .type = NLA_STRING },
+		[IFLA_INFO_DATA]        = { .type = NLA_NESTED },
+		[IFLA_INFO_XSTATS]      = { .type = NLA_NESTED },
+	};
+	const struct ifinfomsg *ifi;
+	struct nlattr *tb[IFLA_MAX+1];
+	struct nlattr *li[IFLA_INFO_MAX+1];
+	struct nlattr *nl_info_data = NULL;
+	const char *nl_info_kind = NULL;
+	int err;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	gboolean completed_from_cache_val = FALSE;
+	gboolean *completed_from_cache = cache ? &completed_from_cache_val : NULL;
+	const NMPObject *link_cached = NULL;
+	NMPObject *lnk_data = NULL;
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*ifi)))
+		return NULL;
+	ifi = nlmsg_data(nlh);
+
+	obj = nmp_object_new_link (ifi->ifi_index);
 
 	if (id_only)
-		return TRUE;
+		goto id_only_handled;
+
+	err = nlmsg_parse (nlh, sizeof (*ifi), tb, IFLA_MAX, policy);
+	if (err < 0)
+		goto errout;
+
+	if (!tb[IFLA_IFNAME])
+		goto errout;
+	nla_strlcpy(obj->link.name, tb[IFLA_IFNAME], IFNAMSIZ);
+	if (!obj->link.name[0])
+		goto errout;
 
-	obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
-	_nlo_rtnl_addr_get_lifetimes (nlo,
-	                              &obj->timestamp,
-	                              &obj->lifetime,
-	                              &obj->preferred);
-	obj->flags = rtnl_addr_get_flags (nlo);
+	if (tb[IFLA_LINKINFO]) {
+		err = nla_parse_nested (li, IFLA_INFO_MAX, tb[IFLA_LINKINFO], policy_link_info);
+		if (err < 0)
+			goto errout;
 
-	if (nlpeer) {
-		if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
-			g_warn_if_reached ();
+		if (li[IFLA_INFO_KIND])
+			nl_info_kind = nla_get_string (li[IFLA_INFO_KIND]);
+
+		nl_info_data = li[IFLA_INFO_DATA];
+	}
+
+	obj->link.flags = ifi->ifi_flags;
+	obj->link.connected = NM_FLAGS_HAS (obj->link.flags, IFF_LOWER_UP);
+	obj->link.arptype = ifi->ifi_type;
+
+	obj->link.type = _linktype_get_type (platform,
+	                                     cache,
+	                                     nl_info_kind,
+	                                     obj->link.ifindex,
+	                                     obj->link.name,
+	                                     obj->link.flags,
+	                                     obj->link.arptype,
+	                                     completed_from_cache,
+	                                     &link_cached,
+	                                     &obj->link.kind);
+
+	if (tb[IFLA_MASTER])
+		obj->link.master = nla_get_u32 (tb[IFLA_MASTER]);
+
+	if (tb[IFLA_LINK]) {
+		if (!tb[IFLA_LINK_NETNSID])
+			obj->link.parent = nla_get_u32 (tb[IFLA_LINK]);
 		else
-			memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
+			obj->link.parent = NM_PLATFORM_LINK_OTHER_NETNS;
 	}
 
-	return TRUE;
+	if (tb[IFLA_ADDRESS]) {
+		int l = nla_len (tb[IFLA_ADDRESS]);
+
+		if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
+			G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->link.addr.data));
+			memcpy (obj->link.addr.data, nla_data (tb[IFLA_ADDRESS]), l);
+			obj->link.addr.len = l;
+		}
+	}
+
+	if (tb[IFLA_AF_SPEC]) {
+		struct nlattr *af_attr;
+		int remaining;
+
+		nla_for_each_nested (af_attr, tb[IFLA_AF_SPEC], remaining) {
+			switch (nla_type (af_attr)) {
+			case AF_INET6:
+				_parse_af_inet6 (platform,
+				                 af_attr,
+				                 &obj->link.inet6_token.iid,
+				                 &obj->link.inet6_token.is_valid,
+				                 &obj->link.inet6_addr_gen_mode_inv);
+				break;
+			}
+		}
+	}
+
+	if (tb[IFLA_MTU])
+		obj->link.mtu = nla_get_u32 (tb[IFLA_MTU]);
+
+	switch (obj->link.type) {
+	case NM_LINK_TYPE_GRE:
+		lnk_data = _parse_lnk_gre (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_INFINIBAND:
+		lnk_data = _parse_lnk_infiniband (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_IP6TNL:
+		lnk_data = _parse_lnk_ip6tnl (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_IPIP:
+		lnk_data = _parse_lnk_ipip (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_MACVLAN:
+	case NM_LINK_TYPE_MACVTAP:
+		lnk_data = _parse_lnk_macvlan (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_SIT:
+		lnk_data = _parse_lnk_sit (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_VLAN:
+		lnk_data = _parse_lnk_vlan (nl_info_kind, nl_info_data);
+		break;
+	case NM_LINK_TYPE_VXLAN:
+		lnk_data = _parse_lnk_vxlan (nl_info_kind, nl_info_data);
+		break;
+	default:
+		goto lnk_data_handled;
+	}
+
+	/* We always try to look into the cache and reuse the object there.
+	 * We do that, because we consider the lnk object as immutable and don't
+	 * modify it after creating. Hence we can share it and reuse.
+	 *
+	 * Also, sometimes the info-data is missing for updates. In this case
+	 * we want to keep the previously received lnk_data. */
+	if (completed_from_cache) {
+		_lookup_cached_link (cache, obj->link.ifindex, completed_from_cache, &link_cached);
+		if (   link_cached
+		    && link_cached->link.type == obj->link.type
+		    && (   !lnk_data
+		        || nmp_object_equal (lnk_data, link_cached->_link.netlink.lnk))) {
+			nmp_object_unref (lnk_data);
+			lnk_data = nmp_object_ref (link_cached->_link.netlink.lnk);
+		}
+	}
+
+lnk_data_handled:
+	obj->_link.netlink.lnk = lnk_data;
+
+	obj->_link.netlink.is_in_netlink = TRUE;
+id_only_handled:
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+/* Copied and heavily modified from libnl3's addr_msg_parser(). */
+static NMPObject *
+_new_from_nl_addr (struct nlmsghdr *nlh, gboolean id_only)
 {
-	NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj;
-	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
-	struct nl_addr *dst, *gw;
-	struct rtnl_nexthop *nexthop;
-	struct nl_addr *pref_src;
+	static struct nla_policy policy[IFA_MAX+1] = {
+		[IFA_LABEL]     = { .type = NLA_STRING,
+		                     .maxlen = IFNAMSIZ },
+		[IFA_CACHEINFO] = { .minlen = sizeof(struct ifa_cacheinfo) },
+	};
+	const struct ifaddrmsg *ifa;
+	struct nlattr *tb[IFA_MAX+1];
+	int err;
+	gboolean is_v4;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	int addr_len;
+	guint32 lifetime, preferred, timestamp;
 
-	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
-	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
-	    rtnl_route_get_tos (nlo) != 0 ||
-	    rtnl_route_get_nnexthops (nlo) != 1)
-		return FALSE;
+	if (!nlmsg_valid_hdr (nlh, sizeof (*ifa)))
+		return NULL;
+	ifa = nlmsg_data(nlh);
 
-	nexthop = rtnl_route_nexthop_n (nlo, 0);
-	if (!nexthop)
-		g_return_val_if_reached (FALSE);
+	if (!NM_IN_SET (ifa->ifa_family, AF_INET, AF_INET6))
+		goto errout;
+	is_v4 = ifa->ifa_family == AF_INET;
 
-	dst = rtnl_route_get_dst (nlo);
-	if (!dst)
-		g_return_val_if_reached (FALSE);
+	err = nlmsg_parse(nlh, sizeof(*ifa), tb, IFA_MAX, policy);
+	if (err < 0)
+		goto errout;
+
+	addr_len = is_v4
+	           ? sizeof (in_addr_t)
+	           : sizeof (struct in6_addr);
+
+	/*****************************************************************/
+
+	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ADDRESS : NMP_OBJECT_TYPE_IP6_ADDRESS, NULL);
+
+	obj->ip_address.ifindex = ifa->ifa_index;
+	obj->ip_address.plen = ifa->ifa_prefixlen;
+
+	_check_addr_or_errout (tb, IFA_ADDRESS, addr_len);
+	_check_addr_or_errout (tb, IFA_LOCAL, addr_len);
+	if (is_v4) {
+		/* For IPv4, kernel omits IFA_LOCAL/IFA_ADDRESS if (and only if) they
+		 * are effectively 0.0.0.0 (all-zero). */
+		if (tb[IFA_LOCAL])
+			memcpy (&obj->ip4_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
+		if (tb[IFA_ADDRESS])
+			memcpy (&obj->ip4_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+	} else {
+		/* For IPv6, IFA_ADDRESS is always present.
+		 *
+		 * If IFA_LOCAL is missing, IFA_ADDRESS is @address and @peer_address
+		 * is :: (all-zero).
+		 *
+		 * If unexpectely IFA_ADDRESS is missing, make the best of it -- but it _should_
+		 * actually be there. */
+		if (tb[IFA_ADDRESS] || tb[IFA_LOCAL]) {
+			if (tb[IFA_LOCAL]) {
+				memcpy (&obj->ip6_address.address, nla_data (tb[IFA_LOCAL]), addr_len);
+				if (tb[IFA_ADDRESS])
+					memcpy (&obj->ip6_address.peer_address, nla_data (tb[IFA_ADDRESS]), addr_len);
+				else
+					obj->ip6_address.peer_address = obj->ip6_address.address;
+			} else
+				memcpy (&obj->ip6_address.address, nla_data (tb[IFA_ADDRESS]), addr_len);
+		}
+	}
 
-	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (obj->network))
-			g_return_val_if_reached (FALSE);
-		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+	obj->ip_address.source = NM_IP_CONFIG_SOURCE_KERNEL;
+
+	if (!is_v4) {
+		obj->ip6_address.flags = tb[IFA_FLAGS]
+		                         ? nla_get_u32 (tb[IFA_FLAGS])
+		                         : ifa->ifa_flags;
 	}
-	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	obj->plen = nl_addr_get_prefixlen (dst);
-	obj->metric = rtnl_route_get_priority (nlo);
-	obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo));
-
-	gw = rtnl_route_nh_get_gateway (nexthop);
-	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+
+	if (is_v4) {
+		if (tb[IFA_LABEL]) {
+			char label[IFNAMSIZ];
+
+			nla_strlcpy (label, tb[IFA_LABEL], IFNAMSIZ);
+
+			/* Check for ':'; we're only interested in labels used as interface aliases */
+			if (strchr (label, ':'))
+				g_strlcpy (obj->ip4_address.label, label, sizeof (obj->ip4_address.label));
+		}
 	}
-	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
-	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) {
+
+	lifetime = NM_PLATFORM_LIFETIME_PERMANENT;
+	preferred = NM_PLATFORM_LIFETIME_PERMANENT;
+	timestamp = 0;
+	/* IPv6 only */
+	if (tb[IFA_CACHEINFO]) {
+		const struct ifa_cacheinfo *ca = nla_data(tb[IFA_CACHEINFO]);
+
+		lifetime = ca->ifa_valid;
+		preferred = ca->ifa_prefered;
+		timestamp = ca->tstamp;
+	}
+	_addrtime_get_lifetimes (timestamp,
+	                         lifetime,
+	                         preferred,
+	                         &obj->ip_address.timestamp,
+	                         &obj->ip_address.lifetime,
+	                         &obj->ip_address.preferred);
+
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
+}
+
+/* Copied and heavily modified from libnl3's rtnl_route_parse() and parse_multipath(). */
+static NMPObject *
+_new_from_nl_route (struct nlmsghdr *nlh, gboolean id_only)
+{
+	static struct nla_policy policy[RTA_MAX+1] = {
+		[RTA_IIF]       = { .type = NLA_U32 },
+		[RTA_OIF]       = { .type = NLA_U32 },
+		[RTA_PRIORITY]  = { .type = NLA_U32 },
+		[RTA_FLOW]      = { .type = NLA_U32 },
+		[RTA_CACHEINFO] = { .minlen = sizeof(struct rta_cacheinfo) },
+		[RTA_METRICS]   = { .type = NLA_NESTED },
+		[RTA_MULTIPATH] = { .type = NLA_NESTED },
+	};
+	const struct rtmsg *rtm;
+	struct nlattr *tb[RTA_MAX + 1];
+	int err;
+	gboolean is_v4;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	NMPObject *obj_result = NULL;
+	int addr_len;
+	struct {
+		gboolean is_present;
+		int ifindex;
+		NMIPAddr gateway;
+	} nh;
+	guint32 mss;
+	guint32 table;
+
+	if (!nlmsg_valid_hdr (nlh, sizeof (*rtm)))
+		return NULL;
+	rtm = nlmsg_data(nlh);
+
+	/*****************************************************************
+	 * only handle ~normal~ routes.
+	 *****************************************************************/
+
+	if (!NM_IN_SET (rtm->rtm_family, AF_INET, AF_INET6))
+		goto errout;
+
+	if (   rtm->rtm_type != RTN_UNICAST
+	    || rtm->rtm_tos != 0)
+		goto errout;
+
+	err = nlmsg_parse (nlh, sizeof (struct rtmsg), tb, RTA_MAX, policy);
+	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;
+	addr_len = is_v4
+	           ? sizeof (in_addr_t)
+	           : sizeof (struct in6_addr);
+
+	/*****************************************************************
+	 * parse nexthops. Only handle routes with one nh.
+	 *****************************************************************/
+
+	memset (&nh, 0, sizeof (nh));
+
+	if (tb[RTA_MULTIPATH]) {
+		struct rtnexthop *rtnh = nla_data (tb[RTA_MULTIPATH]);
+		size_t tlen = nla_len(tb[RTA_MULTIPATH]);
+
+		while (tlen >= sizeof(*rtnh) && tlen >= rtnh->rtnh_len) {
+
+			if (nh.is_present) {
+				/* we don't support multipath routes. */
+				goto errout;
+			}
+			nh.is_present = TRUE;
+
+			nh.ifindex = rtnh->rtnh_ifindex;
+
+			if (rtnh->rtnh_len > sizeof(*rtnh)) {
+				struct nlattr *ntb[RTA_MAX + 1];
+
+				err = nla_parse (ntb, RTA_MAX, (struct nlattr *)
+				                 RTNH_DATA(rtnh),
+				                 rtnh->rtnh_len - sizeof (*rtnh),
+				                 policy);
+				if (err < 0)
+					goto errout;
+
+				if (_check_addr_or_errout (ntb, RTA_GATEWAY, addr_len))
+					memcpy (&nh.gateway, nla_data (ntb[RTA_GATEWAY]), addr_len);
+			}
+
+			tlen -= RTNH_ALIGN(rtnh->rtnh_len);
+			rtnh = RTNH_NEXT(rtnh);
+		}
+	}
+
+	if (   tb[RTA_OIF]
+	    || tb[RTA_GATEWAY]
+	    || tb[RTA_FLOW]) {
+		int ifindex = 0;
+		NMIPAddr gateway = NMIPAddrInit;
+
+		if (tb[RTA_OIF])
+			ifindex = nla_get_u32 (tb[RTA_OIF]);
+		if (_check_addr_or_errout (tb, RTA_GATEWAY, addr_len))
+			memcpy (&gateway, nla_data (tb[RTA_GATEWAY]), addr_len);
+
+		if (!nh.is_present) {
+			/* If no nexthops have been provided via RTA_MULTIPATH
+			 * we add it as regular nexthop to maintain backwards
+			 * compatibility */
+			nh.ifindex = ifindex;
+			nh.gateway = gateway;
+		} else {
+			/* Kernel supports new style nexthop configuration,
+			 * verify that it is a duplicate and ignore old-style nexthop. */
+			if (   nh.ifindex != ifindex
+			    || memcmp (&nh.gateway, &gateway, addr_len) != 0)
+				goto errout;
+		}
+	} else if (!nh.is_present)
+		goto errout;
+
+	/*****************************************************************/
+
+	mss = 0;
+	if (tb[RTA_METRICS]) {
+		struct nlattr *mtb[RTAX_MAX + 1];
+		int i;
+
+		err = nla_parse_nested(mtb, RTAX_MAX, tb[RTA_METRICS], NULL);
+		if (err < 0)
+			goto errout;
+
+		for (i = 1; i <= RTAX_MAX; i++) {
+			if (mtb[i]) {
+				if (i == RTAX_ADVMSS) {
+					if (nla_len (mtb[i]) >= sizeof (uint32_t))
+						mss = nla_get_u32(mtb[i]);
+					break;
+				}
+			}
+		}
+	}
+
+	/*****************************************************************/
+
+	obj = nmp_object_new (is_v4 ? NMP_OBJECT_TYPE_IP4_ROUTE : NMP_OBJECT_TYPE_IP6_ROUTE, NULL);
+
+	obj->ip_route.ifindex = nh.ifindex;
+
+	if (_check_addr_or_errout (tb, RTA_DST, addr_len))
+		memcpy (obj->ip_route.network_ptr, nla_data (tb[RTA_DST]), addr_len);
+
+	obj->ip_route.plen = rtm->rtm_dst_len;
+
+	if (tb[RTA_PRIORITY])
+		obj->ip_route.metric = nla_get_u32(tb[RTA_PRIORITY]);
+
+	if (is_v4)
+		obj->ip4_route.gateway = nh.gateway.addr4;
+	else
+		obj->ip6_route.gateway = nh.gateway.addr6;
+
+	if (is_v4)
+		obj->ip4_route.scope_inv = nm_platform_route_scope_inv (rtm->rtm_scope);
+
+	if (is_v4) {
+		if (_check_addr_or_errout (tb, RTA_PREFSRC, addr_len))
+			memcpy (&obj->ip4_route.pref_src, nla_data (tb[RTA_PREFSRC]), addr_len);
+	}
+
+	obj->ip_route.mss = mss;
+
+	if (NM_FLAGS_HAS (rtm->rtm_flags, RTM_F_CLONED)) {
 		/* we must not straight way reject cloned routes, because we might have cached
 		 * a non-cloned route. If we now receive an update of the route with the route
 		 * being cloned, we must still return the object, so that we can remove the old
@@ -1361,75 +1876,739 @@ _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject
 		 *
 		 * This happens, because this route is not nmp_object_is_alive().
 		 * */
-		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
+		obj->ip_route.source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
 	} else
-		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
+		obj->ip_route.source = _nm_ip_config_source_from_rtprot (rtm->rtm_protocol);
 
-	pref_src = rtnl_route_get_pref_src (nlo);
-	if (pref_src) {
-		if (nl_addr_get_len (pref_src) != sizeof (obj->pref_src))
-			g_warn_if_reached ();
-		else
-			memcpy (&obj->pref_src, nl_addr_get_binary_addr (pref_src), sizeof (obj->pref_src));
+	obj_result = obj;
+	obj = NULL;
+errout:
+	return obj_result;
+}
+
+/**
+ * nmp_object_new_from_nl:
+ * @platform: (allow-none): for creating certain objects, the constructor wants to check
+ *   sysfs. For this the platform instance is needed. If missing, the object might not
+ *   be correctly detected.
+ * @cache: (allow-none): for certain objects, the netlink message doesn't contain all the information.
+ *   If a cache is given, the object is completed with information from the cache.
+ * @nlh: the netlink message header
+ * @id_only: whether only to create an empty object with only the ID fields set.
+ *
+ * Returns: %NULL or a newly created NMPObject instance.
+ **/
+static NMPObject *
+nmp_object_new_from_nl (NMPlatform *platform, const NMPCache *cache, struct nl_msg *msg, gboolean id_only)
+{
+	struct nlmsghdr *msghdr;
+
+	if (nlmsg_get_proto (msg) != NETLINK_ROUTE)
+		return NULL;
+
+	msghdr = nlmsg_hdr (msg);
+
+	switch (msghdr->nlmsg_type) {
+	case RTM_NEWLINK:
+	case RTM_DELLINK:
+	case RTM_GETLINK:
+	case RTM_SETLINK:
+		return _new_from_nl_link (platform, cache, msghdr, id_only);
+	case RTM_NEWADDR:
+	case RTM_DELADDR:
+	case RTM_GETADDR:
+		return _new_from_nl_addr (msghdr, id_only);
+	case RTM_NEWROUTE:
+	case RTM_DELROUTE:
+	case RTM_GETROUTE:
+		return _new_from_nl_route (msghdr, id_only);
+	default:
+		return NULL;
+	}
+}
+
+/******************************************************************/
+
+static gboolean
+_nl_msg_new_link_set_afspec (struct nl_msg *msg,
+                             int addr_gen_mode)
+{
+	struct nlattr *af_spec;
+	struct nlattr *af_attr;
+
+	nm_assert (msg);
+
+	if (!(af_spec = nla_nest_start (msg, IFLA_AF_SPEC)))
+		goto nla_put_failure;
+
+	if (addr_gen_mode >= 0) {
+		if (!(af_attr = nla_nest_start (msg, AF_INET6)))
+			goto nla_put_failure;
+
+		NLA_PUT_U8 (msg, IFLA_INET6_ADDR_GEN_MODE, addr_gen_mode);
+
+		nla_nest_end (msg, af_attr);
 	}
 
+	nla_nest_end (msg, af_spec);
+
 	return TRUE;
+nla_put_failure:
+	return FALSE;
 }
 
-gboolean
-_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
+static gboolean
+_nl_msg_new_link_set_linkinfo (struct nl_msg *msg,
+                               NMLinkType link_type)
 {
-	NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
-	struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
-	struct nl_addr *dst, *gw;
-	struct rtnl_nexthop *nexthop;
+	struct nlattr *info;
+	const char *kind;
+
+	nm_assert (msg);
+
+	kind = nm_link_type_to_rtnl_type_string (link_type);
+	if (!kind)
+		goto nla_put_failure;
+
+	if (!(info = nla_nest_start (msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, IFLA_INFO_KIND, kind);
+
+	nla_nest_end (msg, info);
+
+	return TRUE;
+nla_put_failure:
+	return FALSE;
+}
+
+static gboolean
+_nl_msg_new_link_set_linkinfo_vlan (struct nl_msg *msg,
+                                    int vlan_id,
+                                    guint32 flags_mask,
+                                    guint32 flags_set,
+                                    const NMVlanQosMapping *ingress_qos,
+                                    int ingress_qos_len,
+                                    const NMVlanQosMapping *egress_qos,
+                                    int egress_qos_len)
+{
+	struct nlattr *info;
+	struct nlattr *data;
+	guint i;
+	gboolean has_any_vlan_properties = FALSE;
+
+#define VLAN_XGRESS_PRIO_VALID(from) (((from) & ~(guint32) 0x07) == 0)
+
+	nm_assert (msg);
+
+	/* We must not create an empty IFLA_LINKINFO section. Otherwise, kernel
+	 * rejects the request as invalid. */
+	if (   flags_mask != 0
+	    || vlan_id >= 0)
+		has_any_vlan_properties = TRUE;
+	if (   !has_any_vlan_properties
+	    && ingress_qos && ingress_qos_len > 0) {
+		for (i = 0; i < ingress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (ingress_qos[i].from)) {
+				has_any_vlan_properties = TRUE;
+				break;
+			}
+		}
+	}
+	if (   !has_any_vlan_properties
+	    && egress_qos && egress_qos_len > 0) {
+		for (i = 0; i < egress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
+				has_any_vlan_properties = TRUE;
+				break;
+			}
+		}
+	}
+	if (!has_any_vlan_properties)
+		return TRUE;
+
+	if (!(info = nla_nest_start (msg, IFLA_LINKINFO)))
+		goto nla_put_failure;
+
+	NLA_PUT_STRING (msg, IFLA_INFO_KIND, "vlan");
+
+	if (!(data = nla_nest_start (msg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
+
+	if (vlan_id >= 0)
+		NLA_PUT_U16 (msg, IFLA_VLAN_ID, vlan_id);
+
+	if (flags_mask != 0) {
+		struct ifla_vlan_flags flags = {
+			.flags = flags_mask & flags_set,
+			.mask = flags_mask,
+		};
+
+		NLA_PUT (msg, IFLA_VLAN_FLAGS, sizeof (flags), &flags);
+	}
+
+	if (ingress_qos && ingress_qos_len > 0) {
+		struct nlattr *qos = NULL;
+
+		for (i = 0; i < ingress_qos_len; i++) {
+			/* Silently ignore invalid mappings. Kernel would truncate
+			 * them and modify the wrong mapping. */
+			if (VLAN_XGRESS_PRIO_VALID (ingress_qos[i].from)) {
+				if (!qos) {
+					if (!(qos = nla_nest_start (msg, IFLA_VLAN_INGRESS_QOS)))
+						goto nla_put_failure;
+				}
+				NLA_PUT (msg, i, sizeof (ingress_qos[i]), &ingress_qos[i]);
+			}
+		}
+
+		if (qos)
+			nla_nest_end (msg, qos);
+	}
+
+	if (egress_qos && egress_qos_len > 0) {
+		struct nlattr *qos = NULL;
+
+		for (i = 0; i < egress_qos_len; i++) {
+			if (VLAN_XGRESS_PRIO_VALID (egress_qos[i].to)) {
+				if (!qos) {
+					if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
+						goto nla_put_failure;
+				}
+				NLA_PUT (msg, i, sizeof (egress_qos[i]), &egress_qos[i]);
+			}
+		}
+
+		if (qos)
+			nla_nest_end(msg, qos);
+	}
+
+	nla_nest_end (msg, data);
+	nla_nest_end (msg, info);
+
+	return TRUE;
+nla_put_failure:
+	return FALSE;
+}
+
+static struct nl_msg *
+_nl_msg_new_link (int nlmsg_type,
+                  int nlmsg_flags,
+                  int ifindex,
+                  const char *ifname,
+                  unsigned flags_mask,
+                  unsigned flags_set)
+{
+	struct nl_msg *msg;
+	struct ifinfomsg ifi = {
+		.ifi_change = flags_mask,
+		.ifi_flags = flags_set,
+		.ifi_index = ifindex,
+	};
+
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_DELLINK, RTM_NEWLINK, RTM_GETLINK));
+
+	if (!(msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags)))
+		g_return_val_if_reached (NULL);
+
+	if (nlmsg_append (msg, &ifi, sizeof (ifi), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (ifname)
+		NLA_PUT_STRING (msg, IFLA_IFNAME, ifname);
+
+	return msg;
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+/* Copied and modified from libnl3's build_addr_msg(). */
+static struct nl_msg *
+_nl_msg_new_address (int nlmsg_type,
+                     int nlmsg_flags,
+                     int family,
+                     int ifindex,
+                     gconstpointer address,
+                     int plen,
+                     gconstpointer peer_address,
+                     guint32 flags,
+                     int scope,
+                     guint32 lifetime,
+                     guint32 preferred,
+                     const char *label)
+{
+	struct nl_msg *msg;
+	struct ifaddrmsg am = {
+		.ifa_family = family,
+		.ifa_index = ifindex,
+		.ifa_prefixlen = plen,
+		.ifa_flags = flags,
+	};
+	gsize addr_len;
+
+	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWADDR, RTM_DELADDR));
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	if (!msg)
+		g_return_val_if_reached (NULL);
+
+	if (scope == -1) {
+		/* Allow having scope unset, and detect the scope (including IPv4 compatibility hack). */
+		if (   family == AF_INET
+		    && address
+		    && *((char *) address) == 127)
+			scope = RT_SCOPE_HOST;
+		else
+			scope = RT_SCOPE_UNIVERSE;
+	}
+	am.ifa_scope = scope,
+
+	addr_len = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
+
+	if (nlmsg_append (msg, &am, sizeof (am), NLMSG_ALIGNTO) < 0)
+		goto nla_put_failure;
+
+	if (address)
+		NLA_PUT (msg, IFA_LOCAL, addr_len, address);
+
+	if (peer_address)
+		NLA_PUT (msg, IFA_ADDRESS, addr_len, peer_address);
+	else if (address)
+		NLA_PUT (msg, IFA_ADDRESS, addr_len, address);
+
+	if (label && label[0])
+		NLA_PUT_STRING (msg, IFA_LABEL, label);
+
+	if (   family == AF_INET
+	    && nlmsg_type != RTM_DELADDR
+	    && address
+	    && *((in_addr_t *) address) != 0) {
+		in_addr_t broadcast;
+
+		broadcast = *((in_addr_t *) address) | ~nm_utils_ip4_prefix_to_netmask (plen);
+		NLA_PUT (msg, IFA_BROADCAST, addr_len, &broadcast);
+	}
+
+	if (   lifetime != NM_PLATFORM_LIFETIME_PERMANENT
+	    || preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
+		struct ifa_cacheinfo ca = {
+			.ifa_valid = lifetime,
+			.ifa_prefered = preferred,
+		};
+
+		NLA_PUT (msg, IFA_CACHEINFO, sizeof(ca), &ca);
+	}
+
+	if (flags & ~0xFF) {
+		/* only set the IFA_FLAGS attribute, if they actually contain additional
+		 * flags that are not already set to am.ifa_flags.
+		 *
+		 * Older kernels refuse RTM_NEWADDR and RTM_NEWROUTE messages with EINVAL
+		 * if they contain unknown netlink attributes. See net/core/rtnetlink.c, which
+		 * was fixed by kernel commit 661d2967b3f1b34eeaa7e212e7b9bbe8ee072b59. */
+		NLA_PUT_U32 (msg, IFA_FLAGS, flags);
+	}
+
+	return msg;
+
+nla_put_failure:
+	nlmsg_free (msg);
+	g_return_val_if_reached (NULL);
+}
+
+/* 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,
+                   int plen,
+                   gconstpointer gateway,
+                   guint32 metric,
+                   guint32 mss,
+                   gconstpointer pref_src)
+{
+	struct nl_msg *msg;
+	struct rtmsg rtmsg = {
+		.rtm_family = family,
+		.rtm_tos = 0,
+		.rtm_table = RT_TABLE_MAIN, /* omit setting RTA_TABLE attribute */
+		.rtm_protocol = _nm_ip_config_source_to_rtprot (source),
+		.rtm_scope = scope,
+		.rtm_type = RTN_UNICAST,
+		.rtm_flags = 0,
+		.rtm_dst_len = plen,
+		.rtm_src_len = 0,
+	};
+	NMIPAddr network_clean;
+
+	gsize addr_len;
+
+	nm_assert (NM_IN_SET (family, AF_INET, AF_INET6));
+	nm_assert (NM_IN_SET (nlmsg_type, RTM_NEWROUTE, RTM_DELROUTE));
+	nm_assert (network);
+
+	msg = nlmsg_alloc_simple (nlmsg_type, nlmsg_flags);
+	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);
+
+	clear_host_address (family, network, plen, &network_clean);
+	NLA_PUT (msg, RTA_DST, addr_len, &network_clean);
+
+	NLA_PUT_U32 (msg, RTA_PRIORITY, metric);
+
+	if (pref_src)
+		NLA_PUT (msg, RTA_PREFSRC, addr_len, pref_src);
+
+	if (mss > 0) {
+		struct nlattr *metrics;
+
+		metrics = nla_nest_start (msg, RTA_METRICS);
+		if (!metrics)
+			goto nla_put_failure;
+
+		NLA_PUT_U32 (msg, RTAX_ADVMSS, mss);
+
+		nla_nest_end(msg, metrics);
+	}
+
+	/* 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);
+
+	return msg;
+
+nla_put_failure:
+	nlmsg_free (msg);
+	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), 8 /* IFA_FLAGS */);
+	_LOG2D ("support: kernel-extended-ifa-flags: %ssupported", _support_kernel_extended_ifa_flags ? "" : "not ");
+}
+
+static gboolean
+_support_kernel_extended_ifa_flags_get (void)
+{
+	if (_support_kernel_extended_ifa_flags_still_undecided ()) {
+		_LOG2W ("support: kernel-extended-ifa-flags: unable to detect kernel support for handling IPv6 temporary addresses. Assume none");
+		_support_kernel_extended_ifa_flags = 0;
+	}
+	return _support_kernel_extended_ifa_flags;
+}
+
+/******************************************************************
+ * NMPlatform types and functions
+ ******************************************************************/
+
+typedef struct {
+	guint32 seq_number;
+	gint64 timeout_abs_ns;
+	WaitForNlResponseResult seq_result;
+	WaitForNlResponseResult *out_seq_result;
+} DelayedActionWaitForNlResponseData;
+
+typedef struct _NMLinuxPlatformPrivate NMLinuxPlatformPrivate;
+
+struct _NMLinuxPlatformPrivate {
+	struct nl_sock *nlh;
+	guint32 nlh_seq_next;
+	guint32 nlh_seq_last_handled;
+	NMPCache *cache;
+	GIOChannel *event_channel;
+	guint event_id;
+
+	gboolean sysctl_get_warned;
+	GHashTable *sysctl_get_prev_values;
+
+	GUdevClient *udev_client;
+
+	struct {
+		DelayedActionType flags;
+		GPtrArray *list_master_connected;
+		GPtrArray *list_refresh_link;
+		GArray *list_wait_for_nl_response;
+		gint is_handling;
+	} delayed_action;
+
+	GHashTable *prune_candidates;
+
+	GHashTable *wifi_data;
+};
+
+static inline NMLinuxPlatformPrivate *
+NM_LINUX_PLATFORM_GET_PRIVATE (const void *self)
+{
+	nm_assert (NM_IS_LINUX_PLATFORM (self));
+
+	return ((NMLinuxPlatform *) self)->priv;
+}
+
+G_DEFINE_TYPE (NMLinuxPlatform, nm_linux_platform, NM_TYPE_PLATFORM)
+
+void
+nm_linux_platform_setup (void)
+{
+	g_object_new (NM_TYPE_LINUX_PLATFORM,
+	              NM_PLATFORM_REGISTER_SINGLETON, TRUE,
+	              NULL);
+}
 
-	if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
-	    rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
-	    rtnl_route_get_tos (nlo) != 0 ||
-	    rtnl_route_get_nnexthops (nlo) != 1)
+/******************************************************************/
+
+static void
+_log_dbg_sysctl_set_impl (NMPlatform *platform, const char *path, const char *value)
+{
+	GError *error = NULL;
+	char *contents, *contents_escaped;
+	char *value_escaped = g_strescape (value, NULL);
+
+	if (!g_file_get_contents (path, &contents, NULL, &error)) {
+		_LOGD ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message);
+		g_clear_error (&error);
+	} else {
+		g_strstrip (contents);
+		contents_escaped = g_strescape (contents, NULL);
+		if (strcmp (contents, value) == 0)
+			_LOGD ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped);
+		else
+			_LOGD ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped);
+		g_free (contents);
+		g_free (contents_escaped);
+	}
+	g_free (value_escaped);
+}
+
+#define _log_dbg_sysctl_set(platform, path, value) \
+	G_STMT_START { \
+		if (_LOGD_ENABLED ()) { \
+			_log_dbg_sysctl_set_impl (platform, path, value); \
+		} \
+	} G_STMT_END
+
+static gboolean
+sysctl_set (NMPlatform *platform, const char *path, const char *value)
+{
+	int fd, len, nwrote, tries;
+	char *actual;
+
+	g_return_val_if_fail (path != NULL, FALSE);
+	g_return_val_if_fail (value != NULL, FALSE);
+
+	/* Don't write outside known locations */
+	g_assert (g_str_has_prefix (path, "/proc/sys/")
+	          || g_str_has_prefix (path, "/sys/"));
+	/* Don't write to suspicious locations */
+	g_assert (!strstr (path, "/../"));
+
+	fd = open (path, O_WRONLY | O_TRUNC);
+	if (fd == -1) {
+		if (errno == ENOENT) {
+			_LOGD ("sysctl: failed to open '%s': (%d) %s",
+			       path, errno, strerror (errno));
+		} else {
+			_LOGE ("sysctl: failed to open '%s': (%d) %s",
+			       path, errno, strerror (errno));
+		}
 		return FALSE;
+	}
 
-	nexthop = rtnl_route_nexthop_n (nlo, 0);
-	if (!nexthop)
-		g_return_val_if_reached (FALSE);
+	_log_dbg_sysctl_set (platform, path, value);
 
-	dst = rtnl_route_get_dst (nlo);
-	if (!dst)
-		g_return_val_if_reached (FALSE);
+	/* Most sysfs and sysctl options don't care about a trailing LF, while some
+	 * (like infiniband) do.  So always add the LF.  Also, neither sysfs nor
+	 * sysctl support partial writes so the LF must be added to the string we're
+	 * about to write.
+	 */
+	actual = g_strdup_printf ("%s\n", value);
 
-	if (nl_addr_get_len (dst)) {
-		if (nl_addr_get_len (dst) != sizeof (obj->network))
-			g_return_val_if_reached (FALSE);
-		memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
+	/* Try to write the entire value three times if a partial write occurs */
+	len = strlen (actual);
+	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
+		nwrote = write (fd, actual, len);
+		if (nwrote == -1) {
+			if (errno == EINTR) {
+				_LOGD ("sysctl: interrupted, will try again");
+				continue;
+			}
+			break;
+		}
+	}
+	if (nwrote == -1 && errno != EEXIST) {
+		_LOGE ("sysctl: failed to set '%s' to '%s': (%d) %s",
+		       path, value, errno, strerror (errno));
+	} else if (nwrote < len) {
+		_LOGE ("sysctl: failed to set '%s' to '%s' after three attempts",
+		       path, value);
 	}
-	obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
-	obj->plen = nl_addr_get_prefixlen (dst);
-	obj->metric = rtnl_route_get_priority (nlo);
 
-	if (id_only)
-		return TRUE;
+	g_free (actual);
+	close (fd);
+	return (nwrote == len);
+}
+
+static GSList *sysctl_clear_cache_list;
+
+void
+_nm_linux_platform_sysctl_clear_cache (void)
+{
+	while (sysctl_clear_cache_list) {
+		NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (sysctl_clear_cache_list->data);
+
+		sysctl_clear_cache_list = g_slist_delete_link (sysctl_clear_cache_list, sysctl_clear_cache_list);
+
+		g_hash_table_destroy (priv->sysctl_get_prev_values);
+		priv->sysctl_get_prev_values = NULL;
+		priv->sysctl_get_warned = FALSE;
+	}
+}
+
+static void
+_log_dbg_sysctl_get_impl (NMPlatform *platform, const char *path, const char *contents)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	const char *prev_value = NULL;
+
+	if (!priv->sysctl_get_prev_values) {
+		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);
+	} else
+		prev_value = g_hash_table_lookup (priv->sysctl_get_prev_values, path);
+
+	if (prev_value) {
+		if (strcmp (prev_value, contents) != 0) {
+			char *contents_escaped = g_strescape (contents, NULL);
+			char *prev_value_escaped = g_strescape (prev_value, NULL);
+
+			_LOGD ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped);
+			g_free (contents_escaped);
+			g_free (prev_value_escaped);
+			g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
+		}
+	} else {
+		char *contents_escaped = g_strescape (contents, NULL);
 
-	gw = rtnl_route_nh_get_gateway (nexthop);
-	if (gw) {
-		if (nl_addr_get_len (gw) != sizeof (obj->gateway))
-			g_warn_if_reached ();
+		_LOGD ("sysctl: reading '%s': '%s'", path, contents_escaped);
+		g_free (contents_escaped);
+		g_hash_table_insert (priv->sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
+	}
+
+	if (   !priv->sysctl_get_warned
+	    && g_hash_table_size (priv->sysctl_get_prev_values) > 50000) {
+		_LOGW ("sysctl: the internal cache for debug-logging of sysctl values grew pretty large. You can clear it by disabling debug-logging: `nmcli general logging level KEEP domains PLATFORM:INFO`.");
+		priv->sysctl_get_warned = TRUE;
+	}
+}
+
+#define _log_dbg_sysctl_get(platform, path, contents) \
+	G_STMT_START { \
+		if (_LOGD_ENABLED ()) \
+			_log_dbg_sysctl_get_impl (platform, path, contents); \
+	} G_STMT_END
+
+static char *
+sysctl_get (NMPlatform *platform, const char *path)
+{
+	GError *error = NULL;
+	char *contents;
+
+	/* Don't write outside known locations */
+	g_assert (g_str_has_prefix (path, "/proc/sys/")
+	          || g_str_has_prefix (path, "/sys/"));
+	/* Don't write to suspicious locations */
+	g_assert (!strstr (path, "/../"));
+
+	if (!g_file_get_contents (path, &contents, NULL, &error)) {
+		/* We assume FAILED means EOPNOTSUP */
+		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
+		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED))
+			_LOGD ("error reading %s: %s", path, error->message);
 		else
-			memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
+			_LOGE ("error reading %s: %s", path, error->message);
+		g_clear_error (&error);
+		return NULL;
 	}
-	rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
-	if (rtnl_route_get_flags (nlo) & RTM_F_CLONED)
-		obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
-	else
-		obj->source = _nm_ip_config_source_from_rtprot (rtnl_route_get_protocol (nlo));
 
-	return TRUE;
+	g_strstrip (contents);
+
+	_log_dbg_sysctl_get (platform, path, contents);
+
+	return contents;
 }
 
 /******************************************************************/
 
+static gboolean
+check_support_kernel_extended_ifa_flags (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+
+	return _support_kernel_extended_ifa_flags_get ();
+}
+
+static gboolean
+check_support_user_ipv6ll (NMPlatform *platform)
+{
+	g_return_val_if_fail (NM_IS_LINUX_PLATFORM (platform), FALSE);
+
+	return _support_user_ipv6ll_get ();
+}
+
 static void
-do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible, NMPlatformReason reason)
+process_events (NMPlatform *platform)
+{
+	delayed_action_handle_all (platform, TRUE);
+}
+
+/******************************************************************/
+
+#define cache_lookup_all_objects(type, platform, obj_type, visible_only) \
+	((const type *const*) nmp_cache_lookup_multi (NM_LINUX_PLATFORM_GET_PRIVATE ((platform))->cache, \
+	                                              nmp_cache_id_init_object_type (NMP_CACHE_ID_STATIC, (obj_type), (visible_only)), \
+	                                              NULL))
+
+/******************************************************************/
+
+static void
+do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cache_op, gboolean was_visible)
 {
 	gboolean is_visible;
 	NMPObject obj_clone;
@@ -1476,16 +2655,21 @@ do_emit_signal (NMPlatform *platform, const NMPObject *obj, NMPCacheOpsType cach
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
-	_LOGt ("emit signal %s %s: %s (%ld)",
+	_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),
-	       (long) reason);
+	       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_by_name (platform, klass->signal_type, klass->obj_type, obj_clone.object.ifindex, &obj_clone.object, (NMPlatformSignalChangeType) cache_op, reason);
+	g_signal_emit (platform,
+	               _nm_platform_signal_id_get (klass->signal_type_id),
+	               0,
+	               klass->obj_type,
+	               obj_clone.object.ifindex,
+	               &obj_clone.object,
+	               (NMPlatformSignalChangeType) cache_op);
 }
 
 /******************************************************************/
@@ -1528,42 +2712,140 @@ delayed_action_to_string (DelayedActionType action_type)
 	case DELAYED_ACTION_TYPE_REFRESH_LINK                   : return "refresh-link";
 	case DELAYED_ACTION_TYPE_MASTER_CONNECTED               : return "master-connected";
 	case DELAYED_ACTION_TYPE_READ_NETLINK                   : return "read-netlink";
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE           : return "wait-for-nl-response";
 	default:
 		return "unknown";
 	}
 }
 
-#define _LOGt_delayed_action(action_type, arg, operation) \
-    _LOGt ("delayed-action: %s %s (%d) [%p / %d]", ""operation, delayed_action_to_string (action_type), (int) action_type, arg, GPOINTER_TO_INT (arg))
+static const char *
+delayed_action_to_string_full (DelayedActionType action_type, gpointer user_data, char *buf, gsize buf_size)
+{
+	char *buf0 = buf;
+	const DelayedActionWaitForNlResponseData *data;
+
+	nm_utils_strbuf_append_str (&buf, &buf_size, delayed_action_to_string (action_type));
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
+		nm_utils_strbuf_append (&buf, &buf_size, " (master-ifindex %d)", GPOINTER_TO_INT (user_data));
+		break;
+	case DELAYED_ACTION_TYPE_REFRESH_LINK:
+		nm_utils_strbuf_append (&buf, &buf_size, " (ifindex %d)", GPOINTER_TO_INT (user_data));
+		break;
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
+		data = user_data;
+
+		if (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)",
+			                        data->seq_number,
+			                        timeout < 0 ? "-" : "",
+			                        (timeout < 0 ? -timeout : timeout) / NM_UTILS_NS_PER_SECOND,
+			                        (timeout < 0 ? -timeout : timeout) % NM_UTILS_NS_PER_SECOND,
+			                        data->seq_result ? ", " : "",
+			                        data->seq_result ? wait_for_nl_response_to_string (data->seq_result, b, sizeof (b)) : "");
+		} else
+			nm_utils_strbuf_append_str (&buf, &buf_size, " (any)");
+		break;
+	default:
+		nm_assert (!user_data);
+		break;
+	}
+	return buf0;
+}
+
+#define _LOGt_delayed_action(action_type, user_data, operation) \
+    G_STMT_START { \
+        char _buf[255]; \
+        \
+        _LOGt ("delayed-action: %s %s", \
+               ""operation, \
+               delayed_action_to_string_full (action_type, user_data, _buf, sizeof (_buf))); \
+    } G_STMT_END
+
+/*****************************************************************************/
+
+static void
+delayed_action_wait_for_nl_response_complete (NMPlatform *platform,
+                                              guint idx,
+                                              WaitForNlResponseResult seq_result)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	DelayedActionWaitForNlResponseData *data;
+	WaitForNlResponseResult *out_seq_result;
+
+	nm_assert (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+	nm_assert (idx < priv->delayed_action.list_wait_for_nl_response->len);
+	nm_assert (seq_result);
+
+	data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, idx);
+
+	_LOGt_delayed_action (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, data, "complete");
+
+	out_seq_result = data->out_seq_result;
+
+	g_array_remove_index_fast (priv->delayed_action.list_wait_for_nl_response, idx);
+	/* Note: @data is invalidated at this point */
+
+	if (priv->delayed_action.list_wait_for_nl_response->len <= 0)
+		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE;
+
+	if (out_seq_result)
+		*out_seq_result = seq_result;
+}
+
+static void
+delayed_action_wait_for_nl_response_complete_all (NMPlatform *platform,
+                                                  WaitForNlResponseResult result)
+{
+	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		while (priv->delayed_action.list_wait_for_nl_response->len > 0)
+			delayed_action_wait_for_nl_response_complete (platform, priv->delayed_action.list_wait_for_nl_response->len - 1, result);
+	}
+	nm_assert (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+	nm_assert (priv->delayed_action.list_wait_for_nl_response->len == 0);
+}
+
+/*****************************************************************************/
 
 static void
 delayed_action_handle_MASTER_CONNECTED (NMPlatform *platform, int master_ifindex)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nmp_obj NMPObject *obj_cache = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	gboolean was_visible;
 	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, NM_PLATFORM_REASON_INTERNAL);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible);
 }
 
 static void
 delayed_action_handle_REFRESH_LINK (NMPlatform *platform, int ifindex)
 {
-	do_request_link (platform, ifindex, NULL, FALSE);
+	do_request_link_no_delayed_actions (platform, ifindex, NULL);
 }
 
 static void
 delayed_action_handle_REFRESH_ALL (NMPlatform *platform, DelayedActionType flags)
 {
-	do_request_all (platform, flags, FALSE);
+	do_request_all_no_delayed_actions (platform, flags);
 }
 
 static void
 delayed_action_handle_READ_NETLINK (NMPlatform *platform)
 {
-	event_handler_read_netlink_all (platform, TRUE);
+	event_handler_read_netlink (platform, FALSE);
+}
+
+static void
+delayed_action_handle_WAIT_FOR_NL_RESPONSE (NMPlatform *platform)
+{
+	event_handler_read_netlink (platform, TRUE);
 }
 
 static gboolean
@@ -1572,10 +2854,8 @@ delayed_action_handle_one (NMPlatform *platform)
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	gpointer user_data;
 
-	if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE) {
-		nm_clear_g_source (&priv->delayed_action.idle_id);
+	if (priv->delayed_action.flags == DELAYED_ACTION_TYPE_NONE)
 		return FALSE;
-	}
 
 	/* First process DELAYED_ACTION_TYPE_MASTER_CONNECTED actions.
 	 * This type of action is entirely cache-internal and is here to resolve a
@@ -1622,20 +2902,30 @@ delayed_action_handle_one (NMPlatform *platform)
 		return TRUE;
 	}
 
-	nm_assert (priv->delayed_action.flags == DELAYED_ACTION_TYPE_REFRESH_LINK);
-	nm_assert (priv->delayed_action.list_refresh_link->len > 0);
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
+		nm_assert (priv->delayed_action.list_refresh_link->len > 0);
 
-	user_data = priv->delayed_action.list_refresh_link->pdata[0];
-	g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
-	if (priv->delayed_action.list_refresh_link->len == 0)
-		priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
-	nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
+		user_data = priv->delayed_action.list_refresh_link->pdata[0];
+		g_ptr_array_remove_index_fast (priv->delayed_action.list_refresh_link, 0);
+		if (priv->delayed_action.list_refresh_link->len == 0)
+			priv->delayed_action.flags &= ~DELAYED_ACTION_TYPE_REFRESH_LINK;
+		nm_assert (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0);
 
-	_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, user_data, "handle");
 
-	delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
+		delayed_action_handle_REFRESH_LINK (platform, GPOINTER_TO_INT (user_data));
 
-	return TRUE;
+		return TRUE;
+	}
+
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		nm_assert (priv->delayed_action.list_wait_for_nl_response->len > 0);
+		_LOGt_delayed_action (DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE, NULL, "handle");
+		delayed_action_handle_WAIT_FOR_NL_RESPONSE (platform);
+		return TRUE;
+	}
+
+	return FALSE;
 }
 
 static gboolean
@@ -1644,22 +2934,18 @@ delayed_action_handle_all (NMPlatform *platform, gboolean read_netlink)
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	gboolean any = FALSE;
 
-	nm_clear_g_source (&priv->delayed_action.idle_id);
+	g_return_val_if_fail (priv->delayed_action.is_handling == 0, FALSE);
+
 	priv->delayed_action.is_handling++;
 	if (read_netlink)
 		delayed_action_schedule (platform, DELAYED_ACTION_TYPE_READ_NETLINK, NULL);
 	while (delayed_action_handle_one (platform))
 		any = TRUE;
 	priv->delayed_action.is_handling--;
-	return any;
-}
 
-static gboolean
-delayed_action_handle_idle (gpointer user_data)
-{
-	NM_LINUX_PLATFORM_GET_PRIVATE (user_data)->delayed_action.idle_id = 0;
-	delayed_action_handle_all (user_data, FALSE);
-	return G_SOURCE_REMOVE;
+	cache_prune_candidates_prune (platform);
+
+	return any;
 }
 
 static void
@@ -1670,16 +2956,25 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 
 	nm_assert (action_type != DELAYED_ACTION_TYPE_NONE);
 
-	if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK)) {
-		nm_assert (nm_utils_is_power_of_two (action_type));
+	switch (action_type) {
+	case DELAYED_ACTION_TYPE_REFRESH_LINK:
 		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_refresh_link->pdata, priv->delayed_action.list_refresh_link->len, user_data) < 0)
 			g_ptr_array_add (priv->delayed_action.list_refresh_link, user_data);
-	} else if (NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED)) {
-		nm_assert (nm_utils_is_power_of_two (action_type));
+		break;
+	case DELAYED_ACTION_TYPE_MASTER_CONNECTED:
 		if (_nm_utils_ptrarray_find_first (priv->delayed_action.list_master_connected->pdata, priv->delayed_action.list_master_connected->len, user_data) < 0)
 			g_ptr_array_add (priv->delayed_action.list_master_connected, user_data);
-	} else
+		break;
+	case DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE:
+		g_array_append_vals (priv->delayed_action.list_wait_for_nl_response, user_data, 1);
+		break;
+	default:
 		nm_assert (!user_data);
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_REFRESH_LINK));
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_MASTER_CONNECTED));
+		nm_assert (!NM_FLAGS_HAS (action_type, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE));
+		break;
+	}
 
 	priv->delayed_action.flags |= action_type;
 
@@ -1689,9 +2984,22 @@ delayed_action_schedule (NMPlatform *platform, DelayedActionType action_type, gp
 				_LOGt_delayed_action (iflags, user_data, "schedule");
 		}
 	}
+}
 
-	if (priv->delayed_action.is_handling == 0 && priv->delayed_action.idle_id == 0)
-		priv->delayed_action.idle_id = g_idle_add (delayed_action_handle_idle, platform);
+static void
+delayed_action_schedule_WAIT_FOR_NL_RESPONSE (NMPlatform *platform,
+                                              guint32 seq_number,
+                                              WaitForNlResponseResult *out_seq_result)
+{
+	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,
+	};
+
+	delayed_action_schedule (platform,
+	                         DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE,
+	                         &data);
 }
 
 /******************************************************************/
@@ -1760,11 +3068,11 @@ cache_prune_candidates_prune (NMPlatform *platform)
 
 	g_hash_table_iter_init (&iter, prune_candidates);
 	while (g_hash_table_iter_next (&iter, (gpointer *)&obj, NULL)) {
-		auto_nmp_obj NMPObject *obj_cache = NULL;
+		nm_auto_nmpobj NMPObject *obj_cache = NULL;
 
 		_LOGt ("cache-prune: prune %s", nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_ALL, NULL, 0));
 		cache_op = nmp_cache_remove (priv->cache, obj, TRUE, &obj_cache, &was_visible, cache_pre_hook, platform);
-		do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
 	}
 
 	g_hash_table_unref (prune_candidates);
@@ -1776,8 +3084,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	NMPlatform *platform = NM_PLATFORM (user_data);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
 	const NMPClass *klass;
-	char str_buf[sizeof (_nm_platform_to_string_buffer)];
-	char str_buf2[sizeof (_nm_platform_to_string_buffer)];
+	char str_buf[sizeof (_nm_utils_to_string_buffer)];
+	char str_buf2[sizeof (_nm_utils_to_string_buffer)];
 
 	nm_assert (old || new);
 	nm_assert (NM_IN_SET (ops_type, NMP_CACHE_OPS_ADDED, NMP_CACHE_OPS_REMOVED, NMP_CACHE_OPS_UPDATED));
@@ -1850,7 +3158,8 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 
 			/* removal of a link could be caused by moving the link to another netns.
 			 * In this case, we potentially have to update other links that have this link as parent.
-			 * Currently, kernel misses to sent us a notification in this case (rh #1262908). */
+			 * 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 */
@@ -1894,6 +3203,29 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 		    && (new && new->_link.netlink.is_in_netlink)
 		    && (!old || !old->_link.netlink.is_in_netlink))
 		{
+			if (!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) {
+				case NM_LINK_TYPE_GRE:
+				case NM_LINK_TYPE_IP6TNL:
+				case NM_LINK_TYPE_INFINIBAND:
+				case NM_LINK_TYPE_MACVLAN:
+				case NM_LINK_TYPE_MACVTAP:
+				case NM_LINK_TYPE_SIT:
+				case NM_LINK_TYPE_VLAN:
+				case NM_LINK_TYPE_VXLAN:
+					delayed_action_schedule (platform,
+					                         DELAYED_ACTION_TYPE_REFRESH_LINK,
+					                         GINT_TO_POINTER (new->link.ifindex));
+					break;
+				default:
+					break;
+				}
+			}
 			if (   new->link.type == NM_LINK_TYPE_VETH
 			    && new->link.parent == 0) {
 				/* the initial notification when adding a veth pair can lack the parent/IFLA_LINK
@@ -1959,100 +3291,74 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP
 	}
 }
 
-static NMPCacheOpsType
-cache_remove_netlink (NMPlatform *platform, const NMPObject *obj_needle, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPObject *obj_cache;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
-
-	cache_op = nmp_cache_remove_netlink (priv->cache, obj_needle, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
-
-	if (out_obj_cache)
-		*out_obj_cache = obj_cache;
-	else
-		nmp_object_unref (obj_cache);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
-
-	return cache_op;
-}
+/******************************************************************/
 
-static NMPCacheOpsType
-cache_update_netlink (NMPlatform *platform, NMPObject *obj, NMPObject **out_obj_cache, gboolean *out_was_visible, NMPlatformReason reason)
+static int
+_nl_send_auto_with_seq (NMPlatform *platform,
+                        struct nl_msg *nlmsg,
+                        WaitForNlResponseResult *out_seq_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	NMPObject *obj_cache;
-	gboolean was_visible;
-	NMPCacheOpsType cache_op;
+	guint32 seq;
+	int nle;
 
-	/* This is basically a convenience method to call nmp_cache_update() and do_emit_signal()
-	 * at once. */
+	/* complete the message with a sequence number (ensuring it's not zero). */
+	seq = priv->nlh_seq_next++ ?: priv->nlh_seq_next++;
 
-	cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, reason);
+	nlmsg_hdr (nlmsg)->nlmsg_seq = seq;
 
-	if (out_obj_cache)
-		*out_obj_cache = obj_cache;
-	else
-		nmp_object_unref (obj_cache);
-	if (out_was_visible)
-		*out_was_visible = was_visible;
+	nle = nl_send_auto (priv->nlh, nlmsg);
 
-	return cache_op;
-}
+	if (nle >= 0) {
+		nle = 0;
+		delayed_action_schedule_WAIT_FOR_NL_RESPONSE (platform, seq, out_seq_result);
+	} else
+		_LOGD ("netlink: send: failed sending message: %s (%d)", nl_geterror (nle), nle);
 
-/******************************************************************/
+	return nle;
+}
 
 static void
-_new_sequence_number (NMPlatform *platform, guint32 seq)
+do_request_link_no_delayed_actions (NMPlatform *platform, int ifindex, const char *name)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	_LOGt ("_new_sequence_number(): new sequence number %u", seq);
-
-	priv->nlh_seq_expect = seq;
-}
+	if (name && !name[0])
+		name = NULL;
 
-static void
-do_request_link (NMPlatform *platform, int ifindex, const char *name, gboolean handle_delayed_action)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	guint32 seq;
+	g_return_if_fail (ifindex > 0 || name);
 
-	_LOGt ("do_request_link (%d,%s)", ifindex, name ? name : "");
+	_LOGD ("do-request-link: %d %s", ifindex, name ? name : "");
 
 	if (ifindex > 0) {
 		cache_prune_candidates_record_one (platform,
 		                                   (NMPObject *) nmp_cache_lookup_link (priv->cache, ifindex));
 	}
 
-	event_handler_read_netlink_all (platform, FALSE);
-
-	if (_nl_sock_request_link (platform, priv->nlh_event, ifindex, name, &seq) == 0)
-		_new_sequence_number (platform, seq);
+	event_handler_read_netlink (platform, FALSE);
 
-	event_handler_read_netlink_all (platform, TRUE);
-
-	cache_prune_candidates_prune (platform);
-
-	if (handle_delayed_action)
-		delayed_action_handle_all (platform, FALSE);
+	nlmsg = _nl_msg_new_link (RTM_GETLINK,
+	                          0,
+	                          ifindex,
+	                          name,
+	                          0,
+	                          0);
+	if (nlmsg)
+		_nl_send_auto_with_seq (platform, nlmsg, NULL);
 }
 
 static void
-do_request_one_type (NMPlatform *platform, NMPObjectType obj_type, gboolean handle_delayed_action)
+do_request_link (NMPlatform *platform, int ifindex, const char *name)
 {
-	do_request_all (platform, delayed_action_refresh_from_object_type (obj_type), handle_delayed_action);
+	do_request_link_no_delayed_actions (platform, ifindex, name);
+	delayed_action_handle_all (platform, FALSE);
 }
 
 static void
-do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean handle_delayed_action)
+do_request_all_no_delayed_actions (NMPlatform *platform, DelayedActionType action_type)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	guint32 seq;
 	DelayedActionType iflags;
 
 	nm_assert (!NM_FLAGS_ANY (action_type, ~DELAYED_ACTION_TYPE_REFRESH_ALL));
@@ -2066,6 +3372,12 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha
 	for (iflags = (DelayedActionType) 0x1LL; iflags <= DELAYED_ACTION_TYPE_MAX; iflags <<= 1) {
 		if (NM_FLAGS_HAS (action_type, iflags)) {
 			NMPObjectType obj_type = delayed_action_refresh_to_object_type (iflags);
+			const NMPClass *klass = nmp_class_from_type (obj_type);
+			nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+			struct rtgenmsg gmsg = {
+				.rtgen_family = klass->addr_family,
+			};
+			int nle;
 
 			/* clear any delayed action that request a refresh of this object type. */
 			priv->delayed_action.flags &= ~iflags;
@@ -2076,496 +3388,129 @@ do_request_all (NMPlatform *platform, DelayedActionType action_type, gboolean ha
 				_LOGt_delayed_action (DELAYED_ACTION_TYPE_REFRESH_LINK, NULL, "clear (do-request-all)");
 			}
 
-			event_handler_read_netlink_all (platform, FALSE);
+			event_handler_read_netlink (platform, FALSE);
 
-			if (_nl_sock_request_all (platform, priv->nlh_event, obj_type, &seq) == 0)
-				_new_sequence_number (platform, seq);
-		}
-	}
-	event_handler_read_netlink_all (platform, TRUE);
-
-	cache_prune_candidates_prune (platform);
-
-	if (handle_delayed_action)
-		delayed_action_handle_all (platform, FALSE);
-}
-
-static gboolean
-kernel_add_object (NMPlatform *platform, NMPObjectType obj_type, const struct nl_object *nlo)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
-
-	g_return_val_if_fail (nlo, FALSE);
+			/* reimplement
+			 *   nl_rtgen_request (sk, klass->rtm_gettype, klass->addr_family, NLM_F_DUMP);
+			 * because we need the sequence number.
+			 */
+			nlmsg = nlmsg_alloc_simple (klass->rtm_gettype, NLM_F_DUMP);
+			if (!nlmsg)
+				goto next;
 
-	switch (obj_type) {
-	case NMP_OBJECT_TYPE_LINK:
-		nle = rtnl_link_add (priv->nlh, (struct rtnl_link *) nlo, NLM_F_CREATE);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		nle = rtnl_addr_add (priv->nlh, (struct rtnl_addr *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		nle = rtnl_route_add (priv->nlh, (struct rtnl_route *) nlo, NLM_F_CREATE | NLM_F_REPLACE);
-		break;
-	default:
-		g_return_val_if_reached (-NLE_INVAL);
-	}
+			nle = nlmsg_append (nlmsg, &gmsg, sizeof (gmsg), NLMSG_ALIGNTO);
+			if (nle < 0)
+				goto next;
 
-	_LOGt ("kernel-add-%s: returned %s (%d)",
-	       nmp_class_from_type (obj_type)->obj_type_name, nl_geterror (nle), -nle);
-
-	switch (nle) {
-	case -NLE_SUCCESS:
-		return -NLE_SUCCESS;
-	case -NLE_EXIST:
-		/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-		 * never know when something sends an identical object just before
-		 * NetworkManager. */
-		if (obj_type != NMP_OBJECT_TYPE_LINK)
-			return -NLE_SUCCESS;
-		/* fall-through */
-	default:
-		return nle;
+			_nl_send_auto_with_seq (platform, nlmsg, NULL);
+		}
+next:
+		;
 	}
 }
 
-static int
-kernel_delete_object (NMPlatform *platform, NMPObjectType object_type, const struct nl_object *object)
+static void
+do_request_one_type (NMPlatform *platform, NMPObjectType obj_type)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
-
-	switch (object_type) {
-	case NMP_OBJECT_TYPE_LINK:
-		nle = rtnl_link_delete (priv->nlh, (struct rtnl_link *) object);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ADDRESS:
-	case NMP_OBJECT_TYPE_IP6_ADDRESS:
-		nle = rtnl_addr_delete (priv->nlh, (struct rtnl_addr *) object, 0);
-		break;
-	case NMP_OBJECT_TYPE_IP4_ROUTE:
-	case NMP_OBJECT_TYPE_IP6_ROUTE:
-		nle = rtnl_route_delete (priv->nlh, (struct rtnl_route *) object, 0);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-
-	switch (nle) {
-	case -NLE_SUCCESS:
-		return NLE_SUCCESS;
-	case -NLE_OBJ_NOTFOUND:
-		_LOGt ("kernel-delete-%s: failed with \"%s\" (%d), meaning the object was already removed",
-		       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-		return -NLE_SUCCESS;
-	case -NLE_FAILURE:
-		if (object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-			/* On RHEL7 kernel, deleting a non existing address fails with ENXIO (which libnl maps to NLE_FAILURE) */
-			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
-			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-			return NLE_SUCCESS;
-		}
-		break;
-	case -NLE_NOADDR:
-		if (object_type == NMP_OBJECT_TYPE_IP4_ADDRESS || object_type == NMP_OBJECT_TYPE_IP6_ADDRESS) {
-			_LOGt ("kernel-delete-%s: deleting address failed with \"%s\" (%d), meaning the address was already removed",
-			       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-			return -NLE_SUCCESS;
-		}
-		break;
-	default:
-		break;
-	}
-	_LOGt ("kernel-delete-%s: failed with %s (%d)",
-	       nmp_class_from_type (object_type)->obj_type_name, nl_geterror (nle), -nle);
-	return nle;
+	do_request_all_no_delayed_actions (platform, delayed_action_refresh_from_object_type (obj_type));
+	delayed_action_handle_all (platform, FALSE);
 }
 
-static int
-kernel_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean *complete_from_cache)
+static void
+event_seq_check (NMPlatform *platform, struct nl_msg *msg, WaitForNlResponseResult seq_result)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nl_msg *msg;
-	int nle;
-	const int nlflags = 0;
-	int ifindex;
-
-	ifindex = rtnl_link_get_ifindex (nlo);
+	DelayedActionWaitForNlResponseData *data;
+	guint32 seq_number;
+	guint i;
 
-	g_return_val_if_fail (ifindex > 0, FALSE);
+	seq_number = nlmsg_hdr (msg)->nlmsg_seq;
 
-	/* Previously, we were using rtnl_link_change(), which builds a request based
-	 * on the diff with an original link instance.
-	 *
-	 * The diff only reused ifi_family, ifi_index, ifi_flags, and name from
-	 * the original link (see rtnl_link_build_change_request()).
-	 *
-	 * We don't do that anymore as we don't have an "orig" netlink instance that
-	 * we can use. Instead the caller must ensure to properly initialize @nlo,
-	 * especially it must set family, ifindex (or ifname) and flags.
-	 * ifname should be set *only* if the caller wishes to change the name.
-	 *
-	 * @complete_from_cache is a convenience to copy the link flags over the link inside
-	 * the platform cache. */
-
-	if (*complete_from_cache) {
-		const NMPObject *obj_cache;
+	if (seq_number == 0)
+		return;
 
-		obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
-		if (!obj_cache || !obj_cache->_link.netlink.is_in_netlink) {
-			_LOGt ("kernel-change-link: failure changing link %d: cannot complete link", ifindex);
-			*complete_from_cache = FALSE;
-			return -NLE_INVAL;
+	if (NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE)) {
+		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++) {
+			data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+			if (data->seq_number == seq_number) {
+				/* We potentially receive many parts partial responses for the same sequence number.
+				 * Thus, we only remember the result, and collect it later. */
+				if (data->seq_result < 0) {
+					/* we already saw an error for this seqence number.
+					 * Preserve it. */
+				} else if (   seq_result != WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN
+				           || data->seq_result == WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN)
+					data->seq_result = seq_result;
+				return;
+			}
 		}
-
-		rtnl_link_set_flags (nlo, obj_cache->link.flags);
-
-		/* If the caller wants to rename the link, he should explicitly set
-		 * rtnl_link_set_name(). In all other cases, it should leave the name
-		 * unset. Unfortunately, there is not public API in libnl to modify the
-		 * attribute mask and clear (link->ce_mask = ~LINK_ATTR_IFNAME), so we
-		 * require the caller to do the right thing -- i.e. don't set the name.
-		 */
-	}
-
-	/* We don't use rtnl_link_change() because we have no original rtnl_link object
-	 * at hand. We also don't use rtnl_link_add() because that doesn't have the
-	 * hack to retry with RTM_SETLINK. Reimplement a mix of both. */
-
-	nle = rtnl_link_build_add_request (nlo, nlflags, &msg);
-	if (nle < 0) {
-		_LOGt ("kernel-change-link: failure changing link %d: cannot construct message (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		return nle;
 	}
 
-retry:
-	nle = nl_send_auto_complete (priv->nlh, msg);
-	if (nle < 0)
-		goto errout;
-
-	nle = nl_wait_for_ack(priv->nlh);
-	if (nle == -NLE_OPNOTSUPP && nlmsg_hdr (msg)->nlmsg_type == RTM_NEWLINK) {
-		nlmsg_hdr (msg)->nlmsg_type = RTM_SETLINK;
-		goto retry;
-	}
-
-errout:
-	nlmsg_free(msg);
-
-	/* NLE_EXIST is considered equivalent to success to avoid race conditions. You
-	 * never know when something sends an identical object just before
-	 * NetworkManager.
-	 *
-	 * When netlink returns NLE_OBJ_NOTFOUND, it usually means it failed to find
-	 * firmware for the device, especially on nm_platform_link_set_up ().
-	 * This is basically the same check as in the original code and could
-	 * potentially be improved.
-	 */
-	switch (nle) {
-	case -NLE_SUCCESS:
-		_LOGt ("kernel-change-link: success changing link %d", ifindex);
-		break;
-	case -NLE_EXIST:
-		_LOGt ("kernel-change-link: success changing link %d: %s (%d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	case -NLE_OBJ_NOTFOUND:
-		_LOGt ("kernel-change-link: failure changing link %d: firmware not found (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	default:
-		_LOGt ("kernel-change-link: failure changing link %d: netlink error (%s, %d)",
-		       ifindex, nl_geterror (nle), -nle);
-		break;
-	}
-
-	return nle;
+	if (seq_number != priv->nlh_seq_last_handled)
+		_LOGt ("netlink: recvmsg: unwaited sequence number %u", seq_number);
+	priv->nlh_seq_last_handled = seq_number;
 }
 
 static void
-ref_object (struct nl_object *obj, void *data)
-{
-	struct nl_object **out = data;
-
-	nl_object_get (obj);
-	*out = obj;
-}
-
-static int
-event_seq_check (struct nl_msg *msg, gpointer user_data)
+event_valid_msg (NMPlatform *platform, struct nl_msg *msg)
 {
-	NMPlatform *platform = NM_PLATFORM (user_data);
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	struct nlmsghdr *hdr;
-
-	hdr = nlmsg_hdr (msg);
-
-	if (hdr->nlmsg_seq == 0)
-		return NL_OK;
-
-	priv->nlh_seq_last = hdr->nlmsg_seq;
-
-	if (priv->nlh_seq_expect == 0)
-		_LOGt ("event_seq_check(): seq %u received (not waited)", hdr->nlmsg_seq);
-	else if (hdr->nlmsg_seq == priv->nlh_seq_expect) {
-		_LOGt ("event_seq_check(): seq %u received", hdr->nlmsg_seq);
-
-		priv->nlh_seq_expect = 0;
-	} else
-		_LOGt ("event_seq_check(): seq %u received (wait for %u)", hdr->nlmsg_seq, priv->nlh_seq_last);
-
-	return NL_OK;
-}
-
-static int
-event_err (struct sockaddr_nl *nla, struct nlmsgerr *nlerr, gpointer platform)
-{
-	_LOGt ("event_err(): error from kernel: %s (%d) for request %d",
-	       strerror (nlerr ? -nlerr->error : 0),
-	       nlerr ? -nlerr->error : 0,
-	       NM_LINUX_PLATFORM_GET_PRIVATE (platform)->nlh_seq_last);
-	return NL_OK;
-}
-
-/* This function does all the magic to avoid race conditions caused
- * by concurrent usage of synchronous commands and an asynchronous cache. This
- * might be a nice future addition to libnl but it requires to do all operations
- * through the cache manager. In this case, nm-linux-platform serves as the
- * cache manager instead of the one provided by libnl.
- */
-static int
-event_notification (struct nl_msg *msg, gpointer user_data)
-{
-	NMPlatform *platform = NM_PLATFORM (user_data);
-	auto_nl_object struct nl_object *nlo = NULL;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
+	NMPCacheOpsType cache_op;
 	struct nlmsghdr *msghdr;
 	char buf_nlmsg_type[16];
+	gboolean id_only = FALSE;
+	gboolean was_visible;
 
 	msghdr = nlmsg_hdr (msg);
 
 	if (_support_kernel_extended_ifa_flags_still_undecided () && msghdr->nlmsg_type == RTM_NEWADDR)
 		_support_kernel_extended_ifa_flags_detect (msg);
 
-	nl_msg_parse (msg, ref_object, &nlo);
-	if (!nlo)
-		return NL_OK;
-
-	if (_support_user_ipv6ll_still_undecided() && msghdr->nlmsg_type == RTM_NEWLINK)
-		_support_user_ipv6ll_detect ((struct rtnl_link *) nlo);
-
-	switch (msghdr->nlmsg_type) {
-	case RTM_DELADDR:
-	case RTM_DELLINK:
-	case RTM_DELROUTE:
-		/* The event notifies about a deleted object. We don't need to initialize all the
-		 * fields of the nmp-object. Shortcut nmp_object_from_nl(). */
-		obj = nmp_object_from_nl (platform, nlo, TRUE, TRUE);
-		_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, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-		break;
-	default:
-		obj = nmp_object_from_nl (platform, nlo, FALSE, TRUE);
-		_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, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
-		break;
-	}
-
-	if (obj) {
-		auto_nmp_obj NMPObject *obj_cache = NULL;
-
-		switch (msghdr->nlmsg_type) {
-
-		case RTM_NEWLINK:
-		case RTM_NEWADDR:
-		case RTM_NEWROUTE:
-			cache_update_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
-			break;
-
-		case RTM_DELLINK:
-		case RTM_DELADDR:
-		case RTM_DELROUTE:
-			cache_remove_netlink (platform, obj, &obj_cache, NULL, NM_PLATFORM_REASON_EXTERNAL);
-			break;
-
-		default:
-			break;
-		}
-
-		cache_prune_candidates_drop (platform, obj_cache);
+	if (NM_IN_SET (msghdr->nlmsg_type, RTM_DELLINK, RTM_DELADDR, RTM_DELROUTE)) {
+		/* The event notifies about a deleted object. We don't need to initialize all
+		 * fields of the object. */
+		id_only = TRUE;
 	}
 
-	return NL_OK;
-}
-
-/******************************************************************/
-
-static void
-_log_dbg_sysctl_set_impl (const char *path, const char *value)
-{
-	GError *error = NULL;
-	char *contents, *contents_escaped;
-	char *value_escaped = g_strescape (value, NULL);
-
-	if (!g_file_get_contents (path, &contents, NULL, &error)) {
-		debug ("sysctl: setting '%s' to '%s' (current value cannot be read: %s)", path, value_escaped, error->message);
-		g_clear_error (&error);
-	} else {
-		g_strstrip (contents);
-		contents_escaped = g_strescape (contents, NULL);
-		if (strcmp (contents, value) == 0)
-			debug ("sysctl: setting '%s' to '%s' (current value is identical)", path, value_escaped);
-		else
-			debug ("sysctl: setting '%s' to '%s' (current value is '%s')", path, value_escaped, contents_escaped);
-		g_free (contents);
-		g_free (contents_escaped);
-	}
-	g_free (value_escaped);
-}
-
-#define _log_dbg_sysctl_set(path, value) \
-	G_STMT_START { \
-		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \
-			_log_dbg_sysctl_set_impl (path, value); \
-		} \
-	} G_STMT_END
-
-static gboolean
-sysctl_set (NMPlatform *platform, const char *path, const char *value)
-{
-	int fd, len, nwrote, tries;
-	char *actual;
-
-	g_return_val_if_fail (path != NULL, FALSE);
-	g_return_val_if_fail (value != NULL, FALSE);
-
-	/* Don't write outside known locations */
-	g_assert (g_str_has_prefix (path, "/proc/sys/")
-	          || g_str_has_prefix (path, "/sys/"));
-	/* Don't write to suspicious locations */
-	g_assert (!strstr (path, "/../"));
-
-	fd = open (path, O_WRONLY | O_TRUNC);
-	if (fd == -1) {
-		if (errno == ENOENT) {
-			debug ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
-		} else {
-			error ("sysctl: failed to open '%s': (%d) %s",
-			       path, errno, strerror (errno));
-		}
-		return FALSE;
+	obj = nmp_object_new_from_nl (platform, priv->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);
+		return;
 	}
 
-	_log_dbg_sysctl_set (path, value);
-
-	/* Most sysfs and sysctl options don't care about a trailing LF, while some
-	 * (like infiniband) do.  So always add the LF.  Also, neither sysfs nor
-	 * sysctl support partial writes so the LF must be added to the string we're
-	 * about to write.
-	 */
-	actual = g_strdup_printf ("%s\n", value);
-
-	/* Try to write the entire value three times if a partial write occurs */
-	len = strlen (actual);
-	for (tries = 0, nwrote = 0; tries < 3 && nwrote != len; tries++) {
-		nwrote = write (fd, actual, len);
-		if (nwrote == -1) {
-			if (errno == EINTR) {
-				debug ("sysctl: interrupted, will try again");
-				continue;
-			}
-			break;
-		}
-	}
-	if (nwrote == -1 && errno != EEXIST) {
-		error ("sysctl: failed to set '%s' to '%s': (%d) %s",
-		       path, value, errno, strerror (errno));
-	} else if (nwrote < len) {
-		error ("sysctl: failed to set '%s' to '%s' after three attempts",
-		       path, value);
-	}
+	_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));
 
-	g_free (actual);
-	close (fd);
-	return (nwrote == len);
-}
-
-static GHashTable *sysctl_get_prev_values;
-
-static void
-_log_dbg_sysctl_get_impl (const char *path, const char *contents)
-{
-	const char *prev_value = NULL;
-
-	if (!sysctl_get_prev_values)
-		sysctl_get_prev_values = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-	else
-		prev_value = g_hash_table_lookup (sysctl_get_prev_values, path);
-
-	if (prev_value) {
-		if (strcmp (prev_value, contents) != 0) {
-			char *contents_escaped = g_strescape (contents, NULL);
-			char *prev_value_escaped = g_strescape (prev_value, NULL);
-
-			debug ("sysctl: reading '%s': '%s' (changed from '%s' on last read)", path, contents_escaped, prev_value_escaped);
-			g_free (contents_escaped);
-			g_free (prev_value_escaped);
-			g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
-		}
-	} else {
-		char *contents_escaped = g_strescape (contents, NULL);
-
-		debug ("sysctl: reading '%s': '%s'", path, contents_escaped);
-		g_free (contents_escaped);
-		g_hash_table_insert (sysctl_get_prev_values, g_strdup (path), g_strdup (contents));
-	}
-}
-
-#define _log_dbg_sysctl_get(path, contents) \
-	G_STMT_START { \
-		if (nm_logging_enabled (LOGL_DEBUG, LOGD_PLATFORM)) { \
-			_log_dbg_sysctl_get_impl (path, contents); \
-		} else if (sysctl_get_prev_values) { \
-			g_hash_table_destroy (sysctl_get_prev_values); \
-			sysctl_get_prev_values = NULL; \
-		} \
-	} G_STMT_END
+	switch (msghdr->nlmsg_type) {
 
-static char *
-sysctl_get (NMPlatform *platform, const char *path)
-{
-	GError *error = NULL;
-	char *contents;
+	case RTM_NEWLINK:
+	case RTM_NEWADDR:
+	case RTM_NEWROUTE:
+		cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform);
+		do_emit_signal (platform, obj_cache, cache_op, was_visible);
+		break;
 
-	/* Don't write outside known locations */
-	g_assert (g_str_has_prefix (path, "/proc/sys/")
-	          || g_str_has_prefix (path, "/sys/"));
-	/* Don't write to suspicious locations */
-	g_assert (!strstr (path, "/../"));
+	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);
+		break;
 
-	if (!g_file_get_contents (path, &contents, NULL, &error)) {
-		/* We assume FAILED means EOPNOTSUP */
-		if (   g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT)
-		    || g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_FAILED))
-			debug ("error reading %s: %s", path, error->message);
-		else
-			error ("error reading %s: %s", path, error->message);
-		g_clear_error (&error);
-		return NULL;
+	default:
+		break;
 	}
 
-	g_strstrip (contents);
-
-	_log_dbg_sysctl_get (path, contents);
-
-	return contents;
+	cache_prune_candidates_drop (platform, obj_cache);
 }
 
 /******************************************************************/
@@ -2647,195 +3592,261 @@ _nm_platform_link_get_by_address (NMPlatform *platform,
 	return obj ? &obj->link : NULL;
 }
 
-static struct nl_object *
-build_rtnl_link (int ifindex, const char *name, NMLinkType type)
+/*****************************************************************************/
+
+static const NMPObject *
+link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link)
 {
-	struct rtnl_link *rtnllink;
-	int nle;
+	const NMPObject *obj = cache_lookup_link (platform, ifindex);
 
-	rtnllink = _nl_rtnl_link_alloc (ifindex, name);
-	if (type) {
-		nle = rtnl_link_set_type (rtnllink, nm_link_type_to_rtnl_type_string (type));
-		g_assert (!nle);
-	}
-	return (struct nl_object *) rtnllink;
-}
+	if (!obj)
+		return NULL;
 
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
+	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 build_rtnl_link (obj->ifindex,
-	                        obj->name[0] ? obj->name : NULL,
-	                        obj->type);
+	return obj->_link.netlink.lnk;
 }
 
+/*****************************************************************************/
+
 static gboolean
-do_add_link (NMPlatform *platform, const char *name, const struct rtnl_link *nlo)
+do_add_link_with_lookup (NMPlatform *platform,
+                         NMLinkType link_type,
+                         const char *name,
+                         struct nl_msg *nlmsg,
+                         const NMPlatformLink **out_link)
 {
-	NMPObject obj_needle;
+	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];
+
+	event_handler_read_netlink (platform, FALSE);
 
-	event_handler_read_netlink_all (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);
 
-	nle = kernel_add_object (platform, NMP_OBJECT_TYPE_LINK, (const struct nl_object *) nlo);
+		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);
 	if (nle < 0) {
-		_LOGE ("do-add-link: failure adding link '%s': %s", name, nl_geterror (nle));
+		_LOGE ("do-add-link[%s/%s]: failed sending netlink request \"%s\" (%d)",
+		       name,
+		       nm_link_type_to_string (link_type),
+		       nl_geterror (nle), -nle);
 		return FALSE;
 	}
-	_LOGD ("do-add-link: success adding link '%s'", name);
 
-	nmp_object_stackinit_id_link (&obj_needle, 0);
-	g_strlcpy (obj_needle.link.name, name, sizeof (obj_needle.link.name));
+	delayed_action_handle_all (platform, FALSE);
 
-	delayed_action_handle_all (platform, TRUE);
+	nm_assert (seq_result);
 
-	/* FIXME: we add the link object via the second netlink socket. Sometimes,
-	 * the notification is not yet ready via nlh_event, so we have to re-request the
-	 * link so that it is in the cache. A better solution would be to do everything
-	 * via one netlink socket. */
-	if (!nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, 0, obj_needle.link.name, FALSE, NM_LINK_TYPE_NONE, NULL, NULL)) {
-		_LOGt ("do-add-link: reload: the added link is not yet ready. Request %s", obj_needle.link.name);
-		do_request_link (platform, 0, obj_needle.link.name, TRUE);
-	}
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_ERR,
+	        "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)));
 
-	/* Return true, because kernel_add_object() succeeded. This doesn't indicate that the
-	 * object is now actuall in the cache, because there could be a race.
-	 *
-	 * For that, you'd have to look at @out_obj. */
-	return TRUE;
-}
-
-static gboolean
-do_add_link_with_lookup (NMPlatform *platform, const char *name, const struct rtnl_link *nlo, NMLinkType expected_link_type, NMPlatformLink *out_link)
-{
-	const NMPObject *obj;
+	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);
 
-	do_add_link (platform, name, nlo);
+	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);
+	}
 
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, name, FALSE, expected_link_type, NULL, NULL);
-	if (out_link && obj)
-		*out_link = obj->link;
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
 	return !!obj;
 }
 
 static gboolean
-do_add_addrroute (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+do_add_addrroute (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];
+	const NMPObject *obj;
 
 	nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id),
 	                      NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS,
 	                      NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE));
 
-	event_handler_read_netlink_all (platform, FALSE);
+	event_handler_read_netlink (platform, FALSE);
 
-	nle = kernel_add_object (platform, NMP_OBJECT_GET_CLASS (obj_id)->obj_type, (const struct nl_object *) nlo);
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
 	if (nle < 0) {
-		_LOGW ("do-add-%s: failure adding %s '%s': %s (%d)",
-		       NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name,
+		_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;
 	}
-	_LOGD ("do-add-%s: success adding object %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
 
-	delayed_action_handle_all (platform, TRUE);
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	_NMLOG (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK
+	            ? LOGL_DEBUG
+	            : LOGL_ERR,
+	        "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)));
 
-	/* FIXME: instead of re-requesting the added object, add it via nlh_event
-	 * so that the events are in sync. */
-	if (!nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, obj_id)) {
-		_LOGt ("do-add-%s: reload: the added object is not yet ready. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-		do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
+	/* 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);
 	}
 
-	/* The return value doesn't say, whether the object is in the platform cache after adding
-	 * it.
-	 * Instead the return value says, whether kernel_add_object() succeeded. */
-	return TRUE;
+	/* 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;
 }
 
-
 static gboolean
-do_delete_object (NMPlatform *platform, const NMPObject *obj_id, const struct nl_object *nlo)
+do_delete_object (NMPlatform *platform, const NMPObject *obj_id, struct nl_msg *nlmsg)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nl_object struct nl_object *nlo_free = NULL;
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
+	char s_buf[256];
+	gboolean success = TRUE;
+	const char *log_detail = "";
 
-	event_handler_read_netlink_all (platform, FALSE);
+	event_handler_read_netlink (platform, FALSE);
 
-	if (!nlo)
-		nlo = nlo_free = nmp_object_to_nl (platform, obj_id, FALSE);
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
+	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;
+	}
 
-	nle = kernel_delete_object (platform, NMP_OBJECT_GET_TYPE (obj_id), nlo);
-	if (nle < 0)
-		_LOGE ("do-delete-%s: failure deleting '%s': %s (%d)", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0), nl_geterror (nle), -nle);
-	else
-		_LOGD ("do-delete-%s: success deleting '%s'", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
+	delayed_action_handle_all (platform, FALSE);
 
-	delayed_action_handle_all (platform, TRUE);
+	nm_assert (seq_result);
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		/* ok */
+	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT))
+		log_detail = ", meaning the object was already removed";
+	else if (   NM_IN_SET (-((int) seq_result), ENXIO)
+	         && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id), NMP_OBJECT_TYPE_IP6_ADDRESS)) {
+		/* On RHEL7 kernel, deleting a non existing address fails with ENXIO */
+		log_detail = ", meaning the address was already removed";
+	} else if (   NM_IN_SET (-((int) seq_result), EADDRNOTAVAIL)
+	           && NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_id), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS))
+		log_detail = ", meaning the address was already removed";
+	else
+		success = FALSE;
 
-	/* FIXME: instead of re-requesting the deleted object, add it via nlh_event
-	 * so that the events are in sync. */
-	if (NMP_OBJECT_GET_TYPE (obj_id) == NMP_OBJECT_TYPE_LINK) {
-		const NMPObject *obj;
+	_NMLOG (success ? LOGL_DEBUG : LOGL_ERR,
+	        "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);
 
-		obj = nmp_cache_lookup_link_full (priv->cache, obj_id->link.ifindex, obj_id->link.ifindex <= 0 && obj_id->link.name[0] ? obj_id->link.name : NULL, FALSE, NM_LINK_TYPE_NONE, NULL, NULL);
-		if (obj && obj->_link.netlink.is_in_netlink) {
-			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			do_request_link (platform, obj_id->link.ifindex, obj_id->link.name, TRUE);
-		}
-	} else {
-		if (nmp_cache_lookup_obj (priv->cache, obj_id)) {
-			_LOGt ("do-delete-%s: reload: the deleted object is not yet removed. Request %s", NMP_OBJECT_GET_CLASS (obj_id)->obj_type_name, nmp_object_to_string (obj_id, NMP_OBJECT_TO_STRING_ID, NULL, 0));
-			do_request_one_type (platform, NMP_OBJECT_GET_TYPE (obj_id), TRUE);
-		}
-	}
+out:
+	if (!nmp_cache_lookup_obj (priv->cache, obj_id))
+		return TRUE;
 
-	/* The return value doesn't say, whether the object is in the platform cache after adding
-	 * it.
-	 * Instead the return value says, whether kernel_add_object() succeeded. */
-	return nle >= 0;
+	/* 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);
 }
 
 static NMPlatformError
-do_change_link (NMPlatform *platform, struct rtnl_link *nlo, gboolean complete_from_cache)
+do_change_link (NMPlatform *platform,
+                int ifindex,
+                struct nl_msg *nlmsg)
 {
+	WaitForNlResponseResult seq_result = WAIT_FOR_NL_RESPONSE_RESULT_UNKNOWN;
 	int nle;
-	int ifindex;
-	gboolean complete_from_cache2 = complete_from_cache;
+	char s_buf[256];
+	NMPlatformError result = NM_PLATFORM_ERROR_SUCCESS;
+	NMLogLevel log_level = LOGL_DEBUG;
+	const char *log_result = "failure", *log_detail = "";
 
-	ifindex = rtnl_link_get_ifindex (nlo);
-	if (ifindex <= 0)
-		g_return_val_if_reached (NM_PLATFORM_ERROR_BUG);
+retry:
+	nle = _nl_send_auto_with_seq (platform, nlmsg, &seq_result);
+	if (nle < 0) {
+		_LOGE ("do-change-link[%d]: failure sending netlink request \"%s\" (%d)",
+		       ifindex,
+		       nl_geterror (nle), -nle);
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	}
 
-	nle = kernel_change_link (platform, nlo, &complete_from_cache2);
+	/* always refetch the link after changing it. There seems to be issues
+	 * and we sometimes lack events. Nuke it from the orbit... */
+	delayed_action_schedule (platform, DELAYED_ACTION_TYPE_REFRESH_LINK, GINT_TO_POINTER (ifindex));
 
-	switch (nle) {
-	case -NLE_SUCCESS:
-		_LOGD ("do-change-link: success changing link %d", ifindex);
-		break;
-	case -NLE_EXIST:
-		_LOGD ("do-change-link: success changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
-		break;
-	case -NLE_OBJ_NOTFOUND:
-		/* fall-through */
-	default:
-		if (complete_from_cache != complete_from_cache2)
-			_LOGD ("do-change-link: failure changing link %d: link does not exist in cache", ifindex);
-		else
-			_LOGE ("do-change-link: failure changing link %d: %s (%d)", ifindex, nl_geterror (nle), -nle);
-		return nle == -NLE_OBJ_NOTFOUND ? NM_PLATFORM_ERROR_NO_FIRMWARE : NM_PLATFORM_ERROR_UNSPECIFIED;
+	delayed_action_handle_all (platform, FALSE);
+
+	nm_assert (seq_result);
+
+	if (   NM_IN_SET (-((int) seq_result), EOPNOTSUPP)
+	    && nlmsg_hdr (nlmsg)->nlmsg_type == RTM_NEWLINK) {
+		nlmsg_hdr (nlmsg)->nlmsg_type = RTM_SETLINK;
+		goto retry;
+	}
+
+	if (seq_result == WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK) {
+		log_result = "success";
+	} else if (NM_IN_SET (-((int) seq_result), EEXIST, EADDRINUSE)) {
+		/* */
+	} else if (NM_IN_SET (-((int) seq_result), ESRCH, ENOENT)) {
+		log_detail = ", firmware not found";
+		result = NM_PLATFORM_ERROR_NO_FIRMWARE;
+	} else {
+		log_level = LOGL_ERR;
+		result = NM_PLATFORM_ERROR_UNSPECIFIED;
 	}
+	_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);
 
-	/* FIXME: as we modify the link via a separate socket, the cache is not in
-	 * sync and we have to refetch the link. */
-	do_request_link (platform, ifindex, NULL, TRUE);
-	return NM_PLATFORM_ERROR_SUCCESS;
+	return result;
 }
 
 static gboolean
@@ -2844,9 +3855,9 @@ link_add (NMPlatform *platform,
           NMLinkType type,
           const void *address,
           size_t address_len,
-          NMPlatformLink *out_link)
+          const NMPlatformLink **out_link)
 {
-	auto_nl_object struct nl_object *l = NULL;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
 	if (type == NM_LINK_TYPE_BOND) {
 		/* When the kernel loads the bond module, either via explicit modprobe
@@ -2860,34 +3871,50 @@ link_add (NMPlatform *platform,
 			nm_utils_modprobe (NULL, TRUE, "bonding", "max_bonds=0", NULL);
 	}
 
-	debug ("link: add link '%s' of type '%s' (%d)",
+	_LOGD ("link: add link '%s' of type '%s' (%d)",
 	       name, nm_link_type_to_string (type), (int) type);
 
-	l = build_rtnl_link (0, name, type);
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	g_assert ( (address != NULL) ^ (address_len == 0) );
-	if (address) {
-		auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, address_len);
+	if (address && address_len)
+		NLA_PUT (nlmsg, IFLA_ADDRESS, address_len, address);
 
-		rtnl_link_set_addr ((struct rtnl_link *) l, nladdr);
-	}
+	if (!_nl_msg_new_link_set_linkinfo (nlmsg, type))
+		return FALSE;
 
-	return do_add_link_with_lookup (platform, name, (struct rtnl_link *) l, type, out_link);
+	return do_add_link_with_lookup (platform, type, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 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_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
 	obj = nmp_cache_lookup_link (priv->cache, ifindex);
 	if (!obj || !obj->_link.netlink.is_in_netlink)
 		return FALSE;
 
-	nmp_object_stackinit_id_link (&obj_needle, ifindex);
-	return do_delete_object (platform, &obj_needle, NULL);
+	nlmsg = _nl_msg_new_link (RTM_DELLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+
+	nmp_object_stackinit_id_link (&obj_id, ifindex);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const char *
@@ -2933,33 +3960,35 @@ link_get_unmanaged (NMPlatform *platform, int ifindex, gboolean *unmanaged)
 static gboolean
 link_refresh (NMPlatform *platform, int ifindex)
 {
-	do_request_link (platform, ifindex, NULL, TRUE);
+	do_request_link (platform, ifindex, NULL);
 	return !!cache_lookup_link (platform, ifindex);
 }
 
 static NMPlatformError
-link_change_flags (NMPlatform *platform, int ifindex, unsigned int flags, gboolean value)
-{
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
-	const NMPObject *obj_cache;
-	char buf[256];
-
-	obj_cache = cache_lookup_link (platform, ifindex);
-	if (!obj_cache)
-		return NM_PLATFORM_ERROR_NOT_FOUND;
-
-	rtnl_link_set_flags (change, obj_cache->link.flags);
-	if (value)
-		rtnl_link_set_flags (change, flags);
-	else
-		rtnl_link_unset_flags (change, flags);
-
-	_LOGD ("link: change %d: flags %s '%s' (%d)", ifindex,
-	       value ? "set" : "unset",
-	       rtnl_link_flags2str (flags, buf, sizeof (buf)),
-	       flags);
-
-	return do_change_link (platform, change, FALSE);
+link_change_flags (NMPlatform *platform,
+                   int ifindex,
+                   unsigned flags_mask,
+                   unsigned flags_set)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	char s_flags[100];
+
+	_LOGD ("link: change %d: flags: set 0x%x/0x%x ([%s] / [%s])",
+	       ifindex,
+	       flags_set,
+	       flags_mask,
+	       nm_platform_link_flags2str (flags_set, s_flags, sizeof (s_flags)),
+	       nm_platform_link_flags2str (flags_mask, NULL, 0));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          flags_mask,
+	                          flags_set);
+	if (!nlmsg)
+		return NM_PLATFORM_ERROR_UNSPECIFIED;
+	return do_change_link (platform, ifindex, nlmsg);
 }
 
 static gboolean
@@ -2967,7 +3996,7 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 {
 	NMPlatformError plerr;
 
-	plerr = link_change_flags (platform, ifindex, IFF_UP, TRUE);
+	plerr = link_change_flags (platform, ifindex, IFF_UP, IFF_UP);
 	if (out_no_firmware)
 		*out_no_firmware = plerr == NM_PLATFORM_ERROR_NO_FIRMWARE;
 	return plerr == NM_PLATFORM_ERROR_SUCCESS;
@@ -2976,19 +4005,19 @@ link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware)
 static gboolean
 link_set_down (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_UP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_UP, 0) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_arp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, FALSE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_NOARP, 0) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
 link_set_noarp (NMPlatform *platform, int ifindex)
 {
-	return link_change_flags (platform, ifindex, IFF_NOARP, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	return link_change_flags (platform, ifindex, IFF_NOARP, IFF_NOARP) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static const char *
@@ -3020,19 +4049,30 @@ link_get_udev_device (NMPlatform *platform, int ifindex)
 static gboolean
 link_set_user_ipv6ll_enabled (NMPlatform *platform, int ifindex, gboolean enabled)
 {
-#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
-	if (_support_user_ipv6ll_get ()) {
-		auto_nl_object struct rtnl_link *nlo = _nl_rtnl_link_alloc (ifindex, NULL);
-		guint8 mode = enabled ? IN6_ADDR_GEN_MODE_NONE : IN6_ADDR_GEN_MODE_EUI64;
-		char buf[32];
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	guint8 mode = enabled ? NM_IN6_ADDR_GEN_MODE_NONE : NM_IN6_ADDR_GEN_MODE_EUI64;
 
-		rtnl_link_inet6_set_addr_gen_mode (nlo, mode);
-		debug ("link: change %d: set IPv6 address generation mode to %s",
-		       ifindex, rtnl_link_inet6_addrgenmode2str (mode, buf, sizeof (buf)));
-		return do_change_link (platform, nlo, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	if (!_support_user_ipv6ll_get ()) {
+		_LOGD ("link: change %d: user-ipv6ll: not supported", ifindex);
+		return FALSE;
 	}
-#endif
-	return FALSE;
+
+	_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,
+	                          NULL,
+	                          0,
+	                          0);
+	if (   !nlmsg
+	    || !_nl_msg_new_link_set_afspec (nlmsg,
+	                                     mode))
+		g_return_val_if_reached (FALSE);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
 }
 
 static gboolean
@@ -3067,16 +4107,30 @@ link_supports_vlans (NMPlatform *platform, int ifindex)
 static gboolean
 link_set_address (NMPlatform *platform, int ifindex, gconstpointer address, size_t length)
 {
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
-	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (AF_LLC, address, length);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 	gs_free char *mac = NULL;
 
-	rtnl_link_set_addr (change, nladdr);
+	if (!address || !length)
+		g_return_val_if_reached (FALSE);
 
-	_LOGD ("link: change %d: address %s (%lu bytes)", ifindex,
+	_LOGD ("link: change %d: address: %s (%lu bytes)", ifindex,
 	       (mac = nm_utils_hwaddr_ntoa (address, length)),
 	       (unsigned long) length);
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT (nlmsg, IFLA_ADDRESS, length, address);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
@@ -3091,12 +4145,24 @@ link_get_permanent_address (NMPlatform *platform,
 static gboolean
 link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu)
 {
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (ifindex, NULL);
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	rtnl_link_set_mtu (change, mtu);
-	debug ("link: change %d: mtu %lu", ifindex, (unsigned long)mtu);
+	_LOGD ("link: change %d: mtu: %u", ifindex, (unsigned) mtu);
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
+
+	NLA_PUT_U32 (nlmsg, IFLA_MTU, mtu);
+
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static char *
@@ -3148,649 +4214,680 @@ vlan_add (NMPlatform *platform,
           int parent,
           int vlan_id,
           guint32 vlan_flags,
-          NMPlatformLink *out_link)
+          const NMPlatformLink **out_link)
 {
-	auto_nl_object struct rtnl_link *rtnllink = (struct rtnl_link *) build_rtnl_link (0, name, NM_LINK_TYPE_VLAN);
-	unsigned int kernel_flags;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	kernel_flags = 0;
-	if (vlan_flags & NM_VLAN_FLAG_REORDER_HEADERS)
-		kernel_flags |= VLAN_FLAG_REORDER_HDR;
-	if (vlan_flags & NM_VLAN_FLAG_GVRP)
-		kernel_flags |= VLAN_FLAG_GVRP;
-	if (vlan_flags & NM_VLAN_FLAG_LOOSE_BINDING)
-		kernel_flags |= VLAN_FLAG_LOOSE_BINDING;
+	G_STATIC_ASSERT (NM_VLAN_FLAG_REORDER_HEADERS == (guint32) VLAN_FLAG_REORDER_HDR);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_GVRP == (guint32) VLAN_FLAG_GVRP);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_LOOSE_BINDING == (guint32) VLAN_FLAG_LOOSE_BINDING);
+	G_STATIC_ASSERT (NM_VLAN_FLAG_MVRP == (guint32) VLAN_FLAG_MVRP);
 
-	rtnl_link_set_link (rtnllink, parent);
-	rtnl_link_vlan_set_id (rtnllink, vlan_id);
-	rtnl_link_vlan_set_flags (rtnllink, kernel_flags);
+	vlan_flags &= (guint32) NM_VLAN_FLAGS_ALL;
 
-	debug ("link: add vlan '%s', parent %d, vlan id %d, flags %X (native: %X)",
-	       name, parent, vlan_id, (unsigned int) vlan_flags, kernel_flags);
+	_LOGD ("link: add vlan '%s', parent %d, vlan id %d, flags %X",
+	       name, parent, vlan_id, (unsigned int) vlan_flags);
 
-	return do_add_link_with_lookup (platform, name, rtnllink, NM_LINK_TYPE_VLAN, out_link);
-}
-
-static gboolean
-vlan_get_info (NMPlatform *platform, int ifindex, int *parent, int *vlan_id)
-{
-	const NMPObject *obj = cache_lookup_link (platform, ifindex);
-	int p = 0, v = 0;
-
-	if (obj) {
-		p = obj->link.parent;
-		v = obj->link.vlan_id;
-	}
-	if (parent)
-		*parent = p;
-	if (vlan_id)
-		*vlan_id = v;
-	return !!obj;
-}
-
-static gboolean
-vlan_set_ingress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
-
-	rtnl_link_set_type (change, "vlan");
-	rtnl_link_vlan_set_ingress_map (change, from, to);
-
-	debug ("link: change %d: vlan ingress map %d -> %d", ifindex, from, to);
-
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
-}
-
-static gboolean
-vlan_set_egress_map (NMPlatform *platform, int ifindex, int from, int to)
-{
-	auto_nl_object struct rtnl_link *change = (struct rtnl_link *) build_rtnl_link (ifindex, NULL, NM_LINK_TYPE_VLAN);
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	rtnl_link_set_type (change, "vlan");
-	rtnl_link_vlan_set_egress_map (change, from, to);
+	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
 
-	debug ("link: change %d: vlan egress map %d -> %d", ifindex, from, to);
+	if (!_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
+	                                         vlan_id,
+	                                         NM_VLAN_FLAGS_ALL,
+	                                         vlan_flags,
+	                                         NULL,
+	                                         0,
+	                                         NULL,
+	                                         0))
+		return FALSE;
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VLAN, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_enslave (NMPlatform *platform, int master, int slave)
-{
-	auto_nl_object struct rtnl_link *change = _nl_rtnl_link_alloc (slave, NULL);
+static int
+link_gre_add (NMPlatform *platform,
+              const char *name,
+              const NMPlatformLnkGre *props,
+              const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "gre",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	rtnl_link_set_master (change, master);
-	debug ("link: change %d: enslave to master %d", slave, master);
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-	return do_change_link (platform, change, TRUE) == NM_PLATFORM_ERROR_SUCCESS;
-}
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "gre");
 
-static gboolean
-link_release (NMPlatform *platform, int master, int slave)
-{
-	return link_enslave (platform, 0, slave);
-}
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-static char *
-link_option_path (NMPlatform *platform, int master, const char *category, const char *option)
-{
-	const char *name = nm_platform_link_get_name (platform, master);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_GRE_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_GRE_PMTUDISC, !!props->path_mtu_discovery);
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_IKEY, htonl (props->input_key));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_OKEY, htonl (props->output_key));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_IFLAGS, htons (props->input_flags));
+	NLA_PUT_U32 (nlmsg, IFLA_GRE_OFLAGS, htons (props->output_flags));
 
-	if (!name || !category || !option)
-		return NULL;
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	return g_strdup_printf ("/sys/class/net/%s/%s/%s",
-	                        ASSERT_VALID_PATH_COMPONENT (name),
-	                        ASSERT_VALID_PATH_COMPONENT (category),
-	                        ASSERT_VALID_PATH_COMPONENT (option));
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_GRE, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-link_set_option (NMPlatform *platform, int master, const char *category, const char *option, const char *value)
-{
-	gs_free char *path = link_option_path (platform, master, category, option);
+static int
+link_ip6tnl_add (NMPlatform *platform,
+                 const char *name,
+                 const NMPlatformLnkIp6Tnl *props,
+                 const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+	guint32 flowinfo;
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ip6tnl",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet6_ntop (&props->local, NULL),
+	       nm_utils_inet6_ntop (&props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	return path && nm_platform_sysctl_set (platform, path, value);
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static char *
-link_get_option (NMPlatform *platform, int master, const char *category, const char *option)
-{
-	gs_free char *path = link_option_path (platform, master, category, option);
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ip6tnl");
 
-	return path ? nm_platform_sysctl_get (platform, path) : NULL;
-}
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-static const char *
-master_category (NMPlatform *platform, int master)
-{
-	switch (nm_platform_link_get_type (platform, master)) {
-	case NM_LINK_TYPE_BRIDGE:
-		return "bridge";
-	case NM_LINK_TYPE_BOND:
-		return "bonding";
-	default:
-		return NULL;
-	}
-}
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
 
-static const char *
-slave_category (NMPlatform *platform, int slave)
-{
-	int master = nm_platform_link_get_master (platform, slave);
+	if (memcmp (&props->local, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_IPTUN_LOCAL, sizeof (props->local), &props->local);
+	if (memcmp (&props->remote, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_IPTUN_REMOTE, sizeof (props->remote), &props->remote);
 
-	if (master <= 0)
-		return NULL;
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_ENCAP_LIMIT, props->encap_limit);
 
-	switch (nm_platform_link_get_type (platform, master)) {
-	case NM_LINK_TYPE_BRIDGE:
-		return "brport";
-	default:
-		return NULL;
-	}
-}
+	flowinfo = props->flow_label & IP6_FLOWINFO_FLOWLABEL_MASK;
+	flowinfo |=   (props->tclass << IP6_FLOWINFO_TCLASS_SHIFT)
+	            & IP6_FLOWINFO_TCLASS_MASK;
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_FLOWINFO, htonl (flowinfo));
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PROTO, props->proto);
 
-static gboolean
-master_set_option (NMPlatform *platform, int master, const char *option, const char *value)
-{
-	return link_set_option (platform, master, master_category (platform, master), option, value);
-}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-static char *
-master_get_option (NMPlatform *platform, int master, const char *option)
-{
-	return link_get_option (platform, master, master_category (platform, master), option);
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IP6TNL, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-slave_set_option (NMPlatform *platform, int slave, const char *option, const char *value)
-{
-	return link_set_option (platform, slave, slave_category (platform, slave), option, value);
-}
+static int
+link_ipip_add (NMPlatform *platform,
+               const char *name,
+               const NMPlatformLnkIpIp *props,
+               const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "ipip",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-static char *
-slave_get_option (NMPlatform *platform, int slave, const char *option)
-{
-	return link_get_option (platform, slave, slave_category (platform, slave), option);
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static gboolean
-infiniband_partition_add (NMPlatform *platform, int parent, int p_key, NMPlatformLink *out_link)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *obj_parent;
-	const NMPObject *obj;
-	gs_free char *path = NULL;
-	gs_free char *id = NULL;
-	gs_free char *ifname = NULL;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "ipip");
 
-	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
-	if (!obj_parent || !obj_parent->link.name[0])
-		g_return_val_if_reached (FALSE);
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
 
-	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
-	id = g_strdup_printf ("0x%04x", p_key);
-	if (!nm_platform_sysctl_set (platform, path, id))
-		return FALSE;
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	do_request_link (platform, 0, ifname, TRUE);
-
-	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
-	                                  0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL);
-	if (out_link && obj)
-		*out_link = obj->link;
-	return !!obj;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_IPIP, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-typedef struct {
-	int p_key;
-	const char *mode;
-} IpoibInfo;
-
-/* IFLA_IPOIB_* were introduced in the 3.7 kernel, but the kernel headers
- * we're building against might not have those properties even though the
- * running kernel might.
- */
-#define IFLA_IPOIB_UNSPEC 0
-#define IFLA_IPOIB_PKEY   1
-#define IFLA_IPOIB_MODE   2
-#define IFLA_IPOIB_UMCAST 3
-#undef IFLA_IPOIB_MAX
-#define IFLA_IPOIB_MAX IFLA_IPOIB_UMCAST
+static int
+link_macvlan_add (NMPlatform *platform,
+                  const char *name,
+                  int parent,
+                  const NMPlatformLnkMacvlan *props,
+                  const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+
+	_LOGD ("adding %s '%s' parent %u mode %u",
+	       props->tap ? "macvtap" : "macvlan",
+	       name,
+	       parent,
+	       props->mode);
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-#define IPOIB_MODE_DATAGRAM  0 /* using unreliable datagram QPs */
-#define IPOIB_MODE_CONNECTED 1 /* using connected QPs */
+	NLA_PUT_U32 (nlmsg, IFLA_LINK, parent);
 
-static const struct nla_policy infiniband_info_policy[IFLA_IPOIB_MAX + 1] = {
-	[IFLA_IPOIB_PKEY]	= { .type = NLA_U16 },
-	[IFLA_IPOIB_MODE]	= { .type = NLA_U16 },
-	[IFLA_IPOIB_UMCAST]	= { .type = NLA_U16 },
-};
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static int
-infiniband_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	IpoibInfo *info = parser_data;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, props->tap ? "macvtap" : "macvlan");
 
-	err = nla_parse_nested (tb, IFLA_IPOIB_MAX, info_data,
-	                        (struct nla_policy *) infiniband_info_policy);
-	if (err < 0)
-		return err;
-	if (!tb[IFLA_IPOIB_PKEY] || !tb[IFLA_IPOIB_MODE])
-		return -EINVAL;
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	info->p_key = nla_get_u16 (tb[IFLA_IPOIB_PKEY]);
+	NLA_PUT_U32 (nlmsg, IFLA_MACVLAN_MODE, props->mode);
+	NLA_PUT_U16 (nlmsg, IFLA_MACVLAN_FLAGS, props->no_promisc ? MACVLAN_FLAG_NOPROMISC : 0);
 
-	switch (nla_get_u16 (tb[IFLA_IPOIB_MODE])) {
-	case IPOIB_MODE_DATAGRAM:
-		info->mode = "datagram";
-		break;
-	case IPOIB_MODE_CONNECTED:
-		info->mode = "connected";
-		break;
-	default:
-		return -NLE_PARSE_ERR;
-	}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	return 0;
+	return do_add_link_with_lookup (platform,
+	                                props->tap ? NM_LINK_TYPE_MACVTAP : NM_LINK_TYPE_MACVLAN,
+	                                name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static gboolean
-infiniband_get_info (NMPlatform *platform, int ifindex, int *parent, int *p_key, const char **mode)
-{
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	const NMPObject *obj;
-	IpoibInfo info = { -1, NULL };
-
-	obj = cache_lookup_link (platform, ifindex);
-	if (!obj)
+static int
+link_sit_add (NMPlatform *platform,
+              const char *name,
+              const NMPlatformLnkSit *props,
+              const NMPlatformLink **out_link)
+{
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	char buffer[INET_ADDRSTRLEN];
+
+	_LOGD (LOG_FMT_IP_TUNNEL,
+	       "sit",
+	       name,
+	       props->parent_ifindex,
+	       nm_utils_inet4_ntop (props->local, NULL),
+	       nm_utils_inet4_ntop (props->remote, buffer));
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
 		return FALSE;
 
-	if (parent)
-		*parent = obj->link.parent;
-
-	if (_nl_link_parse_info_data (priv->nlh,
-	                              ifindex,
-	                              infiniband_info_data_parser,
-	                              &info) != 0) {
-		const char *iface = obj->link.name;
-		char *path, *contents = NULL;
-
-		/* Fall back to reading sysfs */
-		path = g_strdup_printf ("/sys/class/net/%s/mode", ASSERT_VALID_PATH_COMPONENT (iface));
-		contents = nm_platform_sysctl_get (platform, path);
-		g_free (path);
-		if (!contents)
-			return FALSE;
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-		if (strstr (contents, "datagram"))
-			info.mode = "datagram";
-		else if (strstr (contents, "connected"))
-			info.mode = "connected";
-		g_free (contents);
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "sit");
 
-		path = g_strdup_printf ("/sys/class/net/%s/pkey", ASSERT_VALID_PATH_COMPONENT (iface));
-		contents = nm_platform_sysctl_get (platform, path);
-		g_free (path);
-		if (!contents)
-			return FALSE;
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-		info.p_key = (int) _nm_utils_ascii_str_to_int64 (contents, 16, 0, 0xFFFF, -1);
-		g_free (contents);
+	if (props->parent_ifindex)
+		NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LINK, props->parent_ifindex);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_LOCAL, props->local);
+	NLA_PUT_U32 (nlmsg, IFLA_IPTUN_REMOTE, props->remote);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TTL, props->ttl);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_IPTUN_PMTUDISC, !!props->path_mtu_discovery);
 
-		if (info.p_key < 0)
-			return FALSE;
-	}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-	if (p_key)
-		*p_key = info.p_key;
-	if (mode)
-		*mode = info.mode;
-	return TRUE;
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_SIT, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
 static gboolean
-veth_get_properties (NMPlatform *platform, int ifindex, NMPlatformVethProperties *props)
+link_vxlan_add (NMPlatform *platform,
+                const char *name,
+                const NMPlatformLnkVxlan *props,
+                const NMPlatformLink **out_link)
 {
-	const char *ifname;
-	int peer_ifindex;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	struct nlattr *info;
+	struct nlattr *data;
+	struct nm_ifla_vxlan_port_range port_range;
 
-	ifname = nm_platform_link_get_name (platform, ifindex);
-	if (!ifname)
-		return FALSE;
+	g_return_val_if_fail (props, FALSE);
+
+	_LOGD ("link: add vxlan '%s', parent %d, vxlan id %d",
+	       name, props->parent_ifindex, props->id);
 
-	peer_ifindex = nmp_utils_ethtool_get_peer_ifindex (ifname);
-	if (peer_ifindex <= 0)
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          NLM_F_CREATE,
+	                          0,
+	                          name,
+	                          0,
+	                          0);
+	if (!nlmsg)
 		return FALSE;
 
-	props->peer = peer_ifindex;
-	return TRUE;
-}
+	if (!(info = nla_nest_start (nlmsg, IFLA_LINKINFO)))
+		goto nla_put_failure;
 
-static gboolean
-tun_get_properties_ifname (NMPlatform *platform, const char *ifname, NMPlatformTunProperties *props)
-{
-	char *path, *val;
-	gboolean success = TRUE;
+	NLA_PUT_STRING (nlmsg, IFLA_INFO_KIND, "vxlan");
 
-	g_return_val_if_fail (props, FALSE);
+	if (!(data = nla_nest_start (nlmsg, IFLA_INFO_DATA)))
+		goto nla_put_failure;
 
-	memset (props, 0, sizeof (*props));
-	props->owner = -1;
-	props->group = -1;
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_ID, props->id);
 
-	if (!ifname || !nm_utils_iface_valid_name (ifname))
-		return FALSE;
-	ifname = ASSERT_VALID_PATH_COMPONENT (ifname);
+	if (props->group)
+		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP, sizeof (props->group), &props->group);
+	else if (memcmp (&props->group6, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_VXLAN_GROUP6, sizeof (props->group6), &props->group6);
 
-	path = g_strdup_printf ("/sys/class/net/%s/owner", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		props->owner = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	if (props->local)
+		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL, sizeof (props->local), &props->local);
+	else if (memcmp (&props->local6, &in6addr_any, sizeof (in6addr_any)))
+		NLA_PUT (nlmsg, IFLA_VXLAN_LOCAL6, sizeof (props->local6), &props->local6);
 
-	path = g_strdup_printf ("/sys/class/net/%s/group", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		props->group = _nm_utils_ascii_str_to_int64 (val, 10, -1, G_MAXINT64, -1);
-		if (errno)
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	if (props->parent_ifindex >= 0)
+		NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LINK, props->parent_ifindex);
 
-	path = g_strdup_printf ("/sys/class/net/%s/tun_flags", ifname);
-	val = nm_platform_sysctl_get (platform, path);
-	g_free (path);
-	if (val) {
-		gint64 flags;
+	if (props->src_port_min || props->src_port_max) {
+		port_range.low = htons (props->src_port_min);
+		port_range.high = htons (props->src_port_max);
+		NLA_PUT (nlmsg, IFLA_VXLAN_PORT_RANGE, sizeof (port_range), &port_range);
+	}
 
-		flags = _nm_utils_ascii_str_to_int64 (val, 16, 0, G_MAXINT64, 0);
-		if (!errno) {
-#ifndef IFF_MULTI_QUEUE
-			const int IFF_MULTI_QUEUE = 0x0100;
-#endif
-			props->mode = ((flags & (IFF_TUN | IFF_TAP)) == IFF_TUN) ? "tun" : "tap";
-			props->no_pi = !!(flags & IFF_NO_PI);
-			props->vnet_hdr = !!(flags & IFF_VNET_HDR);
-			props->multi_queue = !!(flags & IFF_MULTI_QUEUE);
-		} else
-			success = FALSE;
-		g_free (val);
-	} else
-		success = FALSE;
+	NLA_PUT_U16 (nlmsg, IFLA_VXLAN_PORT, htons (props->dst_port));
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TOS, props->tos);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_TTL, props->ttl);
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_AGEING, props->ageing);
+	NLA_PUT_U32 (nlmsg, IFLA_VXLAN_LIMIT, props->limit);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_LEARNING, !!props->learning);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_PROXY, !!props->proxy);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_RSC, !!props->rsc);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L2MISS, !!props->l2miss);
+	NLA_PUT_U8 (nlmsg, IFLA_VXLAN_L3MISS, !!props->l3miss);
 
-	return success;
-}
+	nla_nest_end (nlmsg, data);
+	nla_nest_end (nlmsg, info);
 
-static gboolean
-tun_get_properties (NMPlatform *platform, int ifindex, NMPlatformTunProperties *props)
-{
-	return tun_get_properties_ifname (platform, nm_platform_link_get_name (platform, ifindex), props);
+	return do_add_link_with_lookup (platform, NM_LINK_TYPE_VXLAN, name, nlmsg, out_link);
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
 }
 
-static const struct nla_policy macvlan_info_policy[IFLA_MACVLAN_MAX + 1] = {
-	[IFLA_MACVLAN_MODE]  = { .type = NLA_U32 },
-#ifdef MACVLAN_FLAG_NOPROMISC
-	[IFLA_MACVLAN_FLAGS] = { .type = NLA_U16 },
-#endif
-};
-
-static int
-macvlan_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	NMPlatformMacvlanProperties *props = parser_data;
-	struct nlattr *tb[IFLA_MACVLAN_MAX + 1];
-	int err;
+static void
+_vlan_change_vlan_qos_mapping_create (gboolean is_ingress_map,
+                                      gboolean reset_all,
+                                      const NMVlanQosMapping *current_map,
+                                      guint current_n_map,
+                                      const NMVlanQosMapping *set_map,
+                                      guint set_n_map,
+                                      NMVlanQosMapping **out_map,
+                                      guint *out_n_map)
+{
+	NMVlanQosMapping *map;
+	guint i, j, len;
+	const guint INGRESS_RANGE_LEN = 8;
+
+	nm_assert (out_map && !*out_map);
+	nm_assert (out_n_map && !*out_n_map);
+
+	if (!reset_all)
+		current_n_map = 0;
+	else if (is_ingress_map)
+		current_n_map = INGRESS_RANGE_LEN;
+
+	len = current_n_map + set_n_map;
+
+	if (len == 0)
+		return;
 
-	err = nla_parse_nested (tb, IFLA_MACVLAN_MAX, info_data,
-	                        (struct nla_policy *) macvlan_info_policy);
-	if (err < 0)
-		return err;
+	map = g_new (NMVlanQosMapping, len);
 
-	switch (nla_get_u32 (tb[IFLA_MACVLAN_MODE])) {
-	case MACVLAN_MODE_PRIVATE:
-		props->mode = "private";
-		break;
-	case MACVLAN_MODE_VEPA:
-		props->mode = "vepa";
-		break;
-	case MACVLAN_MODE_BRIDGE:
-		props->mode = "bridge";
-		break;
-	case MACVLAN_MODE_PASSTHRU:
-		props->mode = "passthru";
-		break;
-	default:
-		return -NLE_PARSE_ERR;
+	if (current_n_map) {
+		if (is_ingress_map) {
+			/* For the ingress-map, there are only 8 entries (0 to 7).
+			 * When the user requests to reset all entires, we don't actually
+			 * need the cached entries, we can just explicitly clear all possible
+			 * ones.
+			 *
+			 * That makes only a real difference in case our cache is out-of-date.
+			 *
+			 * For the egress map we cannot do that, because there are far too
+			 * many. There we can only clear the entries that we know about. */
+			for (i = 0; i < INGRESS_RANGE_LEN; i++) {
+				map[i].from = i;
+				map[i].to = 0;
+			}
+		} else {
+			for (i = 0; i < current_n_map; i++) {
+				map[i].from = current_map[i].from;
+				map[i].to = 0;
+			}
+		}
+	}
+	if (set_n_map)
+		memcpy (&map[current_n_map], set_map, sizeof (*set_map) * set_n_map);
+
+	g_qsort_with_data (map,
+	                   len,
+	                   sizeof (*map),
+	                   _vlan_qos_mapping_cmp_from,
+	                   NULL);
+
+	for (i = 0, j = 0; i < len; i++) {
+		if (   ( is_ingress_map && !VLAN_XGRESS_PRIO_VALID (map[i].from))
+		    || (!is_ingress_map && !VLAN_XGRESS_PRIO_VALID (map[i].to)))
+			continue;
+		if (   j > 0
+		    && map[j - 1].from == map[i].from)
+			map[j - 1] = map[i];
+		else
+			map[j++] = map[i];
 	}
 
-#ifdef MACVLAN_FLAG_NOPROMISC
-	props->no_promisc = !!(nla_get_u16 (tb[IFLA_MACVLAN_FLAGS]) & MACVLAN_FLAG_NOPROMISC);
-#else
-	props->no_promisc = FALSE;
-#endif
-
-	return 0;
+	*out_map = map;
+	*out_n_map = j;
 }
 
 static gboolean
-macvlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformMacvlanProperties *props)
+link_vlan_change (NMPlatform *platform,
+                  int ifindex,
+                  NMVlanFlags flags_mask,
+                  NMVlanFlags flags_set,
+                  gboolean ingress_reset_all,
+                  const NMVlanQosMapping *ingress_map,
+                  gsize n_ingress_map,
+                  gboolean egress_reset_all,
+                  const NMVlanQosMapping *egress_map,
+                  gsize n_egress_map)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
-	const NMPObject *obj;
-
-	obj = cache_lookup_link (platform, ifindex);
-	if (!obj)
+	const NMPObject *obj_cache;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	unsigned flags;
+	const NMPObjectLnkVlan *lnk;
+	guint new_n_ingress_map = 0;
+	guint new_n_egress_map = 0;
+	gs_free NMVlanQosMapping *new_ingress_map = NULL;
+	gs_free NMVlanQosMapping *new_egress_map = NULL;
+	char s_flags[64];
+	char s_ingress[256];
+	char s_egress[256];
+
+	obj_cache = nmp_cache_lookup_link (priv->cache, ifindex);
+	if (   !obj_cache
+	    || !obj_cache->_link.netlink.is_in_netlink) {
+		_LOGD ("link: change %d: %s: link does not exist", ifindex, "vlan");
 		return FALSE;
-
-	props->parent_ifindex = obj->link.parent;
-
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                macvlan_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read properties: %s",
-		         obj->link.name, nl_geterror (err));
 	}
-	return (err == 0);
-}
-
-/* The installed kernel headers might not have VXLAN stuff at all, or
- * they might have the original properties, but not PORT, GROUP6, or LOCAL6.
- * So until we depend on kernel >= 3.11, we just ignore the actual enum
- * in if_link.h and define the values ourselves.
- */
-#define IFLA_VXLAN_UNSPEC      0
-#define IFLA_VXLAN_ID          1
-#define IFLA_VXLAN_GROUP       2
-#define IFLA_VXLAN_LINK        3
-#define IFLA_VXLAN_LOCAL       4
-#define IFLA_VXLAN_TTL         5
-#define IFLA_VXLAN_TOS         6
-#define IFLA_VXLAN_LEARNING    7
-#define IFLA_VXLAN_AGEING      8
-#define IFLA_VXLAN_LIMIT       9
-#define IFLA_VXLAN_PORT_RANGE 10
-#define IFLA_VXLAN_PROXY      11
-#define IFLA_VXLAN_RSC        12
-#define IFLA_VXLAN_L2MISS     13
-#define IFLA_VXLAN_L3MISS     14
-#define IFLA_VXLAN_PORT       15
-#define IFLA_VXLAN_GROUP6     16
-#define IFLA_VXLAN_LOCAL6     17
-#undef IFLA_VXLAN_MAX
-#define IFLA_VXLAN_MAX IFLA_VXLAN_LOCAL6
 
-/* older kernel header might not contain 'struct ifla_vxlan_port_range'.
- * Redefine it. */
-struct nm_ifla_vxlan_port_range {
-	guint16 low;
-	guint16 high;
-};
+	lnk = obj_cache->_link.netlink.lnk ? &obj_cache->_link.netlink.lnk->_lnk_vlan : NULL;
+	flags = obj_cache->link.flags;
+
+	flags_set &= flags_mask;
+
+	_vlan_change_vlan_qos_mapping_create (TRUE,
+	                                      ingress_reset_all,
+	                                      lnk ? lnk->ingress_qos_map : NULL,
+	                                      lnk ? lnk->n_ingress_qos_map : 0,
+	                                      ingress_map,
+	                                      n_ingress_map,
+	                                      &new_ingress_map,
+	                                      &new_n_ingress_map);
+
+	_vlan_change_vlan_qos_mapping_create (FALSE,
+	                                      egress_reset_all,
+	                                      lnk ? lnk->egress_qos_map : NULL,
+	                                      lnk ? lnk->n_egress_qos_map : 0,
+	                                      egress_map,
+	                                      n_egress_map,
+	                                      &new_egress_map,
+	                                      &new_n_egress_map);
+
+	_LOGD ("link: change %d: vlan:%s%s%s",
+	       ifindex,
+	       flags_mask
+	           ? nm_sprintf_buf (s_flags, " flags 0x%x/0x%x", (unsigned) flags_set, (unsigned) flags_mask)
+	           : "",
+	       new_n_ingress_map
+	           ? nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
+	                                                     new_ingress_map,
+	                                                     new_n_ingress_map,
+	                                                     s_ingress,
+	                                                     sizeof (s_ingress))
+	           : "",
+	       new_n_egress_map
+	           ? nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
+	                                                     new_egress_map,
+	                                                     new_n_egress_map,
+	                                                     s_egress,
+	                                                     sizeof (s_egress))
+	           : "");
+
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (   !nlmsg
+	    || !_nl_msg_new_link_set_linkinfo_vlan (nlmsg,
+	                                            -1,
+	                                            flags_mask,
+	                                            flags_set,
+	                                            new_ingress_map,
+	                                            new_n_ingress_map,
+	                                            new_egress_map,
+	                                            new_n_egress_map))
+		g_return_val_if_reached (FALSE);
 
-static const struct nla_policy vxlan_info_policy[IFLA_VXLAN_MAX + 1] = {
-	[IFLA_VXLAN_ID]         = { .type = NLA_U32 },
-	[IFLA_VXLAN_GROUP]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_GROUP6]     = { .type = NLA_UNSPEC,
-	                            .minlen = sizeof (struct in6_addr) },
-	[IFLA_VXLAN_LINK]       = { .type = NLA_U32 },
-	[IFLA_VXLAN_LOCAL]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_LOCAL6]     = { .type = NLA_UNSPEC,
-	                            .minlen = sizeof (struct in6_addr) },
-	[IFLA_VXLAN_TOS]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_TTL]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_LEARNING]   = { .type = NLA_U8 },
-	[IFLA_VXLAN_AGEING]     = { .type = NLA_U32 },
-	[IFLA_VXLAN_LIMIT]      = { .type = NLA_U32 },
-	[IFLA_VXLAN_PORT_RANGE] = { .type = NLA_UNSPEC,
-	                            .minlen  = sizeof (struct nm_ifla_vxlan_port_range) },
-	[IFLA_VXLAN_PROXY]      = { .type = NLA_U8 },
-	[IFLA_VXLAN_RSC]        = { .type = NLA_U8 },
-	[IFLA_VXLAN_L2MISS]     = { .type = NLA_U8 },
-	[IFLA_VXLAN_L3MISS]     = { .type = NLA_U8 },
-	[IFLA_VXLAN_PORT]       = { .type = NLA_U16 },
-};
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+}
 
 static int
-vxlan_info_data_parser (struct nlattr *info_data, gpointer parser_data)
+tun_add (NMPlatform *platform, const char *name, gboolean tap,
+         gint64 owner, gint64 group, gboolean pi, gboolean vnet_hdr,
+         gboolean multi_queue, const NMPlatformLink **out_link)
 {
-	NMPlatformVxlanProperties *props = parser_data;
-	struct nlattr *tb[IFLA_VXLAN_MAX + 1];
-	struct nm_ifla_vxlan_port_range *range;
-	int err;
+	const NMPObject *obj;
+	struct ifreq ifr = { };
+	int fd;
 
-	err = nla_parse_nested (tb, IFLA_VXLAN_MAX, info_data,
-	                        (struct nla_policy *) vxlan_info_policy);
-	if (err < 0)
-		return err;
+	_LOGD ("link: add %s '%s' owner %" G_GINT64_FORMAT " group %" G_GINT64_FORMAT,
+	       tap ? "tap" : "tun", name, owner, group);
 
-	memset (props, 0, sizeof (*props));
+	fd = open ("/dev/net/tun", O_RDWR);
+	if (fd < 0)
+		return FALSE;
 
-	if (tb[IFLA_VXLAN_LINK])
-		props->parent_ifindex = nla_get_u32 (tb[IFLA_VXLAN_LINK]);
-	if (tb[IFLA_VXLAN_ID])
-		props->id = nla_get_u32 (tb[IFLA_VXLAN_ID]);
-	if (tb[IFLA_VXLAN_GROUP])
-		props->group = nla_get_u32 (tb[IFLA_VXLAN_GROUP]);
-	if (tb[IFLA_VXLAN_LOCAL])
-		props->local = nla_get_u32 (tb[IFLA_VXLAN_LOCAL]);
-	if (tb[IFLA_VXLAN_GROUP6])
-		memcpy (&props->group6, nla_data (tb[IFLA_VXLAN_GROUP6]), sizeof (props->group6));
-	if (tb[IFLA_VXLAN_LOCAL6])
-		memcpy (&props->local6, nla_data (tb[IFLA_VXLAN_LOCAL6]), sizeof (props->local6));
+	strncpy (ifr.ifr_name, name, IFNAMSIZ);
+	ifr.ifr_flags = tap ? IFF_TAP : IFF_TUN;
 
-	if (tb[IFLA_VXLAN_AGEING])
-		props->ageing = nla_get_u32 (tb[IFLA_VXLAN_AGEING]);
-	if (tb[IFLA_VXLAN_LIMIT])
-		props->limit = nla_get_u32 (tb[IFLA_VXLAN_LIMIT]);
-	if (tb[IFLA_VXLAN_TOS])
-		props->tos = nla_get_u8 (tb[IFLA_VXLAN_TOS]);
-	if (tb[IFLA_VXLAN_TTL])
-		props->ttl = nla_get_u8 (tb[IFLA_VXLAN_TTL]);
+	if (!pi)
+		ifr.ifr_flags |= IFF_NO_PI;
+	if (vnet_hdr)
+		ifr.ifr_flags |= IFF_VNET_HDR;
+	if (multi_queue)
+		ifr.ifr_flags |= NM_IFF_MULTI_QUEUE;
 
-	if (tb[IFLA_VXLAN_PORT])
-		props->dst_port = nla_get_u16 (tb[IFLA_VXLAN_PORT]);
+	if (ioctl (fd, TUNSETIFF, &ifr)) {
+		close (fd);
+		return FALSE;
+	}
 
-	if (tb[IFLA_VXLAN_PORT_RANGE]) {
-		range = nla_data (tb[IFLA_VXLAN_PORT_RANGE]);
-		props->src_port_min = range->low;
-		props->src_port_max = range->high;
+	if (owner >= 0 && owner < G_MAXINT32) {
+		if (ioctl (fd, TUNSETOWNER, (uid_t) owner)) {
+			close (fd);
+			return FALSE;
+		}
 	}
 
-	if (tb[IFLA_VXLAN_LEARNING])
-		props->learning = !!nla_get_u8 (tb[IFLA_VXLAN_LEARNING]);
-	if (tb[IFLA_VXLAN_PROXY])
-		props->proxy = !!nla_get_u8 (tb[IFLA_VXLAN_PROXY]);
-	if (tb[IFLA_VXLAN_RSC])
-		props->rsc = !!nla_get_u8 (tb[IFLA_VXLAN_RSC]);
-	if (tb[IFLA_VXLAN_L2MISS])
-		props->l2miss = !!nla_get_u8 (tb[IFLA_VXLAN_L2MISS]);
-	if (tb[IFLA_VXLAN_L3MISS])
-		props->l3miss = !!nla_get_u8 (tb[IFLA_VXLAN_L3MISS]);
+	if (group >= 0 && group < G_MAXINT32) {
+		if (ioctl (fd, TUNSETGROUP, (gid_t) group)) {
+			close (fd);
+			return FALSE;
+		}
+	}
 
-	return 0;
+	if (ioctl (fd, TUNSETPERSIST, 1)) {
+		close (fd);
+		return FALSE;
+	}
+	do_request_link (platform, 0, name);
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, name, FALSE,
+	                                  tap ? NM_LINK_TYPE_TAP : NM_LINK_TYPE_TUN,
+	                                  NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+
+	return !!obj;
 }
 
 static gboolean
-vxlan_get_properties (NMPlatform *platform, int ifindex, NMPlatformVxlanProperties *props)
+link_enslave (NMPlatform *platform, int master, int slave)
 {
-	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	int ifindex = slave;
 
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                vxlan_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read vxlan properties: %s",
-		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
-	}
-	return (err == 0);
-}
-
-static const struct nla_policy gre_info_policy[IFLA_GRE_MAX + 1] = {
-	[IFLA_GRE_LINK]     = { .type = NLA_U32 },
-	[IFLA_GRE_IFLAGS]   = { .type = NLA_U16 },
-	[IFLA_GRE_OFLAGS]   = { .type = NLA_U16 },
-	[IFLA_GRE_IKEY]     = { .type = NLA_U32 },
-	[IFLA_GRE_OKEY]     = { .type = NLA_U32 },
-	[IFLA_GRE_LOCAL]    = { .type = NLA_U32 },
-	[IFLA_GRE_REMOTE]   = { .type = NLA_U32 },
-	[IFLA_GRE_TTL]      = { .type = NLA_U8 },
-	[IFLA_GRE_TOS]      = { .type = NLA_U8 },
-	[IFLA_GRE_PMTUDISC] = { .type = NLA_U8 },
-};
+	_LOGD ("link: change %d: enslave: master %d", slave, master);
 
-static int
-gre_info_data_parser (struct nlattr *info_data, gpointer parser_data)
-{
-	NMPlatformGreProperties *props = parser_data;
-	struct nlattr *tb[IFLA_GRE_MAX + 1];
-	int err;
+	nlmsg = _nl_msg_new_link (RTM_NEWLINK,
+	                          0,
+	                          ifindex,
+	                          NULL,
+	                          0,
+	                          0);
+	if (!nlmsg)
+		return FALSE;
 
-	err = nla_parse_nested (tb, IFLA_GRE_MAX, info_data,
-	                        (struct nla_policy *) gre_info_policy);
-	if (err < 0)
-		return err;
+	NLA_PUT_U32 (nlmsg, IFLA_MASTER, master);
 
-	props->parent_ifindex = tb[IFLA_GRE_LINK] ? nla_get_u32 (tb[IFLA_GRE_LINK]) : 0;
-	props->input_flags = nla_get_u16 (tb[IFLA_GRE_IFLAGS]);
-	props->output_flags = nla_get_u16 (tb[IFLA_GRE_OFLAGS]);
-	props->input_key = (props->input_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_IKEY]) : 0;
-	props->output_key = (props->output_flags & GRE_KEY) ? nla_get_u32 (tb[IFLA_GRE_OKEY]) : 0;
-	props->local = nla_get_u32 (tb[IFLA_GRE_LOCAL]);
-	props->remote = nla_get_u32 (tb[IFLA_GRE_REMOTE]);
-	props->tos = nla_get_u8 (tb[IFLA_GRE_TOS]);
-	props->ttl = nla_get_u8 (tb[IFLA_GRE_TTL]);
-	props->path_mtu_discovery = !!nla_get_u8 (tb[IFLA_GRE_PMTUDISC]);
+	return do_change_link (platform, ifindex, nlmsg) == NM_PLATFORM_ERROR_SUCCESS;
+nla_put_failure:
+	g_return_val_if_reached (FALSE);
+}
 
-	return 0;
+static gboolean
+link_release (NMPlatform *platform, int master, int slave)
+{
+	return link_enslave (platform, 0, slave);
 }
 
+/******************************************************************/
+
 static gboolean
-gre_get_properties (NMPlatform *platform, int ifindex, NMPlatformGreProperties *props)
+infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int err;
+	const NMPObject *obj_parent;
+	const NMPObject *obj;
+	gs_free char *path = NULL;
+	gs_free char *id = NULL;
+	gs_free char *ifname = NULL;
 
-	err = _nl_link_parse_info_data (priv->nlh, ifindex,
-	                                gre_info_data_parser, props);
-	if (err != 0) {
-		warning ("(%s) could not read gre properties: %s",
-		         nm_platform_link_get_name (platform, ifindex), nl_geterror (err));
-	}
-	return (err == 0);
+	obj_parent = nmp_cache_lookup_link (priv->cache, parent);
+	if (!obj_parent || !obj_parent->link.name[0])
+		g_return_val_if_reached (FALSE);
+
+	ifname = g_strdup_printf ("%s.%04x", obj_parent->link.name, p_key);
+
+	path = g_strdup_printf ("/sys/class/net/%s/create_child", ASSERT_VALID_PATH_COMPONENT (obj_parent->link.name));
+	id = g_strdup_printf ("0x%04x", p_key);
+	if (!nm_platform_sysctl_set (platform, path, id))
+		return FALSE;
+
+	do_request_link (platform, 0, ifname);
+
+	obj = nmp_cache_lookup_link_full (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache,
+	                                  0, ifname, FALSE, NM_LINK_TYPE_INFINIBAND, NULL, NULL);
+	if (out_link)
+		*out_link = obj ? &obj->link : NULL;
+	return !!obj;
 }
 
+/******************************************************************/
+
 static WifiData *
 wifi_get_wifi_data (NMPlatform *platform, int ifindex)
 {
@@ -3846,16 +4943,6 @@ wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid)
 	return wifi_utils_get_bssid (wifi_data, bssid);
 }
 
-static GByteArray *
-wifi_get_ssid (NMPlatform *platform, int ifindex)
-{
-	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
-
-	if (!wifi_data)
-		return NULL;
-	return wifi_utils_get_ssid (wifi_data);
-}
-
 static guint32
 wifi_get_frequency (NMPlatform *platform, int ifindex)
 {
@@ -3906,6 +4993,15 @@ wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode)
 		wifi_utils_set_mode (wifi_data, mode);
 }
 
+static void
+wifi_set_powersave (NMPlatform *platform, int ifindex, guint32 powersave)
+{
+	WifiData *wifi_data = wifi_get_wifi_data (platform, ifindex);
+
+	if (wifi_data)
+		wifi_utils_set_powersave (wifi_data, powersave);
+}
+
 static guint32
 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs)
 {
@@ -3926,6 +5022,7 @@ wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean ru
 		wifi_utils_indicate_addressing_running (wifi_data, running);
 }
 
+/******************************************************************/
 
 static guint32
 mesh_get_channel (NMPlatform *platform, int ifindex)
@@ -3960,6 +5057,8 @@ mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len)
 	return wifi_utils_set_mesh_ssid (wifi_data, ssid, len);
 }
 
+/******************************************************************/
+
 static gboolean
 link_get_wake_on_lan (NMPlatform *platform, int ifindex)
 {
@@ -4019,208 +5118,124 @@ ip6_address_get_all (NMPlatform *platform, int ifindex)
 	return ipx_address_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ADDRESS);
 }
 
-#define IPV4LL_NETWORK (htonl (0xA9FE0000L))
-#define IPV4LL_NETMASK (htonl (0xFFFF0000L))
-
-static gboolean
-ip4_is_link_local (const struct in_addr *src)
-{
-	return (src->s_addr & IPV4LL_NETMASK) == IPV4LL_NETWORK;
-}
-
-static struct nl_object *
-build_rtnl_addr (NMPlatform *platform,
-                 int family,
-                 int ifindex,
-                 gconstpointer addr,
-                 gconstpointer peer_addr,
-                 int plen,
-                 guint32 lifetime,
-                 guint32 preferred,
-                 guint flags,
-                 const char *label)
-{
-	auto_nl_object struct rtnl_addr *rtnladdr = _nl_rtnl_addr_alloc (ifindex);
-	struct rtnl_addr *rtnladdr_copy;
-	int addrlen = family == AF_INET ? sizeof (in_addr_t) : sizeof (struct in6_addr);
-	auto_nl_addr struct nl_addr *nladdr = _nl_addr_build (family, addr, addrlen);
-	int nle;
-
-	/* IP address */
-	nle = rtnl_addr_set_local (rtnladdr, nladdr);
-	if (nle) {
-		error ("build_rtnl_addr(): rtnl_addr_set_local failed with %s (%d)", nl_geterror (nle), nle);
-		return NULL;
-	}
-
-	/* Tighten scope (IPv4 only) */
-	if (family == AF_INET && ip4_is_link_local (addr))
-		rtnl_addr_set_scope (rtnladdr, RT_SCOPE_LINK);
-
-	/* IPv4 Broadcast address */
-	if (family == AF_INET) {
-		in_addr_t bcast;
-		auto_nl_addr struct nl_addr *bcaddr = NULL;
-
-		bcast = *((in_addr_t *) addr) | ~nm_utils_ip4_prefix_to_netmask (plen);
-		bcaddr = _nl_addr_build (family, &bcast, addrlen);
-		g_assert (bcaddr);
-		rtnl_addr_set_broadcast (rtnladdr, bcaddr);
-	}
-
-	/* Peer/point-to-point address */
-	if (peer_addr) {
-		auto_nl_addr struct nl_addr *nlpeer = _nl_addr_build (family, peer_addr, addrlen);
-
-		nle = rtnl_addr_set_peer (rtnladdr, nlpeer);
-		if (nle && nle != -NLE_AF_NOSUPPORT) {
-			/* IPv6 doesn't support peer addresses yet */
-			error ("build_rtnl_addr(): rtnl_addr_set_peer failed with %s (%d)", nl_geterror (nle), nle);
-			return NULL;
-		}
-	}
-
-	_nl_rtnl_addr_set_prefixlen (rtnladdr, plen);
-	if (   (lifetime  != 0 && lifetime  != NM_PLATFORM_LIFETIME_PERMANENT)
-	    || (preferred != 0 && preferred != NM_PLATFORM_LIFETIME_PERMANENT)) {
-		/* note that here we set the relative timestamps (ticking from *now*). */
-		rtnl_addr_set_valid_lifetime (rtnladdr, lifetime);
-		rtnl_addr_set_preferred_lifetime (rtnladdr, preferred);
-	}
-	if (flags) {
-		if ((flags & ~0xFF) && !_support_kernel_extended_ifa_flags_get ()) {
-			/* Older kernels don't accept unknown netlink attributes.
-			 *
-			 * With commit libnl commit 5206c050504f8676a24854519b9c351470fb7cc6, libnl will only set
-			 * the extended address flags attribute IFA_FLAGS when necessary (> 8 bit). But it's up to
-			 * us not to shove those extended flags on to older kernels.
-			 *
-			 * Just silently clear them. The kernel should ignore those unknown flags anyway. */
-			flags &= 0xFF;
-		}
-		rtnl_addr_set_flags (rtnladdr, flags);
-	}
-	if (label && *label)
-		rtnl_addr_set_label (rtnladdr, label);
-
-	rtnladdr_copy = rtnladdr;
-	rtnladdr = NULL;
-	return (struct nl_object *) rtnladdr_copy;
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj;
-	guint32 lifetime, preferred;
-
-	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
-	                        0, 0, &lifetime, &preferred);
-
-	return build_rtnl_addr (platform,
-	                        AF_INET,
-	                        obj->ifindex,
-	                        &obj->address,
-	                        obj->peer_address ? &obj->peer_address : NULL,
-	                        obj->plen,
-	                        lifetime,
-	                        preferred,
-	                        0,
-	                        obj->label[0] ? obj->label : NULL);
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj;
-	guint32 lifetime, preferred;
-
-	nmp_utils_lifetime_get (obj->timestamp, obj->lifetime, obj->preferred,
-	                        0, 0, &lifetime, &preferred);
-
-	return build_rtnl_addr (platform,
-	                        AF_INET6,
-	                        obj->ifindex,
-	                        &obj->address,
-	                        !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL,
-	                        obj->plen,
-	                        lifetime,
-	                        preferred,
-	                        0,
-	                        NULL);
-}
-
 static gboolean
 ip4_address_add (NMPlatform *platform,
                  int ifindex,
                  in_addr_t addr,
-                 in_addr_t peer_addr,
                  int plen,
+                 in_addr_t peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
                  const char *label)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	nlo = build_rtnl_addr (platform, AF_INET, ifindex, &addr,
-	                       peer_addr ? &peer_addr : NULL,
-	                       plen, lifetime, preferred, 0,
-	                       label);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen),
-	                         nlo);
+	nlmsg = _nl_msg_new_address (RTM_NEWADDR,
+	                             NLM_F_CREATE | NLM_F_REPLACE,
+	                             AF_INET,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_addr,
+	                             0,
+	                             ip4_address_is_link_local (addr) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE,
+	                             lifetime,
+	                             preferred,
+	                             label);
+
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_addr);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_address_add (NMPlatform *platform,
                  int ifindex,
                  struct in6_addr addr,
-                 struct in6_addr peer_addr,
                  int plen,
+                 struct in6_addr peer_addr,
                  guint32 lifetime,
                  guint32 preferred,
                  guint flags)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+
+	nlmsg = _nl_msg_new_address (RTM_NEWADDR,
+	                             NLM_F_CREATE | NLM_F_REPLACE,
+	                             AF_INET6,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_addr,
+	                             flags,
+	                             RT_SCOPE_UNIVERSE,
+	                             lifetime,
+	                             preferred,
+	                             NULL);
 
-	nlo = build_rtnl_addr (platform, AF_INET6, ifindex, &addr,
-	                       IN6_IS_ADDR_UNSPECIFIED (&peer_addr) ? NULL : &peer_addr,
-	                       plen, lifetime, preferred, flags,
-	                       NULL);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen),
-	                         nlo);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
+
+	nlmsg = _nl_msg_new_address (RTM_DELADDR,
+	                             0,
+	                             AF_INET,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             &peer_address,
+	                             0,
+	                             RT_SCOPE_NOWHERE,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NULL);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
-	obj_needle.ip4_address.peer_address = peer_address;
-	return do_delete_object (platform, &obj_needle, NULL);
+	nmp_object_stackinit_id_ip4_address (&obj_id, ifindex, addr, plen, peer_address);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
+
+	nlmsg = _nl_msg_new_address (RTM_DELADDR,
+	                             0,
+	                             AF_INET6,
+	                             ifindex,
+	                             &addr,
+	                             plen,
+	                             NULL,
+	                             0,
+	                             RT_SCOPE_NOWHERE,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NM_PLATFORM_LIFETIME_PERMANENT,
+	                             NULL);
+	if (!nlmsg)
+		g_return_val_if_reached (FALSE);
 
-	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
-	return do_delete_object (platform, &obj_needle, NULL);
+	nmp_object_stackinit_id_ip6_address (&obj_id, ifindex, &addr, plen);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const NMPlatformIP4Address *
-ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
+ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen, in_addr_t peer_address)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip4_address (&obj_needle, ifindex, addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	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;
@@ -4229,11 +5244,11 @@ ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, int plen)
 static const NMPlatformIP6Address *
 ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, int plen)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip6_address (&obj_needle, ifindex, &addr, plen);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	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;
@@ -4292,113 +5307,29 @@ ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags fl
 	return ipx_route_get_all (platform, ifindex, NMP_OBJECT_TYPE_IP6_ROUTE, flags);
 }
 
-static void
-clear_host_address (int family, const void *network, int plen, void *dst)
-{
-	g_return_if_fail (plen == (guint8)plen);
-	g_return_if_fail (network);
-
-	switch (family) {
-	case AF_INET:
-		*((in_addr_t *) dst) = nm_utils_ip4_address_clear_host_address (*((in_addr_t *) network), plen);
-		break;
-	case AF_INET6:
-		nm_utils_ip6_address_clear_host_address ((struct in6_addr *) dst, (const struct in6_addr *) network, plen);
-		break;
-	default:
-		g_assert_not_reached ();
-	}
-}
-
-static struct nl_object *
-build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
-                  gconstpointer network, int plen, gconstpointer gateway,
-                  gconstpointer pref_src,
-                  guint32 metric, guint32 mss)
-{
-	guint32 network_clean[4];
-	struct rtnl_route *rtnlroute;
-	struct rtnl_nexthop *nexthop;
-	int addrlen = (family == AF_INET) ? sizeof (in_addr_t) : sizeof (struct in6_addr);
-	/* Workaround a libnl bug by using zero destination address length for default routes */
-	auto_nl_addr struct nl_addr *dst = NULL;
-	auto_nl_addr struct nl_addr *gw = gateway ? _nl_addr_build (family, gateway, addrlen) : NULL;
-	auto_nl_addr struct nl_addr *pref_src_nl = pref_src ? _nl_addr_build (family, pref_src, addrlen) : NULL;
-
-	/* There seem to be problems adding a route with non-zero host identifier.
-	 * Adding IPv6 routes is simply ignored, without error message.
-	 * In the IPv4 case, we got an error. Thus, we have to make sure, that
-	 * the address is sane. */
-	clear_host_address (family, network, plen, network_clean);
-	dst = _nl_addr_build (family, network_clean, plen ? addrlen : 0);
-	nl_addr_set_prefixlen (dst, plen);
-
-	rtnlroute = _nl_rtnl_route_alloc ();
-	rtnl_route_set_table (rtnlroute, RT_TABLE_MAIN);
-	rtnl_route_set_tos (rtnlroute, 0);
-	rtnl_route_set_dst (rtnlroute, dst);
-	rtnl_route_set_priority (rtnlroute, metric);
-	rtnl_route_set_family (rtnlroute, family);
-	rtnl_route_set_protocol (rtnlroute, _nm_ip_config_source_to_rtprot (source));
-
-	nexthop = _nl_rtnl_route_nh_alloc ();
-	rtnl_route_nh_set_ifindex (nexthop, ifindex);
-	if (gw && !nl_addr_iszero (gw))
-		rtnl_route_nh_set_gateway (nexthop, gw);
-	if (pref_src_nl)
-		rtnl_route_set_pref_src (rtnlroute, pref_src_nl);
-	rtnl_route_add_nexthop (rtnlroute, nexthop);
-
-	if (mss > 0)
-		rtnl_route_set_metric (rtnlroute, RTAX_ADVMSS, mss);
-
-	return (struct nl_object *) rtnlroute;
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj;
-
-	return build_rtnl_route (AF_INET,
-	                         obj->ifindex,
-	                         obj->source,
-	                         &obj->network,
-	                         obj->plen,
-	                         &obj->gateway,
-	                         obj->pref_src ? &obj->pref_src : NULL,
-	                         obj->metric,
-	                         obj->mss);
-}
-
-struct nl_object *
-_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
-{
-	const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
-
-	return build_rtnl_route (AF_INET6,
-	                         obj->ifindex,
-	                         obj->source,
-	                         &obj->network,
-	                         obj->plen,
-	                         &obj->gateway,
-	                         NULL,
-	                         obj->metric,
-	                         obj->mss);
-}
-
 static gboolean
 ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                in_addr_t network, int plen, in_addr_t gateway,
-               guint32 pref_src, guint32 metric, guint32 mss)
+               in_addr_t pref_src, guint32 metric, guint32 mss)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	nlo = build_rtnl_route (AF_INET, ifindex, source, &network, plen, &gateway, pref_src ? &pref_src : NULL, metric, mss);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric),
-	                         nlo);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
+	                           NLM_F_CREATE | NLM_F_REPLACE,
+	                           AF_INET,
+	                           ifindex,
+	                           source,
+	                           gateway ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
+	                           &network,
+	                           plen,
+	                           &gateway,
+	                           metric,
+	                           mss,
+	                           pref_src ? &pref_src : NULL);
+
+	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
@@ -4406,30 +5337,34 @@ ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
                struct in6_addr network, int plen, struct in6_addr gateway,
                guint32 metric, guint32 mss)
 {
-	NMPObject obj_needle;
-	auto_nl_object struct nl_object *nlo = NULL;
+	NMPObject obj_id;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
 
-	metric = nm_utils_ip6_route_metric_normalize (metric);
+	nlmsg = _nl_msg_new_route (RTM_NEWROUTE,
+	                           NLM_F_CREATE | NLM_F_REPLACE,
+	                           AF_INET6,
+	                           ifindex,
+	                           source,
+	                           !IN6_IS_ADDR_UNSPECIFIED (&gateway) ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK,
+	                           &network,
+	                           plen,
+	                           &gateway,
+	                           metric,
+	                           mss,
+	                           NULL);
 
-	nlo = build_rtnl_route (AF_INET6, ifindex, source, &network, plen, &gateway, NULL, metric, mss);
-	return do_add_addrroute (platform,
-	                         nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric),
-	                         nlo);
+	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
+	return do_add_addrroute (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	in_addr_t gateway = 0;
-	auto_nl_object struct nl_object *nlo = build_rtnl_route (AF_INET, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN, &network, plen, &gateway, NULL, metric, 0);
-	uint8_t scope = RT_SCOPE_NOWHERE;
-	const NMPObject *obj;
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
 
-	g_return_val_if_fail (nlo, FALSE);
-
-	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
+	nmp_object_stackinit_id_ip4_route (&obj_id, ifindex, network, plen, metric);
 
 	if (metric == 0) {
 		/* Deleting an IPv4 route with metric 0 does not only delete an exectly matching route.
@@ -4439,94 +5374,81 @@ ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, int plen
 		 * 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);
-	}
-
-	obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
-
-	if (metric == 0 && !obj) {
-		/* hmm... we are about to delete an IP4 route with metric 0. We must only
-		 * send the delete request if such a route really exists. Above we refreshed
-		 * the platform cache, still no such route exists.
-		 *
-		 * Be extra careful and reload the routes. We must be sure that such a
-		 * route doesn't exists, because when we add an IPv4 address, we immediately
-		 * afterwards try to delete the kernel-added device route with metric 0.
-		 * It might be, that we didn't yet get the notification about that route.
-		 *
-		 * FIXME: once our ip4_address_add() is sure that upon return we have
-		 * the latest state from in the platform cache, we might save this
-		 * additional expensive cache-resync. */
-		do_request_one_type (platform, NMP_OBJECT_TYPE_IP4_ROUTE, TRUE);
-
-		obj = nmp_cache_lookup_obj (priv->cache, &obj_needle);
-		if (!obj)
-			return TRUE;
-	}
-
-	if (!_nl_has_capability (1 /* NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE */)) {
-		/* When searching for a matching IPv4 route to delete, the kernel
-		 * searches for a matching scope, unless the RTM_DELROUTE message
-		 * specifies RT_SCOPE_NOWHERE (see fib_table_delete()).
-		 *
-		 * However, if we set the scope of @rtnlroute to RT_SCOPE_NOWHERE (or
-		 * leave it unset), rtnl_route_build_msg() will reset the scope to
-		 * rtnl_route_guess_scope() -- which probably guesses wrong.
-		 *
-		 * As a workaround, we look at the cached route and use that scope.
-		 *
-		 * Newer versions of libnl, no longer reset the scope if explicitly set to RT_SCOPE_NOWHERE.
-		 * So, this workaround is only needed unless we have NL_CAPABILITY_ROUTE_BUILD_MSG_SET_SCOPE.
-		 **/
 
-		if (obj)
-			scope = nm_platform_route_scope_inv (obj->ip4_route.scope_inv);
+		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 (scope == RT_SCOPE_NOWHERE) {
-			/* If we would set the scope to RT_SCOPE_NOWHERE, libnl would guess the scope.
-			 * But probably it will guess 'link' because we set the next hop of the route
-			 * to zero (0.0.0.0). A better guess is 'global'. */
-			scope = RT_SCOPE_UNIVERSE;
+			if (!nmp_cache_lookup_obj (priv->cache, &obj_id))
+				return TRUE;
 		}
 	}
-	rtnl_route_set_scope ((struct rtnl_route *) nlo, scope);
-
-	/* we only support routes with TOS zero. As such, delete_route() is also only able to delete
-	 * routes with tos==0. build_rtnl_route() already initializes tos properly. */
 
-	/* The following fields are also relevant when comparing the route, but the default values
-	 * are already as we want them:
-	 *
-	 * type: RTN_UNICAST (setting to zero would ignore the type, but we only want to delete RTN_UNICAST)
-	 * pref_src: 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);
+	if (!nlmsg)
+		return FALSE;
 
-	return do_delete_object (platform, &obj_needle, nlo);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static gboolean
 ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	struct in6_addr gateway = IN6ADDR_ANY_INIT;
-	auto_nl_object struct nl_object *nlo = NULL;
-	NMPObject obj_needle;
+	nm_auto_nlmsg struct nl_msg *nlmsg = NULL;
+	NMPObject obj_id;
 
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	nlo = build_rtnl_route (AF_INET6, ifindex, NM_IP_CONFIG_SOURCE_UNKNOWN ,&network, plen, &gateway, NULL, metric, 0);
+	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);
+	if (!nlmsg)
+		return FALSE;
 
-	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
+	nmp_object_stackinit_id_ip6_route (&obj_id, ifindex, &network, plen, metric);
 
-	return do_delete_object (platform, &obj_needle, nlo);
+	return do_delete_object (platform, &obj_id, nlmsg);
 }
 
 static const NMPlatformIP4Route *
 ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, guint32 metric)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
-	nmp_object_stackinit_id_ip4_route (&obj_needle, ifindex, network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	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;
@@ -4535,13 +5457,13 @@ ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, int plen, g
 static const NMPlatformIP6Route *
 ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int plen, guint32 metric)
 {
-	NMPObject obj_needle;
+	NMPObject obj_id;
 	const NMPObject *obj;
 
 	metric = nm_utils_ip6_route_metric_normalize (metric);
 
-	nmp_object_stackinit_id_ip6_route (&obj_needle, ifindex, &network, plen, metric);
-	obj = nmp_cache_lookup_obj (NM_LINUX_PLATFORM_GET_PRIVATE (platform)->cache, &obj_needle);
+	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;
@@ -4553,22 +5475,6 @@ ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, int p
 #define ERROR_CONDITIONS      ((GIOCondition) (G_IO_ERR | G_IO_NVAL))
 #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP))
 
-static int
-verify_source (struct nl_msg *msg, gpointer user_data)
-{
-	struct ucred *creds = nlmsg_get_creds (msg);
-
-	if (!creds || creds->pid) {
-		if (creds)
-			warning ("netlink: received non-kernel message (pid %d)", creds->pid);
-		else
-			warning ("netlink: received message without credentials");
-		return NL_STOP;
-	}
-
-	return NL_OK;
-}
-
 static gboolean
 event_handler (GIOChannel *channel,
                GIOCondition io_condition,
@@ -4578,148 +5484,288 @@ event_handler (GIOChannel *channel,
 	return TRUE;
 }
 
-static gboolean
-event_handler_read_netlink_one (NMPlatform *platform)
+/*****************************************************************************/
+
+/* copied from libnl3's recvmsgs() */
+static int
+event_handler_recvmsgs (NMPlatform *platform, gboolean handle_events)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int nle;
+	struct nl_sock *sk = priv->nlh;
+	int n, err = 0, multipart = 0, interrupted = 0, nrecv = 0;
+	unsigned char *buf = NULL;
+	struct nlmsghdr *hdr;
+	WaitForNlResponseResult seq_result;
+
+	/*
+	nla is passed on to not only to nl_recv() but may also be passed
+	to a function pointer provided by the caller which may or may not
+	initialize the variable. Thomas Graf.
+	*/
+	struct sockaddr_nl nla = {0};
+	struct nl_msg *msg = NULL;
+	struct ucred *creds = NULL;
 
+continue_reading:
 	errno = 0;
-	nle = nl_recvmsgs_default (priv->nlh_event);
+	n = nl_recv (sk, &nla, &buf, &creds);
 
 	/* Work around a libnl bug fixed in 3.2.22 (375a6294) */
-	if (nle == 0 && errno == EAGAIN) {
+	if (n == 0 && errno == EAGAIN) {
 		/* EAGAIN is equal to EWOULDBLOCK. If it would not be, we'd have to
 		 * workaround libnl3 mapping EWOULDBLOCK to -NLE_FAILURE. */
 		G_STATIC_ASSERT (EAGAIN == EWOULDBLOCK);
-		nle = -NLE_AGAIN;
+		n = -NLE_AGAIN;
 	}
 
-	if (nle < 0)
-		switch (nle) {
-		case -NLE_AGAIN:
-			return FALSE;
-		case -NLE_DUMP_INTR:
-			debug ("Uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
-			break;
-		case -NLE_NOMEM:
-			info ("Too many netlink events. Need to resynchronize platform cache");
-			/* Drain the event queue, we've lost events and are out of sync anyway and we'd
-			 * like to free up some space. We'll read in the status synchronously. */
-			_nl_sock_flush_data (priv->nlh_event);
-			priv->nlh_seq_expect = 0;
-			delayed_action_schedule (platform,
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
-			                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
-			                         NULL);
-			break;
-		default:
-			error ("Failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
-			break;
+	if (n <= 0)
+		return n;
+
+	if (!handle_events) {
+		/* we read until failure or there is nothing to read (EAGAIN). */
+		goto continue_reading;
 	}
-	return TRUE;
+
+	hdr = (struct nlmsghdr *) buf;
+	while (nlmsg_ok (hdr, n)) {
+		gboolean abort_parsing = FALSE;
+
+		nlmsg_free (msg);
+		msg = nlmsg_convert (hdr);
+		if (!msg) {
+			err = -NLE_NOMEM;
+			goto out;
+		}
+
+		nlmsg_set_proto (msg, NETLINK_ROUTE);
+		nlmsg_set_src (msg, &nla);
+		nrecv++;
+
+		if (!creds || creds->pid) {
+			if (creds)
+				_LOGD ("netlink: recvmsg: received non-kernel message (pid %d)", creds->pid);
+			else
+				_LOGD ("netlink: recvmsg: received message without credentials");
+			goto stop;
+		}
+
+		_LOGt ("netlink: recvmsg: new message type %d, seq %u",
+		       hdr->nlmsg_type, hdr->nlmsg_seq);
+
+		if (creds)
+			nlmsg_set_creds (msg, creds);
+
+		if (hdr->nlmsg_flags & NLM_F_MULTI)
+			multipart = 1;
+
+		if (hdr->nlmsg_flags & NLM_F_DUMP_INTR) {
+			/*
+			 * We have to continue reading to clear
+			 * all messages until a NLMSG_DONE is
+			 * received and report the inconsistency.
+			 */
+			interrupted = 1;
+		}
+
+		/* Other side wishes to see an ack for this message */
+		if (hdr->nlmsg_flags & NLM_F_ACK) {
+			/* FIXME: implement */
+		}
+
+		seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_UNKNOWN;
+
+		if (hdr->nlmsg_type == NLMSG_DONE) {
+			/* messages terminates a multipart message, this is
+			 * usually the end of a message and therefore we slip
+			 * out of the loop by default. the user may overrule
+			 * this action by skipping this packet. */
+			multipart = 0;
+			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		} else if (hdr->nlmsg_type == NLMSG_NOOP) {
+			/* Message to be ignored, the default action is to
+			 * skip this message if no callback is specified. The
+			 * user may overrule this action by returning
+			 * NL_PROCEED. */
+		} else if (hdr->nlmsg_type == NLMSG_OVERRUN) {
+			/* Data got lost, report back to user. The default action is to
+			 * quit parsing. The user may overrule this action by retuning
+			 * NL_SKIP or NL_PROCEED (dangerous) */
+			err = -NLE_MSG_OVERFLOW;
+			abort_parsing = TRUE;
+		} else if (hdr->nlmsg_type == NLMSG_ERROR) {
+			/* Message carries a nlmsgerr */
+			struct nlmsgerr *e = nlmsg_data (hdr);
+
+			if (hdr->nlmsg_len < nlmsg_size (sizeof (*e))) {
+				/* Truncated error message, the default action
+				 * is to stop parsing. The user may overrule
+				 * this action by returning NL_SKIP or
+				 * NL_PROCEED (dangerous) */
+				err = -NLE_MSG_TRUNC;
+				abort_parsing = TRUE;
+			} else if (e->error) {
+				int errsv = e->error > 0 ? e->error : -e->error;
+
+				/* Error message reported back from kernel. */
+				_LOGD ("netlink: recvmsg: error message from kernel: %s (%d) for request %d",
+				       strerror (errsv),
+				       errsv,
+				       nlmsg_hdr (msg)->nlmsg_seq);
+				seq_result = -errsv;
+			} else
+				seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		} else {
+			/* Valid message (not checking for MULTIPART bit to
+			 * get along with broken kernels. NL_SKIP has no
+			 * effect on this.  */
+			event_valid_msg (platform, msg);
+			seq_result = WAIT_FOR_NL_RESPONSE_RESULT_RESPONSE_OK;
+		}
+
+		event_seq_check (platform, msg, seq_result);
+		err = 0;
+		hdr = nlmsg_next (hdr, &n);
+
+		if (abort_parsing)
+			goto out;
+	}
+
+	nlmsg_free (msg);
+	free (buf);
+	free (creds);
+	buf = NULL;
+	msg = NULL;
+	creds = NULL;
+
+	if (multipart) {
+		/* Multipart message not yet complete, continue reading */
+		goto continue_reading;
+	}
+stop:
+	err = 0;
+out:
+	nlmsg_free (msg);
+	free (buf);
+	free (creds);
+
+	if (interrupted)
+		err = -NLE_DUMP_INTR;
+
+	if (!err)
+		err = nrecv;
+
+	return err;
 }
 
+/*****************************************************************************/
+
 static gboolean
-event_handler_read_netlink_all (NMPlatform *platform, gboolean wait_for_acks)
+event_handler_read_netlink (NMPlatform *platform, gboolean wait_for_acks)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	int r;
+	int r, nle;
 	struct pollfd pfd;
 	gboolean any = FALSE;
-	gint64 timestamp = 0, now;
-	const int TIMEOUT = 250;
-	int timeout = 0;
-	guint32 wait_for_seq = 0;
+	gint64 now_ns;
+	int timeout_ms;
+	guint i;
+	struct {
+		guint32 seq_number;
+		gint64 timeout_abs_ns;
+	} data_next;
 
 	while (TRUE) {
-		while (event_handler_read_netlink_one (platform))
+
+		while (TRUE) {
+
+			nle = event_handler_recvmsgs (platform, TRUE);
+
+			if (nle < 0)
+				switch (nle) {
+				case -NLE_AGAIN:
+					goto after_read;
+				case -NLE_DUMP_INTR:
+					_LOGD ("netlink: read: uncritical failure to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					break;
+				case -NLE_NOMEM:
+					_LOGI ("netlink: read: too many netlink events. Need to resynchronize platform cache");
+					/* Drain the event queue, we've lost events and are out of sync anyway and we'd
+					 * like to free up some space. We'll read in the status synchronously. */
+					delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_RESYNC);
+					event_handler_recvmsgs (platform, FALSE);
+					delayed_action_schedule (platform,
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_LINKS |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ADDRESSES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ADDRESSES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES |
+					                         DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES,
+					                         NULL);
+					break;
+				default:
+					_LOGE ("netlink: read: failed to retrieve incoming events: %s (%d)", nl_geterror (nle), nle);
+					break;
+			}
 			any = TRUE;
+		}
 
-		if (!wait_for_acks || priv->nlh_seq_expect == 0) {
-			if (wait_for_seq)
-				_LOGt ("read-netlink-all: ACK for sequence number %u received", priv->nlh_seq_expect);
+after_read:
+
+		if (!NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
 			return any;
-		}
 
-		now = nm_utils_get_monotonic_timestamp_ms ();
-		if (wait_for_seq != priv->nlh_seq_expect) {
-			/* We are waiting for a new sequence number (or we will wait for the first time).
-			 * Reset/start counting the overall wait time. */
-			_LOGt ("read-netlink-all: wait for ACK for sequence number %u...", priv->nlh_seq_expect);
-			wait_for_seq = priv->nlh_seq_expect;
-			timestamp = now;
-			timeout = TIMEOUT;
-		} else {
-			if ((now - timestamp) >= TIMEOUT) {
-				/* timeout. Don't wait for this sequence number anymore. */
-				break;
-			}
+		now_ns = 0;
+		data_next.seq_number = 0;
+		data_next.timeout_abs_ns = 0;
+
+		for (i = 0; i < priv->delayed_action.list_wait_for_nl_response->len; ) {
+			DelayedActionWaitForNlResponseData *data = &g_array_index (priv->delayed_action.list_wait_for_nl_response, DelayedActionWaitForNlResponseData, i);
+
+			if (data->seq_result)
+				delayed_action_wait_for_nl_response_complete (platform, i, data->seq_result);
+			else if ((now_ns ?: (now_ns = nm_utils_get_monotonic_timestamp_ns ())) > data->timeout_abs_ns)
+				delayed_action_wait_for_nl_response_complete (platform, i, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_TIMEOUT);
+			else {
+				i++;
 
-			/* readjust the wait-time. */
-			timeout = TIMEOUT - (now - timestamp);
+				if (   data_next.seq_number == 0
+				    || data_next.timeout_abs_ns > data->timeout_abs_ns)
+					data_next.seq_number = data->seq_number;
+					data_next.timeout_abs_ns = data->timeout_abs_ns;
+			}
 		}
 
+		if (   !wait_for_acks
+		    || !NM_FLAGS_HAS (priv->delayed_action.flags, DELAYED_ACTION_TYPE_WAIT_FOR_NL_RESPONSE))
+			return any;
+
+		nm_assert (data_next.seq_number);
+		nm_assert (data_next.timeout_abs_ns > 0);
+		nm_assert (now_ns > 0);
+
+		_LOGT ("netlink: read: wait for ACK for sequence number %u...", data_next.seq_number);
+
+		timeout_ms = (data_next.timeout_abs_ns - now_ns) / (NM_UTILS_NS_PER_SECOND / 1000);
+
 		memset (&pfd, 0, sizeof (pfd));
-		pfd.fd = nl_socket_get_fd (priv->nlh_event);
+		pfd.fd = nl_socket_get_fd (priv->nlh);
 		pfd.events = POLLIN;
-		r = poll (&pfd, 1, timeout);
+		r = poll (&pfd, 1, MAX (1, timeout_ms));
 
 		if (r == 0) {
-			/* timeout. */
-			break;
+			/* timeout and there is nothing to read. */
+			goto after_read;
 		}
 		if (r < 0) {
 			int errsv = errno;
 
 			if (errsv != EINTR) {
-				_LOGE ("read-netlink-all: poll failed with %s", strerror (errsv));
+				_LOGE ("netlink: read: poll failed with %s", strerror (errsv));
+				delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_POLL);
 				return any;
 			}
 			/* Continue to read again, even if there might be nothing to read after EINTR. */
 		}
 	}
-
-	_LOGW ("read-netlink-all: timeout waiting for ACK to sequence number %u...", wait_for_seq);
-	priv->nlh_seq_expect = 0;
-	return any;
-}
-
-static struct nl_sock *
-setup_socket (gboolean event, gpointer user_data)
-{
-	struct nl_sock *sock;
-	int nle;
-
-	sock = nl_socket_alloc ();
-	g_return_val_if_fail (sock, NULL);
-
-	/* Only ever accept messages from kernel */
-	nle = nl_socket_modify_cb (sock, NL_CB_MSG_IN, NL_CB_CUSTOM, verify_source, user_data);
-	g_assert (!nle);
-
-	/* Dispatch event messages (event socket only) */
-	if (event) {
-		nl_socket_modify_cb (sock, NL_CB_VALID, NL_CB_CUSTOM, event_notification, user_data);
-		nl_socket_modify_cb (sock, NL_CB_SEQ_CHECK, NL_CB_CUSTOM, event_seq_check, user_data);
-		nl_socket_modify_err_cb (sock, NL_CB_CUSTOM, event_err, user_data);
-	}
-
-	nle = nl_connect (sock, NETLINK_ROUTE);
-	g_assert (!nle);
-	nle = nl_socket_set_passcred (sock, 1);
-	g_assert (!nle);
-
-	/* No blocking for event socket, so that we can drain it safely. */
-	if (event) {
-		nle = nl_socket_set_nonblocking (sock);
-		g_assert (!nle);
-	}
-
-	return sock;
 }
 
 /******************************************************************/
@@ -4728,12 +5774,12 @@ static void
 cache_update_link_udev (NMPlatform *platform, int ifindex, GUdevDevice *udev_device)
 {
 	NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform);
-	auto_nmp_obj NMPObject *obj_cache = NULL;
+	nm_auto_nmpobj NMPObject *obj_cache = NULL;
 	gboolean was_visible;
 	NMPCacheOpsType cache_op;
 
 	cache_op = nmp_cache_update_link_udev (priv->cache, ifindex, udev_device, &obj_cache, &was_visible, cache_pre_hook, platform);
-	do_emit_signal (platform, obj_cache, cache_op, was_visible, NM_PLATFORM_REASON_INTERNAL);
+	do_emit_signal (platform, obj_cache, cache_op, was_visible);
 }
 
 static void
@@ -4745,23 +5791,23 @@ udev_device_added (NMPlatform *platform,
 
 	ifname = g_udev_device_get_name (udev_device);
 	if (!ifname) {
-		debug ("udev-add: failed to get device's interface");
+		_LOGD ("udev-add: failed to get device's interface");
 		return;
 	}
 
 	if (g_udev_device_get_property (udev_device, "IFINDEX"))
 		ifindex = g_udev_device_get_property_as_int (udev_device, "IFINDEX");
 	else {
-		warning ("(%s): udev-add: failed to get device's ifindex", ifname);
+		_LOGW ("(%s): udev-add: failed to get device's ifindex", ifname);
 		return;
 	}
 	if (ifindex <= 0) {
-		warning ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex);
+		_LOGW ("(%s): udev-add: retrieved invalid IFINDEX=%d", ifname, ifindex);
 		return;
 	}
 
 	if (!g_udev_device_get_sysfs_path (udev_device)) {
-		debug ("(%s): udev-add: couldn't determine device path; ignoring...", ifname);
+		_LOGD ("(%s): udev-add: couldn't determine device path; ignoring...", ifname);
 		return;
 	}
 
@@ -4791,7 +5837,7 @@ udev_device_removed (NMPlatform *platform,
 			ifindex = obj->link.ifindex;
 	}
 
-	debug ("udev-remove: IFINDEX=%d", ifindex);
+	_LOGD ("udev-remove: IFINDEX=%d", ifindex);
 	if (ifindex <= 0)
 		return;
 
@@ -4817,9 +5863,9 @@ handle_udev_event (GUdevClient *client,
 
 	ifindex = g_udev_device_get_property (udev_device, "IFINDEX");
 	seqnum = g_udev_device_get_seqnum (udev_device);
-	debug ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT,
-	       action, subsys, g_udev_device_get_name (udev_device),
-	       ifindex ? ifindex : "unknown", seqnum);
+	_LOGD ("UDEV event: action '%s' subsys '%s' device '%s' (%s); seqnum=%" G_GUINT64_FORMAT,
+	        action, subsys, g_udev_device_get_name (udev_device),
+	        ifindex ? ifindex : "unknown", seqnum);
 
 	if (!strcmp (action, "add") || !strcmp (action, "move"))
 		udev_device_added (platform, udev_device);
@@ -4836,9 +5882,11 @@ nm_linux_platform_init (NMLinuxPlatform *self)
 
 	self->priv = priv;
 
+	priv->nlh_seq_next = 1;
 	priv->cache = nmp_cache_new ();
 	priv->delayed_action.list_master_connected = g_ptr_array_new ();
 	priv->delayed_action.list_refresh_link = g_ptr_array_new ();
+	priv->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);
 }
 
@@ -4856,29 +5904,37 @@ constructed (GObject *_object)
 
 	_LOGD ("create");
 
-	/* Initialize netlink socket for requests */
-	priv->nlh = setup_socket (FALSE, platform);
+	priv->nlh = nl_socket_alloc ();
 	g_assert (priv->nlh);
-	debug ("Netlink socket for requests established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	/* Initialize netlink socket for events */
-	priv->nlh_event = setup_socket (TRUE, platform);
-	g_assert (priv->nlh_event);
+	nle = nl_connect (priv->nlh, NETLINK_ROUTE);
+	g_assert (!nle);
+	nle = nl_socket_set_passcred (priv->nlh, 1);
+	g_assert (!nle);
+
+	/* No blocking for event socket, so that we can drain it safely. */
+	nle = nl_socket_set_nonblocking (priv->nlh);
+	g_assert (!nle);
+
 	/* The default buffer size wasn't enough for the testsuites. It might just
 	 * as well happen with NetworkManager itself. For now let's hope 128KB is
 	 * good enough.
+	 *
+	 * FIXME: it's unclear that this is still actually needed. The testsuite
+	 * certainly doesn't fail for me. Maybe it can be removed.
 	 */
-	nle = nl_socket_set_buffer_size (priv->nlh_event, 131072, 0);
+	nle = nl_socket_set_buffer_size (priv->nlh, 131072, 0);
 	g_assert (!nle);
-	nle = nl_socket_add_memberships (priv->nlh_event,
+
+	nle = nl_socket_add_memberships (priv->nlh,
 	                                 RTNLGRP_LINK,
 	                                 RTNLGRP_IPV4_IFADDR, RTNLGRP_IPV6_IFADDR,
 	                                 RTNLGRP_IPV4_ROUTE,  RTNLGRP_IPV6_ROUTE,
 	                                 0);
 	g_assert (!nle);
-	debug ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh_event), nl_socket_get_fd (priv->nlh_event));
+	_LOGD ("Netlink socket for events established: port=%u, fd=%d", nl_socket_get_local_port (priv->nlh), nl_socket_get_fd (priv->nlh));
 
-	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh_event));
+	priv->event_channel = g_io_channel_unix_new (nl_socket_get_fd (priv->nlh));
 	g_io_channel_set_encoding (priv->event_channel, NULL, NULL);
 	g_io_channel_set_close_on_unref (priv->event_channel, TRUE);
 
@@ -4931,12 +5987,12 @@ dispose (GObject *object)
 
 	_LOGD ("dispose");
 
+	delayed_action_wait_for_nl_response_complete_all (platform, WAIT_FOR_NL_RESPONSE_RESULT_FAILED_DISPOSING);
+
 	priv->delayed_action.flags = DELAYED_ACTION_TYPE_NONE;
 	g_ptr_array_set_size (priv->delayed_action.list_master_connected, 0);
 	g_ptr_array_set_size (priv->delayed_action.list_refresh_link, 0);
 
-	nm_clear_g_source (&priv->delayed_action.idle_id);
-
 	g_clear_pointer (&priv->prune_candidates, g_hash_table_unref);
 
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->dispose (object);
@@ -4951,16 +6007,21 @@ nm_linux_platform_finalize (GObject *object)
 
 	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);
 
 	/* Free netlink resources */
 	g_source_remove (priv->event_id);
 	g_io_channel_unref (priv->event_channel);
 	nl_socket_free (priv->nlh);
-	nl_socket_free (priv->nlh_event);
 
 	g_object_unref (priv->udev_client);
 	g_hash_table_unref (priv->wifi_data);
 
+	if (priv->sysctl_get_prev_values) {
+		sysctl_clear_cache_list = g_slist_remove (sysctl_clear_cache_list, object);
+		g_hash_table_destroy (priv->sysctl_get_prev_values);
+	}
+
 	G_OBJECT_CLASS (nm_linux_platform_parent_class)->finalize (object);
 }
 
@@ -4991,6 +6052,8 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	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;
 
 	platform_class->link_set_up = link_set_up;
@@ -5017,33 +6080,23 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 
 	platform_class->link_enslave = link_enslave;
 	platform_class->link_release = link_release;
-	platform_class->master_set_option = master_set_option;
-	platform_class->master_get_option = master_get_option;
-	platform_class->slave_set_option = slave_set_option;
-	platform_class->slave_get_option = slave_get_option;
 
 	platform_class->vlan_add = vlan_add;
-	platform_class->vlan_get_info = vlan_get_info;
-	platform_class->vlan_set_ingress_map = vlan_set_ingress_map;
-	platform_class->vlan_set_egress_map = vlan_set_egress_map;
+	platform_class->link_vlan_change = link_vlan_change;
+	platform_class->link_vxlan_add = link_vxlan_add;
 
-	platform_class->infiniband_partition_add = infiniband_partition_add;
-	platform_class->infiniband_get_info = infiniband_get_info;
+	platform_class->tun_add = tun_add;
 
-	platform_class->veth_get_properties = veth_get_properties;
-	platform_class->tun_get_properties = tun_get_properties;
-	platform_class->macvlan_get_properties = macvlan_get_properties;
-	platform_class->vxlan_get_properties = vxlan_get_properties;
-	platform_class->gre_get_properties = gre_get_properties;
+	platform_class->infiniband_partition_add = infiniband_partition_add;
 
 	platform_class->wifi_get_capabilities = wifi_get_capabilities;
 	platform_class->wifi_get_bssid = wifi_get_bssid;
-	platform_class->wifi_get_ssid = wifi_get_ssid;
 	platform_class->wifi_get_frequency = wifi_get_frequency;
 	platform_class->wifi_get_quality = wifi_get_quality;
 	platform_class->wifi_get_rate = wifi_get_rate;
 	platform_class->wifi_get_mode = wifi_get_mode;
 	platform_class->wifi_set_mode = wifi_set_mode;
+	platform_class->wifi_set_powersave = wifi_set_powersave;
 	platform_class->wifi_find_frequency = wifi_find_frequency;
 	platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running;
 
@@ -5051,6 +6104,12 @@ nm_linux_platform_class_init (NMLinuxPlatformClass *klass)
 	platform_class->mesh_set_channel = mesh_set_channel;
 	platform_class->mesh_set_ssid = mesh_set_ssid;
 
+	platform_class->link_gre_add = link_gre_add;
+	platform_class->link_ip6tnl_add = link_ip6tnl_add;
+	platform_class->link_macvlan_add = link_macvlan_add;
+	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;