about summary refs log tree commit diff
path: root/src/platform/nmp-object.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/platform/nmp-object.c')
-rw-r--r--src/platform/nmp-object.c663
1 files changed, 450 insertions, 213 deletions
diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c
index eb6bfc3a..892fd9dd 100644
--- a/src/platform/nmp-object.c
+++ b/src/platform/nmp-object.c
@@ -22,11 +22,11 @@
 
 #include <unistd.h>
 
+#include "nm-default.h"
 #include "nmp-object.h"
 #include "nm-platform-utils.h"
 #include "NetworkManagerUtils.h"
 #include "nm-utils.h"
-#include "nm-logging.h"
 
 /*********************************************************************************************/
 
@@ -82,6 +82,42 @@ _id_hash_ip6_addr (const struct in6_addr *addr)
 	return hash;
 }
 
+static int
+_vlan_xgress_qos_mappings_cmp (guint n_map,
+                               const NMVlanQosMapping *map1,
+                               const NMVlanQosMapping *map2)
+{
+	guint i;
+
+	for (i = 0; i < n_map; i++) {
+		if (map1[i].from != map2[i].from)
+			return map1[i].from < map2[i].from ? -1 : 1;
+		if (map1[i].to != map2[i].to)
+			return map1[i].to < map2[i].to ? -1 : 1;
+	}
+	return 0;
+}
+
+static void
+_vlan_xgress_qos_mappings_cpy (guint *dst_n_map,
+                               const NMVlanQosMapping **dst_map,
+                               guint src_n_map,
+                               const NMVlanQosMapping *src_map)
+{
+	if (src_n_map == 0) {
+		g_clear_pointer (dst_map, g_free);
+		*dst_n_map = 0;
+	} else if (   src_n_map != *dst_n_map
+	           || _vlan_xgress_qos_mappings_cmp (src_n_map, *dst_map, src_map) != 0) {
+		g_clear_pointer (dst_map, g_free);
+		*dst_n_map = src_n_map;
+		if (src_n_map > 0)
+			*dst_map = g_memdup (src_map, sizeof (*src_map) * src_n_map);
+	}
+}
+
+/******************************************************************/
+
 static const char *
 _link_get_driver (GUdevDevice *udev_device, const char *kind, const char *ifname)
 {
@@ -207,6 +243,14 @@ static void
 _vt_cmd_obj_dispose_link (NMPObject *obj)
 {
 	g_clear_object (&obj->_link.udev.device);
+	nmp_object_unref (obj->_link.netlink.lnk);
+}
+
+static void
+_vt_cmd_obj_dispose_lnk_vlan (NMPObject *obj)
+{
+	g_free ((gpointer) obj->_lnk_vlan.ingress_qos_map);
+	g_free ((gpointer) obj->_lnk_vlan.egress_qos_map);
 }
 
 static NMPObject *
@@ -274,10 +318,16 @@ nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, const NMPlatformOb
 const NMPObject *
 nmp_object_stackinit_id  (NMPObject *obj, const NMPObject *src)
 {
+	const NMPClass *klass;
+
 	nm_assert (NMP_OBJECT_IS_VALID (src));
 	nm_assert (obj);
 
-	NMP_OBJECT_GET_CLASS (src)->cmd_obj_stackinit_id (obj, src);
+	klass = NMP_OBJECT_GET_CLASS (src);
+	if (!klass->cmd_obj_stackinit_id)
+		nmp_object_stackinit (obj, klass->obj_type, NULL);
+	else
+		klass->cmd_obj_stackinit_id (obj, src);
 	return obj;
 }
 
@@ -296,19 +346,20 @@ _vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src)
 }
 
 const NMPObject *
-nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen)
+nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen, guint32 peer_address)
 {
 	nmp_object_stackinit (obj, NMP_OBJECT_TYPE_IP4_ADDRESS, NULL);
 	obj->ip4_address.ifindex = ifindex;
 	obj->ip4_address.address = address;
 	obj->ip4_address.plen = plen;
+	obj->ip4_address.peer_address = peer_address;
 	return obj;
 }
 
 static void
 _vt_cmd_obj_stackinit_id_ip4_address (NMPObject *obj, const NMPObject *src)
 {
-	nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen);
+	nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen, src->ip4_address.peer_address);
 }
 
 const NMPObject *
@@ -369,50 +420,131 @@ const char *
 nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
 {
 	const NMPClass *klass;
-	char buf2[sizeof (_nm_platform_to_string_buffer)];
-	char buf3[sizeof (_nm_platform_to_string_buffer)];
-	const char *str;
-
-	if (!buf) {
-		buf = _nm_platform_to_string_buffer;
-		buf_size = sizeof (_nm_platform_to_string_buffer);
-	}
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
 
-	if (!obj) {
-		g_strlcpy (buf, "NULL", buf_size);
+	if (!nm_utils_to_string_buffer_init_null (obj, &buf, &buf_size))
 		return buf;
-	}
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL);
 
 	klass = NMP_OBJECT_GET_CLASS (obj);
 
+	if (klass->cmd_obj_to_string)
+		return klass->cmd_obj_to_string (obj, to_string_mode, buf, buf_size);
+
 	switch (to_string_mode) {
 	case NMP_OBJECT_TO_STRING_ID:
+		if (!klass->cmd_plobj_to_string_id) {
+			g_snprintf (buf, buf_size, "%p", obj);
+			return buf;
+		}
 		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
 	case NMP_OBJECT_TO_STRING_ALL:
-		g_strlcpy (buf2, NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object), sizeof (buf2));
+		g_snprintf (buf, buf_size,
+		            "[%s,%p,%d,%ccache,%calive,%cvisible; %s]",
+		            klass->obj_type_name, obj, obj->_ref_count,
+		            obj->is_cached ? '+' : '-',
+		            nmp_object_is_alive (obj) ? '+' : '-',
+		            nmp_object_is_visible (obj) ? '+' : '-',
+		            NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2)));
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
 
-		if (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK) {
-			g_snprintf (buf3, sizeof (buf3),
-			            ",%cin-nl,%p",
-			            obj->_link.netlink.is_in_netlink ? '+' : '-',
-			            obj->_link.udev.device);
+static const char *
+_vt_cmd_obj_to_string_link (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char buf3[sizeof (_nm_utils_to_string_buffer)];
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size);
+	case NMP_OBJECT_TO_STRING_ALL:
+		g_snprintf (buf, buf_size,
+		            "[%s,%p,%d,%ccache,%calive,%cvisible,%cin-nl,%p; %s]",
+		            klass->obj_type_name, obj, obj->_ref_count,
+		            obj->is_cached ? '+' : '-',
+		            nmp_object_is_alive (obj) ? '+' : '-',
+		            nmp_object_is_visible (obj) ? '+' : '-',
+		            obj->_link.netlink.is_in_netlink ? '+' : '-',
+		            obj->_link.udev.device,
+		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
+		return buf;
+	case NMP_OBJECT_TO_STRING_PUBLIC:
+		if (obj->_link.netlink.lnk) {
+			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf2, sizeof (buf2));
+			nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, buf3, sizeof (buf3));
+			g_snprintf (buf, buf_size,
+			            "%s; %s",
+			            buf2, buf3);
 		} else
-			buf3[0] = '\0';
+			NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+		return buf;
+	default:
+		g_return_val_if_reached ("ERROR");
+	}
+}
+
+static const char *
+_vt_cmd_obj_to_string_lnk_vlan (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size)
+{
+	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj);
+	char buf2[sizeof (_nm_utils_to_string_buffer)];
+	char *b;
+	gsize l;
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	switch (to_string_mode) {
+	case NMP_OBJECT_TO_STRING_ID:
+		g_snprintf (buf, buf_size, "%p", obj);
+		return buf;
+	case NMP_OBJECT_TO_STRING_ALL:
 
 		g_snprintf (buf, buf_size,
-		            "[%s,%p,%d,%ccache,%calive,%cvisible%s; %s]",
+		            "[%s,%p,%d,%ccache,%calive,%cvisible; %s]",
 		            klass->obj_type_name, obj, obj->_ref_count,
 		            obj->is_cached ? '+' : '-',
 		            nmp_object_is_alive (obj) ? '+' : '-',
 		            nmp_object_is_visible (obj) ? '+' : '-',
-		            buf3, buf2);
+		            nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof (buf2)));
 		return buf;
 	case NMP_OBJECT_TO_STRING_PUBLIC:
-		str = NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object);
-		if (str != buf)
-			g_strlcpy (buf, str, buf_size);
+		NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, buf, buf_size);
+
+		b = buf;
+		l = strlen (b);
+		b += l;
+		buf_size -= l;
+
+		if (obj->_lnk_vlan.n_ingress_qos_map) {
+			nm_platform_vlan_qos_mapping_to_string (" ingress-qos-map",
+			                                        obj->_lnk_vlan.ingress_qos_map,
+			                                        obj->_lnk_vlan.n_ingress_qos_map,
+			                                        b,
+			                                        buf_size);
+			l = strlen (b);
+			b += l;
+			buf_size -= l;
+		}
+		if (obj->_lnk_vlan.n_egress_qos_map) {
+			nm_platform_vlan_qos_mapping_to_string (" egress-qos-map",
+			                                        obj->_lnk_vlan.egress_qos_map,
+			                                        obj->_lnk_vlan.n_egress_qos_map,
+			                                        b,
+			                                        buf_size);
+			l = strlen (b);
+			b += l;
+			buf_size -= l;
+		}
+
 		return buf;
 	default:
 		g_return_val_if_reached ("ERROR");
@@ -425,21 +557,27 @@ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, gsiz
 { \
 	plat_type *const obj = (plat_type *) _obj; \
 	char buf1[NM_UTILS_INET_ADDRSTRLEN]; \
+	char buf2[NM_UTILS_INET_ADDRSTRLEN]; \
 	\
 	(void) buf1; \
+	(void) buf2; \
 	g_snprintf (buf, buf_len, \
 	            __VA_ARGS__); \
 	return buf; \
 }
-_vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",           obj->ifindex);
-_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d",    obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen);
-_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s/%d",    obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1), obj->plen);
-_vt_cmd_plobj_to_string_id (ip4_route,   NMPlatformIP4Route,   "%d: %s/%d %d", obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
-_vt_cmd_plobj_to_string_id (ip6_route,   NMPlatformIP6Route,   "%d: %s/%d %d", obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
+_vt_cmd_plobj_to_string_id (link,        NMPlatformLink,       "%d",            obj->ifindex);
+_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d%s%s", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen,
+                                                               obj->peer_address != obj->address ? "," : "",
+                                                               obj->peer_address != obj->address ? nm_utils_inet4_ntop (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen), buf2) : "");
+_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s",        obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1));
+_vt_cmd_plobj_to_string_id (ip4_route,   NMPlatformIP4Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric);
+_vt_cmd_plobj_to_string_id (ip6_route,   NMPlatformIP6Route,   "%d: %s/%d %d",  obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric);
 
 int
 nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 {
+	const NMPClass *klass1, *klass2;
+
 	if (obj1 == obj2)
 		return 0;
 	if (!obj1)
@@ -450,49 +588,72 @@ nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2)
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1);
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1);
 
-	if (NMP_OBJECT_GET_CLASS (obj1) != NMP_OBJECT_GET_CLASS (obj2))
-		return NMP_OBJECT_GET_CLASS (obj1) < NMP_OBJECT_GET_CLASS (obj2) ? -1 : 1;
+	klass1 = NMP_OBJECT_GET_CLASS (obj1);
+	klass2 = NMP_OBJECT_GET_CLASS (obj2);
+
+	if (klass1 != klass2)
+		return klass1->obj_type < klass2->obj_type ? -1 : 1;
 
-	return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object);
+	if (klass1->cmd_obj_cmp)
+		return klass1->cmd_obj_cmp (obj1, obj2);
+	return klass1->cmd_plobj_cmp (&obj1->object, &obj2->object);
 }
 
-gboolean
-nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
+static int
+_vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2)
 {
-	const NMPClass *klass;
+	int i;
 
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE);
-	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE);
+	i = nm_platform_link_cmp (&obj1->link, &obj2->link);
+	if (i)
+		return i;
+	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
+		return obj1->_link.netlink.is_in_netlink ? -1 : 1;
+	i = nmp_object_cmp (obj1->_link.netlink.lnk, obj2->_link.netlink.lnk);
+	if (i)
+		return i;
+	if (obj1->_link.udev.device != obj2->_link.udev.device) {
+		if (!obj1->_link.udev.device)
+			return -1;
+		if (!obj2->_link.udev.device)
+			return 1;
+
+		/* Only compare based on pointer values. That is ugly because it's not a
+		 * stable sort order, but probably udev gives us always the same GUdevDevice
+		 * instance.
+		 *
+		 * Have this check as very last. */
+		return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1;
+	}
+	return 0;
+}
 
-	if (obj1 == obj2)
-		return TRUE;
+static int
+_vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2)
+{
+	int c;
 
-	klass = NMP_OBJECT_GET_CLASS (obj1);
+	c = nm_platform_lnk_vlan_cmp (&obj1->lnk_vlan, &obj2->lnk_vlan);
+	if (c)
+		return c;
 
-	if (klass != NMP_OBJECT_GET_CLASS (obj2))
-		return FALSE;
+	if (obj1->_lnk_vlan.n_ingress_qos_map != obj2->_lnk_vlan.n_ingress_qos_map)
+		return obj1->_lnk_vlan.n_ingress_qos_map < obj2->_lnk_vlan.n_ingress_qos_map ? -1 : 1;
+	if (obj1->_lnk_vlan.n_egress_qos_map != obj2->_lnk_vlan.n_egress_qos_map)
+		return obj1->_lnk_vlan.n_egress_qos_map < obj2->_lnk_vlan.n_egress_qos_map ? -1 : 1;
 
-	return klass->cmd_obj_equal (obj1, obj2);
-}
+	c = _vlan_xgress_qos_mappings_cmp (obj1->_lnk_vlan.n_ingress_qos_map, obj1->_lnk_vlan.ingress_qos_map, obj2->_lnk_vlan.ingress_qos_map);
+	if (c)
+		return c;
+	c = _vlan_xgress_qos_mappings_cmp (obj1->_lnk_vlan.n_egress_qos_map, obj1->_lnk_vlan.egress_qos_map, obj2->_lnk_vlan.egress_qos_map);
 
-static gboolean
-_vt_cmd_obj_equal_plain (const NMPObject *obj1, const NMPObject *obj2)
-{
-	return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object) == 0;
+	return c;
 }
 
-static gboolean
-_vt_cmd_obj_equal_link (const NMPObject *obj1, const NMPObject *obj2)
+gboolean
+nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2)
 {
-	const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj1);
-
-	if (klass->cmd_plobj_cmp (&obj1->object, &obj2->object) != 0)
-		return FALSE;
-	if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink)
-		return FALSE;
-	if (obj1->_link.udev.device != obj2->_link.udev.device)
-		return FALSE;
-	return TRUE;
+	return nmp_object_cmp (obj1, obj2) == 0;
 }
 
 /* @src is a const object, which is not entirely correct for link types, where
@@ -511,11 +672,46 @@ nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only)
 
 		g_return_if_fail (klass == NMP_OBJECT_GET_CLASS (src));
 
-		if (id_only)
-			klass->cmd_plobj_id_copy (&dst->object, &src->object);
-		else
+		if (id_only) {
+			if (klass->cmd_plobj_id_copy)
+				klass->cmd_plobj_id_copy (&dst->object, &src->object);
+		} else if (klass->cmd_obj_copy)
 			klass->cmd_obj_copy (dst, src);
+		else
+			memcpy (&dst->object, &src->object, klass->sizeof_data);
+	}
+}
+
+static void
+_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
+{
+	if (dst->_link.udev.device != src->_link.udev.device) {
+		if (src->_link.udev.device)
+			g_object_ref (src->_link.udev.device);
+		if (dst->_link.udev.device)
+			g_object_unref (dst->_link.udev.device);
 	}
+	if (dst->_link.netlink.lnk != src->_link.netlink.lnk) {
+		if (src->_link.netlink.lnk)
+			nmp_object_ref (src->_link.netlink.lnk);
+		if (dst->_link.netlink.lnk)
+			nmp_object_unref (dst->_link.netlink.lnk);
+	}
+	dst->_link = src->_link;
+}
+
+static void
+_vt_cmd_obj_copy_lnk_vlan (NMPObject *dst, const NMPObject *src)
+{
+	dst->lnk_vlan = src->lnk_vlan;
+	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_ingress_qos_map,
+	                               &dst->_lnk_vlan.ingress_qos_map,
+	                               src->_lnk_vlan.n_ingress_qos_map,
+	                               src->_lnk_vlan.ingress_qos_map);
+	_vlan_xgress_qos_mappings_cpy (&dst->_lnk_vlan.n_egress_qos_map,
+	                               &dst->_lnk_vlan.egress_qos_map,
+	                               src->_lnk_vlan.n_egress_qos_map,
+	                               src->_lnk_vlan.egress_qos_map);
 }
 
 #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \
@@ -533,10 +729,10 @@ _vt_cmd_plobj_id_copy (ip4_address, NMPlatformIP4Address, {
 	dst->ifindex = src->ifindex;
 	dst->plen = src->plen;
 	dst->address = src->address;
+	dst->peer_address = src->peer_address;
 });
 _vt_cmd_plobj_id_copy (ip6_address, NMPlatformIP6Address, {
 	dst->ifindex = src->ifindex;
-	dst->plen = src->plen;
 	dst->address = src->address;
 });
 _vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, {
@@ -552,24 +748,6 @@ _vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, {
 	dst->network = src->network;
 });
 
-static void
-_vt_cmd_obj_copy_plain (NMPObject *dst, const NMPObject *src)
-{
-	memcpy (&dst->object, &src->object, NMP_OBJECT_GET_CLASS (dst)->sizeof_data);
-}
-
-static void
-_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src)
-{
-	if (dst->_link.udev.device != src->_link.udev.device) {
-		if (dst->_link.udev.device)
-			g_object_unref (dst->_link.udev.device);
-		if (src->_link.udev.device)
-			g_object_ref (src->_link.udev.device);
-	}
-	dst->_link = src->_link;
-}
-
 /* Uses internally nmp_object_copy(), hence it also violates the const
  * promise for @obj.
  * */
@@ -603,6 +781,7 @@ nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2)
 
 	klass = NMP_OBJECT_GET_CLASS (obj1);
 	return    klass == NMP_OBJECT_GET_CLASS (obj2)
+	       && klass->cmd_plobj_id_equal
 	       && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object);
 }
 
@@ -619,10 +798,13 @@ _vt_cmd_plobj_id_equal (link, NMPlatformLink,
 _vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address,
                            obj1->ifindex == obj2->ifindex
                         && obj1->plen == obj2->plen
-                        && obj1->address == obj2->address);
+                        && obj1->address == obj2->address
+                        /* for IPv4 addresses, you can add the same local address with differing peer-adddress
+                         * (IFA_ADDRESS), provided that their net-part differs. */
+                        && ((obj1->peer_address ^ obj2->peer_address) & nm_utils_ip4_prefix_to_netmask (obj1->plen)) == 0);
 _vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address,
                            obj1->ifindex == obj2->ifindex
-                        && obj1->plen == obj2->plen
+                        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
                         && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address));
 _vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route,
                            obj1->ifindex == obj2->ifindex
@@ -638,11 +820,20 @@ _vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route,
 guint
 nmp_object_id_hash (const NMPObject *obj)
 {
+	const NMPClass *klass;
+
 	if (!obj)
 		return 0;
 
 	g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), 0);
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_id_hash (&obj->object);
+
+	klass = NMP_OBJECT_GET_CLASS (obj);
+
+	if (klass->cmd_plobj_id_hash)
+		return klass->cmd_plobj_id_hash (&obj->object);
+
+	/* unhashable objects implement pointer equality. */
+	return g_direct_hash (obj);
 }
 
 #define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \
@@ -655,34 +846,25 @@ _vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \
 	return hash; \
 }
 _vt_cmd_plobj_id_hash (link, NMPlatformLink, {
-	/* libnl considers:
-	 *   .oo_id_attrs = LINK_ATTR_IFINDEX | LINK_ATTR_FAMILY,
-	 */
 	hash = (guint) 3982791431u;
 	hash = hash      + ((guint) obj->ifindex);
 })
 _vt_cmd_plobj_id_hash (ip4_address, NMPlatformIP4Address, {
-	/* libnl considers:
-	 *   .oo_id_attrs = (ADDR_ATTR_FAMILY | ADDR_ATTR_IFINDEX |
-	 *                   ADDR_ATTR_LOCAL | ADDR_ATTR_PREFIXLEN),
-	 */
 	hash = (guint) 3591309853u;
 	hash = hash      + ((guint) obj->ifindex);
 	hash = hash * 33 + ((guint) obj->plen);
 	hash = hash * 33 + ((guint) obj->address);
+
+	/* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
+	hash = hash * 33 + ((guint) (obj->peer_address & nm_utils_ip4_prefix_to_netmask (obj->plen)));
 })
 _vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, {
 	hash = (guint) 2907861637u;
 	hash = hash      + ((guint) obj->ifindex);
-	hash = hash * 33 + ((guint) obj->plen);
+	/* for IPv6 addresses, the prefix length is not part of the primary identifier. */
 	hash = hash * 33 + _id_hash_ip6_addr (&obj->address);
 })
 _vt_cmd_plobj_id_hash (ip4_route, NMPlatformIP4Route, {
-	/* libnl considers:
-	 *   .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS |
-	 *                   ROUTE_ATTR_TABLE | ROUTE_ATTR_DST |
-	 *                   ROUTE_ATTR_PRIO),
-	 */
 	hash = (guint) 2569857221u;
 	hash = hash      + ((guint) obj->ifindex);
 	hash = hash * 33 + ((guint) obj->plen);
@@ -700,11 +882,15 @@ _vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, {
 gboolean
 nmp_object_is_alive (const NMPObject *obj)
 {
+	const NMPClass *klass;
+
 	/* for convenience, allow NULL. */
 	if (!obj)
 		return FALSE;
 
-	return  NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_alive (obj);
+	klass = NMP_OBJECT_GET_CLASS (obj);
+	return    !klass->cmd_obj_is_alive
+	       || klass->cmd_obj_is_alive (obj);
 }
 
 static gboolean
@@ -742,75 +928,29 @@ _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj)
 
 gboolean
 nmp_object_is_visible (const NMPObject *obj)
-
 {
+	const NMPClass *klass;
+
 	/* for convenience, allow NULL. */
 	if (!obj)
 		return FALSE;
 
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_visible (obj);
-}
+	klass = NMP_OBJECT_GET_CLASS (obj);
 
-static gboolean
-_vt_cmd_obj_is_visible_link (const NMPObject *obj)
-{
-	return    obj->object.ifindex > 0
-	       && obj->_link.netlink.is_in_netlink
-	       && obj->link.name[0];
-}
+	/* a dead object is never visible. */
+	if (   klass->cmd_obj_is_alive
+	    && !klass->cmd_obj_is_alive (obj))
+		return FALSE;
 
-static gboolean
-_vt_cmd_obj_is_visible_ipx_address (const NMPObject *obj)
-{
-	return obj->object.ifindex > 0;
+	return    !klass->cmd_obj_is_visible
+	       || klass->cmd_obj_is_visible (obj);
 }
 
 static gboolean
-_vt_cmd_obj_is_visible_ipx_route (const NMPObject *obj)
-{
-	NMIPConfigSource source = obj->ip_route.source;
-
-	return obj->object.ifindex > 0 && source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
-}
-
-/******************************************************************/
-
-/**
- * nmp_object_from_nl:
- * @platform: platform instance (needed to lookup sysctl)
- * @nlo:
- * @id_only: if %TRUE, only fill the id fields of the object and leave the
- *   other fields unset. This is useful to create a needle to lookup a matching
- *   item in the cache.
- * @complete_from_cache: sometimes the netlink object doesn't contain all information.
- *   If true, look them up in the cache and preserve the original value.
- *
- * Convert a libnl object to a platform object.
- * Returns: a NMPObject containing @nlo. If @id_only is %TRUE, only the id fields
- *   are defined.
- **/
-NMPObject *
-nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache)
-{
-	NMPObjectType obj_type = _nlo_get_object_type (nlo);
-	NMPObject *obj;
-
-	if (obj_type == NMP_OBJECT_TYPE_UNKNOWN)
-		return NULL;
-
-	obj = nmp_object_new (obj_type, NULL);
-
-	if (!NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_init_from_nl (platform, &obj->object, nlo, id_only, complete_from_cache)) {
-		nmp_object_unref (obj);
-		return NULL;
-	}
-	return obj;
-}
-
-struct nl_object *
-nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only)
+_vt_cmd_obj_is_visible_link (const NMPObject *obj)
 {
-	return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_nl (platform, &obj->object, id_only);
+	return    obj->_link.netlink.is_in_netlink
+	       && obj->link.name[0];
 }
 
 /******************************************************************/
@@ -922,6 +1062,24 @@ nmp_cache_id_init_routes_visible (NMPCacheId *id,
 	return id;
 }
 
+NMPCacheId *
+nmp_cache_id_init_link_by_ifname (NMPCacheId *id,
+                                  const char *ifname)
+{
+	gsize l;
+
+	_nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_LINK_BY_IFNAME);
+
+	if (   !ifname
+	    || (l = strlen (ifname)) > sizeof (id->link_by_ifname.ifname_short))
+		g_return_val_if_reached (id);
+
+	/* the trailing NUL is dropped!! */
+	memcpy (id->link_by_ifname.ifname_short, ifname, l);
+
+	return id;
+}
+
 /******************************************************************/
 
 static gboolean
@@ -940,14 +1098,26 @@ _nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCach
 			*out_id = NULL;
 		return TRUE;
 	default:
-		return klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
+		return    klass->cmd_obj_init_cache_id
+		       && klass->cmd_obj_init_cache_id (obj, id_type, id, out_id);
 	}
 }
 
 static gboolean
 _vt_cmd_obj_init_cache_id_link (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id)
 {
-	return FALSE;
+	switch (id_type) {
+	case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME:
+		if (obj->link.name[0]) {
+			*out_id = nmp_cache_id_init_link_by_ifname (id, obj->link.name);
+			return TRUE;
+		}
+		break;
+	default:
+		return FALSE;
+	}
+	*out_id = NULL;
+	return TRUE;
 }
 
 static gboolean
@@ -955,7 +1125,7 @@ _vt_cmd_obj_init_cache_id_ipx_address (const NMPObject *obj, NMPCacheIdType id_t
 {
 	switch (id_type) {
 	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (_vt_cmd_obj_is_visible_ipx_address (obj)) {
+		if (nmp_object_is_visible (obj)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
 			return TRUE;
@@ -973,14 +1143,14 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 {
 	switch (id_type) {
 	case NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX:
-		if (_vt_cmd_obj_is_visible_ipx_route (obj)) {
+		if (nmp_object_is_visible (obj)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_addrroute_visible_by_ifindex (id, NMP_OBJECT_GET_TYPE (obj), obj->object.ifindex);
 			return TRUE;
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible  (obj)
 		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, 0);
@@ -988,7 +1158,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible  (obj)
 		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, 0);
@@ -996,7 +1166,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible (obj)
 		    && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), FALSE, TRUE, obj->object.ifindex);
@@ -1004,7 +1174,7 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ
 		}
 		break;
 	case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT:
-		if (   _vt_cmd_obj_is_visible_ipx_route (obj)
+		if (   nmp_object_is_visible (obj)
 		    && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) {
 			nm_assert (obj->object.ifindex > 0);
 			*out_id = nmp_cache_id_init_routes_visible (id, NMP_OBJECT_GET_TYPE (obj), TRUE, FALSE, obj->object.ifindex);
@@ -1211,7 +1381,7 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	const NMPObject *obj;
 	const NMPlatformObject *const *list;
 	guint i, len;
-	NMPCacheId cache_id;
+	NMPCacheId cache_id, *p_cache_id;
 
 	if (ifindex > 0) {
 		obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex));
@@ -1226,7 +1396,14 @@ nmp_cache_lookup_link_full (const NMPCache *cache,
 	} else if (!ifname && !match_fn)
 		return NULL;
 	else {
-		list = nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, visible_only), &len);
+		if (   ifname
+		    && strlen (ifname) <= sizeof (cache_id.link_by_ifname.ifname_short)) {
+			p_cache_id = nmp_cache_id_init_link_by_ifname (&cache_id, ifname);
+			ifname = NULL;
+		} else
+			p_cache_id = nmp_cache_id_init_object_type (&cache_id, NMP_OBJECT_TYPE_LINK, visible_only);
+
+		list = nmp_cache_lookup_multi (cache, p_cache_id, &len);
 		for (i = 0; i < len; i++) {
 			obj = NMP_OBJECT_UP_CAST (list[i]);
 
@@ -1303,12 +1480,8 @@ _nmp_cache_update_add (NMPCache *cache, NMPObject *obj)
 	nm_assert (!obj->is_cached);
 	nmp_object_ref (obj);
 	nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object));
-#if GLIB_CHECK_VERSION(2, 40, 0)
-	if (!g_hash_table_add (cache->idx_main, obj))
+	if (!nm_g_hash_table_add (cache->idx_main, obj))
 		g_assert_not_reached ();
-#else
-	g_hash_table_add (cache->idx_main, obj);
-#endif
 	obj->is_cached = TRUE;
 	_nmp_cache_update_cache (cache, obj, FALSE);
 }
@@ -1385,7 +1558,7 @@ nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObjec
 {
 	if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) {
 		NMPObject *old;
-		auto_nmp_obj NMPObject *obj = NULL;
+		nm_auto_nmpobj NMPObject *obj = NULL;
 
 		/* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be
 		 * removed from netlink. Link objects are alive without being in netlink when they
@@ -1510,8 +1683,8 @@ nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj,
 		 * We could add efficient reverse lookup by adding a reverse index to NMMultiIndex. But that
 		 * also adds some cost to support an (uncommon?) usage pattern.
 		 *
-		 * Instead we just don't support it. Actually, we expect the user to
-		 * create a new instance with nmp_object_from_nl(). That is what nmp_cache_update_netlink().
+		 * Instead we just don't support it, instead we expect the user to
+		 * create a new instance from netlink.
 		 *
 		 * TL;DR: a cached object must never be modified.
 		 */
@@ -1580,7 +1753,7 @@ NMPCacheOpsType
 nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
 {
 	NMPObject *old;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
 
 	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
 
@@ -1647,7 +1820,7 @@ NMPCacheOpsType
 nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data)
 {
 	NMPObject *old;
-	auto_nmp_obj NMPObject *obj = NULL;
+	nm_auto_nmpobj NMPObject *obj = NULL;
 
 	old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex);
 
@@ -1727,7 +1900,7 @@ nmp_cache_free (NMPCache *cache)
 void
 ASSERT_nmp_cache_is_consistent (const NMPCache *cache)
 {
-#ifdef NM_MORE_ASSERTS
+#if NM_MORE_ASSERTS
 	NMMultiIndexIter iter_multi;
 	GHashTableIter iter_hash;
 	guint i, len;
@@ -1788,24 +1961,23 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectLink),
 		.sizeof_public                      = sizeof (NMPlatformLink),
 		.obj_type_name                      = "link",
-		.nl_type                            = "route/link",
 		.addr_family                        = AF_UNSPEC,
 		.rtm_gettype                        = RTM_GETLINK,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_LINK,
 		.signal_type                        = NM_PLATFORM_SIGNAL_LINK_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_link,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_link,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_link,
 		.cmd_obj_copy                       = _vt_cmd_obj_copy_link,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_link,
 		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_link,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_link,
 		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_link,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_link,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_link,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_link,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_link,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_link,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_link,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_link,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_link_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_link_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = {
@@ -1813,23 +1985,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP4Address),
 		.sizeof_public                      = sizeof (NMPlatformIP4Address),
 		.obj_type_name                      = "ip4-address",
-		.nl_type                            = "route/addr",
 		.addr_family                        = AF_INET,
 		.rtm_gettype                        = RTM_GETADDR,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_address,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip4_address,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip4_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_address,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_address,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_address,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_address_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_address_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP6_ADDRESS - 1] = {
@@ -1837,23 +2004,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP6Address),
 		.sizeof_public                      = sizeof (NMPlatformIP6Address),
 		.obj_type_name                      = "ip6-address",
-		.nl_type                            = "route/addr",
 		.addr_family                        = AF_INET6,
 		.rtm_gettype                        = RTM_GETADDR,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ADDRESS,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_address,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_address,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_address,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_address,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip6_address,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip6_address,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_address,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_address,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_address,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_address,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_address_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_address_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp
 	},
 	[NMP_OBJECT_TYPE_IP4_ROUTE - 1] = {
@@ -1861,23 +2023,18 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP4Route),
 		.sizeof_public                      = sizeof (NMPlatformIP4Route),
 		.obj_type_name                      = "ip4-route",
-		.nl_type                            = "route/route",
 		.addr_family                        = AF_INET,
 		.rtm_gettype                        = RTM_GETROUTE,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip4_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_route,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip4_route,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip4_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip4_route,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip4_route,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip4_route,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip4_route,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_route_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_route_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp,
 	},
 	[NMP_OBJECT_TYPE_IP6_ROUTE - 1] = {
@@ -1885,24 +2042,104 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
 		.sizeof_data                        = sizeof (NMPObjectIP6Route),
 		.sizeof_public                      = sizeof (NMPlatformIP6Route),
 		.obj_type_name                      = "ip6-route",
-		.nl_type                            = "route/route",
 		.addr_family                        = AF_INET6,
 		.rtm_gettype                        = RTM_GETROUTE,
+		.signal_type_id                     = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE,
 		.signal_type                        = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
 		.cmd_obj_init_cache_id              = _vt_cmd_obj_init_cache_id_ipx_route,
-		.cmd_obj_equal                      = _vt_cmd_obj_equal_plain,
-		.cmd_obj_copy                       = _vt_cmd_obj_copy_plain,
 		.cmd_obj_stackinit_id               = _vt_cmd_obj_stackinit_id_ip6_route,
 		.cmd_obj_is_alive                   = _vt_cmd_obj_is_alive_ipx_route,
-		.cmd_obj_is_visible                 = _vt_cmd_obj_is_visible_ipx_route,
-		.cmd_plobj_init_from_nl             = _nmp_vt_cmd_plobj_init_from_nl_ip6_route,
-		.cmd_plobj_to_nl                    = _nmp_vt_cmd_plobj_to_nl_ip6_route,
 		.cmd_plobj_id_copy                  = _vt_cmd_plobj_id_copy_ip6_route,
 		.cmd_plobj_id_equal                 = _vt_cmd_plobj_id_equal_ip6_route,
 		.cmd_plobj_id_hash                  = _vt_cmd_plobj_id_hash_ip6_route,
 		.cmd_plobj_to_string_id             = _vt_cmd_plobj_to_string_id_ip6_route,
-		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_route_to_string,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_route_to_string,
 		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp,
 	},
+	[NMP_OBJECT_TYPE_LNK_GRE - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_GRE,
+		.sizeof_data                        = sizeof (NMPObjectLnkGre),
+		.sizeof_public                      = sizeof (NMPlatformLnkGre),
+		.obj_type_name                      = "gre",
+		.lnk_link_type                      = NM_LINK_TYPE_GRE,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_gre_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_INFINIBAND,
+		.sizeof_data                        = sizeof (NMPObjectLnkInfiniband),
+		.sizeof_public                      = sizeof (NMPlatformLnkInfiniband),
+		.obj_type_name                      = "infiniband",
+		.lnk_link_type                      = NM_LINK_TYPE_INFINIBAND,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_infiniband_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_infiniband_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IP6TNL,
+		.sizeof_data                        = sizeof (NMPObjectLnkIp6Tnl),
+		.sizeof_public                      = sizeof (NMPlatformLnkIp6Tnl),
+		.obj_type_name                      = "ip6tnl",
+		.lnk_link_type                      = NM_LINK_TYPE_IP6TNL,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_IPIP - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_IPIP,
+		.sizeof_data                        = sizeof (NMPObjectLnkIpIp),
+		.sizeof_public                      = sizeof (NMPlatformLnkIpIp),
+		.obj_type_name                      = "ipip",
+		.lnk_link_type                      = NM_LINK_TYPE_IPIP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ipip_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ipip_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkMacvlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvlan),
+		.obj_type_name                      = "macvlan",
+		.lnk_link_type                      = NM_LINK_TYPE_MACVLAN,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_MACVTAP,
+		.sizeof_data                        = sizeof (NMPObjectLnkMacvtap),
+		.sizeof_public                      = sizeof (NMPlatformLnkMacvtap),
+		.obj_type_name                      = "macvtap",
+		.lnk_link_type                      = NM_LINK_TYPE_MACVTAP,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macvlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_SIT - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_SIT,
+		.sizeof_data                        = sizeof (NMPObjectLnkSit),
+		.sizeof_public                      = sizeof (NMPlatformLnkSit),
+		.obj_type_name                      = "sit",
+		.lnk_link_type                      = NM_LINK_TYPE_SIT,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_sit_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_VLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkVlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkVlan),
+		.obj_type_name                      = "vlan",
+		.lnk_link_type                      = NM_LINK_TYPE_VLAN,
+		.cmd_obj_cmp                        = _vt_cmd_obj_cmp_lnk_vlan,
+		.cmd_obj_copy                       = _vt_cmd_obj_copy_lnk_vlan,
+		.cmd_obj_dispose                    = _vt_cmd_obj_dispose_lnk_vlan,
+		.cmd_obj_to_string                  = _vt_cmd_obj_to_string_lnk_vlan,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp,
+	},
+	[NMP_OBJECT_TYPE_LNK_VXLAN - 1] = {
+		.obj_type                           = NMP_OBJECT_TYPE_LNK_VXLAN,
+		.sizeof_data                        = sizeof (NMPObjectLnkVxlan),
+		.sizeof_public                      = sizeof (NMPlatformLnkVxlan),
+		.obj_type_name                      = "vxlan",
+		.lnk_link_type                      = NM_LINK_TYPE_VXLAN,
+		.cmd_plobj_to_string                = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vxlan_to_string,
+		.cmd_plobj_cmp                      = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp,
+	},
 };