summary refs log tree commit diff
path: root/src/libnm-platform/nmp-object.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
committerMichael Biebl <biebl@debian.org>2023-02-10 11:50:34 +0100
commit1372848511cb896b80b51ed1a3e9606bd9816631 (patch)
tree674792b9385bdef935988894b45f06b2af39f88c /src/libnm-platform/nmp-object.c
parent40ec077ea305994c1fc2130add6787ca0c73e2c6 (diff)
New upstream version 1.42.0 upstream/1.42.0
Diffstat (limited to 'src/libnm-platform/nmp-object.c')
-rw-r--r--src/libnm-platform/nmp-object.c680
1 files changed, 423 insertions, 257 deletions
diff --git a/src/libnm-platform/nmp-object.c b/src/libnm-platform/nmp-object.c
index db7bd314..9ba027ba 100644
--- a/src/libnm-platform/nmp-object.c
+++ b/src/libnm-platform/nmp-object.c
@@ -216,7 +216,7 @@ nm_sock_addr_union_cpy_untrusted(NMSockAddrUnion *dst,
 const char *
 nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len)
 {
-    char s_addr[NM_UTILS_INET_ADDRSTRLEN];
+    char s_addr[NM_INET_ADDRSTRLEN];
     char s_scope_id[40];
 
     if (!nm_utils_to_string_buffer_init_null(sa, &buf, &len))
@@ -232,14 +232,14 @@ nm_sock_addr_union_to_string(const NMSockAddrUnion *sa, char *buf, gsize len)
         g_snprintf(buf,
                    len,
                    "%s:%u",
-                   _nm_utils_inet4_ntop(sa->in.sin_addr.s_addr, s_addr),
+                   nm_inet4_ntop(sa->in.sin_addr.s_addr, s_addr),
                    (guint) htons(sa->in.sin_port));
         break;
     case AF_INET6:
         g_snprintf(buf,
                    len,
                    "[%s%s]:%u",
-                   _nm_utils_inet6_ntop(&sa->in6.sin6_addr, s_addr),
+                   nm_inet6_ntop(&sa->in6.sin6_addr, s_addr),
                    (sa->in6.sin6_scope_id != 0
                         ? nm_sprintf_buf(s_scope_id, "%u", sa->in6.sin6_scope_id)
                         : ""),
@@ -735,6 +735,12 @@ _vt_cmd_obj_dispose_link(NMPObject *obj)
 }
 
 static void
+_vt_cmd_obj_dispose_ip4_route(NMPObject *obj)
+{
+    nm_clear_g_free((gpointer *) &obj->_ip4_route.extra_nexthops);
+}
+
+static void
 _vt_cmd_obj_dispose_lnk_vlan(NMPObject *obj)
 {
     g_free((gpointer) obj->_lnk_vlan.ingress_qos_map);
@@ -762,17 +768,23 @@ _vt_cmd_obj_dispose_lnk_wireguard(NMPObject *obj)
     _wireguard_clear(&obj->_lnk_wireguard);
 }
 
+static gsize
+_NMP_OBJECT_STRUCT_SIZE(const NMPClass *klass)
+{
+    nm_assert(klass);
+    nm_assert(klass->sizeof_public > 0);
+    nm_assert(klass->sizeof_public <= klass->sizeof_data);
+
+    return klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object);
+}
+
 static NMPObject *
 _nmp_object_new_from_class(const NMPClass *klass)
 {
     NMPObject *obj;
 
-    nm_assert(klass);
-    nm_assert(klass->sizeof_data > 0);
-    nm_assert(klass->sizeof_public > 0 && klass->sizeof_public <= klass->sizeof_data);
-
-    obj         = g_slice_alloc0(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object));
-    obj->_class = klass;
+    obj                    = g_slice_alloc0(_NMP_OBJECT_STRUCT_SIZE(klass));
+    obj->_class            = klass;
     obj->parent._ref_count = 1;
     return obj;
 }
@@ -805,9 +817,8 @@ static NMPObject *
 _nmp_object_stackinit_from_class(NMPObject *obj, const NMPClass *klass)
 {
     nm_assert(obj);
-    nm_assert(klass);
 
-    memset(obj, 0, sizeof(NMPObject));
+    memset(obj, 0, _NMP_OBJECT_STRUCT_SIZE(klass));
     obj->_class            = klass;
     obj->parent._ref_count = NM_OBJ_REF_COUNT_STACKINIT;
     return obj;
@@ -843,14 +854,12 @@ nmp_object_stackinit_id(NMPObject *obj, const NMPObject *src)
     if (klass->cmd_plobj_id_copy)
         klass->cmd_plobj_id_copy(&obj->object, &src->object);
     else {
-        /* This object must not implement cmd_obj_copy().
-         * If it would, it would mean that we require a deep copy
-         * of the data. As @obj is stack-allocated, it cannot track
-         * ownership. The caller must not use nmp_object_stackinit_id()
-         * with an object of such a type. */
-        nm_assert(!klass->cmd_obj_copy);
-
-        /* plain memcpy of the public part suffices. */
+        /* plain memcpy.
+         *
+         * Note that for NMPObjectIP4Route this also copies extra_nexthops
+         * pointer, aliasing it without taking ownership. That is potentially
+         * dangerous, but when using a stack allocated instance, you must
+         * always take care of ownership. */
         memcpy(&obj->object, &src->object, klass->sizeof_data);
     }
     return obj;
@@ -907,30 +916,33 @@ nmp_object_to_string(const NMPObject      *obj,
 
     klass = NMP_OBJECT_GET_CLASS(obj);
 
-    if (klass->cmd_obj_to_string)
+    if (klass->cmd_obj_to_string) {
+        nm_assert(!klass->cmd_plobj_to_string);
+        nm_assert(!klass->cmd_plobj_to_string_id);
         return klass->cmd_obj_to_string(obj, to_string_mode, buf, buf_size);
+    }
+
+    nm_assert(klass->cmd_plobj_to_string);
 
     switch (to_string_mode) {
     case NMP_OBJECT_TO_STRING_ID:
-        if (!klass->cmd_plobj_to_string_id) {
-            g_snprintf(buf, buf_size, NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(obj));
-            return buf;
-        }
-        return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size);
+        if (klass->cmd_plobj_to_string_id)
+            return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size);
+        g_snprintf(buf, buf_size, NM_HASH_OBFUSCATE_PTR_FMT, NM_HASH_OBFUSCATE_PTR(obj));
+        return buf;
     case NMP_OBJECT_TO_STRING_ALL:
-        g_snprintf(
-            buf,
-            buf_size,
-            "[%s," NM_HASH_OBFUSCATE_PTR_FMT ",%u,%calive,%cvisible; %s]",
-            klass->obj_type_name,
-            NM_HASH_OBFUSCATE_PTR(obj),
-            obj->parent._ref_count,
-            nmp_object_is_alive(obj) ? '+' : '-',
-            nmp_object_is_visible(obj) ? '+' : '-',
-            NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf2, sizeof(buf2)));
+        g_snprintf(buf,
+                   buf_size,
+                   "[%s," NM_HASH_OBFUSCATE_PTR_FMT ",%u,%calive,%cvisible; %s]",
+                   klass->obj_type_name,
+                   NM_HASH_OBFUSCATE_PTR(obj),
+                   obj->parent._ref_count,
+                   nmp_object_is_alive(obj) ? '+' : '-',
+                   nmp_object_is_visible(obj) ? '+' : '-',
+                   klass->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);
+        klass->cmd_plobj_to_string(&obj->object, buf, buf_size);
         return buf;
     default:
         g_return_val_if_reached("ERROR");
@@ -948,7 +960,8 @@ _vt_cmd_obj_to_string_link(const NMPObject      *obj,
 
     switch (to_string_mode) {
     case NMP_OBJECT_TO_STRING_ID:
-        return klass->cmd_plobj_to_string_id(&obj->object, buf, buf_size);
+        g_snprintf(buf, buf_size, "%d", obj->link.ifindex);
+        return buf;
     case NMP_OBJECT_TO_STRING_ALL:
         nm_strbuf_append(&b,
                          &buf_size,
@@ -961,7 +974,7 @@ _vt_cmd_obj_to_string_link(const NMPObject      *obj,
                          nmp_object_is_visible(obj) ? '+' : '-',
                          obj->_link.netlink.is_in_netlink ? '+' : '-',
                          NM_HASH_OBFUSCATE_PTR(obj->_link.udev.device));
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size);
+        nm_platform_link_to_string(&obj->link, b, buf_size);
         nm_strbuf_seek_end(&b, &buf_size);
         if (obj->_link.netlink.lnk) {
             nm_strbuf_append_str(&b, &buf_size, "; ");
@@ -971,7 +984,7 @@ _vt_cmd_obj_to_string_link(const NMPObject      *obj,
         nm_strbuf_append_c(&b, &buf_size, ']');
         return buf;
     case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, b, buf_size);
+        nm_platform_link_to_string(&obj->link, b, buf_size);
         if (obj->_link.netlink.lnk) {
             nm_strbuf_seek_end(&b, &buf_size);
             nm_strbuf_append_str(&b, &buf_size, "; ");
@@ -984,6 +997,41 @@ _vt_cmd_obj_to_string_link(const NMPObject      *obj,
 }
 
 static const char *
+_vt_cmd_obj_to_string_ip4_route(const NMPObject      *obj,
+                                NMPObjectToStringMode to_string_mode,
+                                char                 *buf,
+                                gsize                 buf_size)
+{
+    const NMPClass *klass;
+    char            buf2[NM_UTILS_TO_STRING_BUFFER_SIZE];
+
+    klass = NMP_OBJECT_GET_CLASS(obj);
+
+    switch (to_string_mode) {
+    case NMP_OBJECT_TO_STRING_PUBLIC:
+    case NMP_OBJECT_TO_STRING_ID:
+        nm_platform_ip4_route_to_string_full(&obj->ip4_route,
+                                             obj->_ip4_route.extra_nexthops,
+                                             buf,
+                                             buf_size);
+        return buf;
+    case NMP_OBJECT_TO_STRING_ALL:
+        g_snprintf(buf,
+                   buf_size,
+                   "[%s," NM_HASH_OBFUSCATE_PTR_FMT ",%u,%calive,%cvisible; %s]",
+                   klass->obj_type_name,
+                   NM_HASH_OBFUSCATE_PTR(obj),
+                   obj->parent._ref_count,
+                   nmp_object_is_alive(obj) ? '+' : '-',
+                   nmp_object_is_visible(obj) ? '+' : '-',
+                   nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, buf2, sizeof(buf2)));
+        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,
@@ -1013,7 +1061,7 @@ _vt_cmd_obj_to_string_lnk_vlan(const NMPObject      *obj,
                    nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, 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);
+        nm_platform_lnk_vlan_to_string(&obj->lnk_vlan, buf, buf_size);
 
         b = buf;
         l = strlen(b);
@@ -1092,8 +1140,7 @@ _vt_cmd_obj_to_string_lnk_wireguard(const NMPObject      *obj,
 
         return buf;
     case NMP_OBJECT_TO_STRING_PUBLIC:
-        NMP_OBJECT_GET_CLASS(obj)->cmd_plobj_to_string(&obj->object, buf, buf_size);
-
+        nm_platform_lnk_wireguard_to_string(&obj->lnk_wireguard, buf, buf_size);
         return buf;
     default:
         g_return_val_if_reached("ERROR");
@@ -1106,41 +1153,38 @@ _vt_cmd_obj_to_string_lnk_wireguard(const NMPObject      *obj,
                                                          gsize                   buf_len) \
     {                                                                                     \
         plat_type *const obj = (plat_type *) _obj;                                        \
-        _nm_unused char  buf1[NM_UTILS_INET_ADDRSTRLEN];                                  \
-        _nm_unused char  buf2[NM_UTILS_INET_ADDRSTRLEN];                                  \
+        _nm_unused char  buf1[NM_INET_ADDRSTRLEN];                                        \
+        _nm_unused char  buf2[NM_INET_ADDRSTRLEN];                                        \
                                                                                           \
         g_snprintf(buf, buf_len, __VA_ARGS__);                                            \
         return buf;                                                                       \
     }                                                                                     \
     _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
-_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(
-                               nm_utils_ip4_address_clear_host_address(obj->peer_address,
-                                                                       obj->plen),
-                               buf2)
-                                                             : "");
+_vt_cmd_plobj_to_string_id(
+    ip4_address,
+    NMPlatformIP4Address,
+    "%d: %s/%d%s%s",
+    obj->ifindex,
+    nm_inet4_ntop(obj->address, buf1),
+    obj->plen,
+    obj->peer_address != obj->address ? "," : "",
+    obj->peer_address != obj->address
+        ? nm_inet4_ntop(nm_ip4_addr_clear_host_address(obj->peer_address, obj->plen), buf2)
+        : "");
 
 _vt_cmd_plobj_to_string_id(ip6_address,
                            NMPlatformIP6Address,
                            "%d: %s",
                            obj->ifindex,
-                           _nm_utils_inet6_ntop(&obj->address, buf1));
+                           nm_inet6_ntop(&obj->address, buf1));
 
 _vt_cmd_plobj_to_string_id(qdisc, NMPlatformQdisc, "%d: %d", obj->ifindex, obj->parent);
 
 _vt_cmd_plobj_to_string_id(tfilter, NMPlatformTfilter, "%d: %d", obj->ifindex, obj->parent);
 
 void
-nmp_object_hash_update(const NMPObject *obj, NMHashState *h)
+nmp_object_hash_update_full(const NMPObject *obj, gboolean for_id, NMHashState *h)
 {
     const NMPClass *klass;
 
@@ -1148,20 +1192,44 @@ nmp_object_hash_update(const NMPObject *obj, NMHashState *h)
 
     klass = NMP_OBJECT_GET_CLASS(obj);
 
+    nm_assert((!!klass->cmd_plobj_id_cmp) == (!!klass->cmd_plobj_id_hash_update));
+    nm_assert((!!klass->cmd_obj_cmp) == (!!klass->cmd_obj_hash_update));
+    nm_assert((!!klass->cmd_plobj_cmp) == (!!klass->cmd_plobj_hash_update));
+
+    nm_assert((!!klass->cmd_obj_hash_update) ^ (!!klass->cmd_plobj_hash_update));
+    nm_assert((!klass->cmd_obj_hash_update) || (!klass->cmd_plobj_id_hash_update));
+
     nm_hash_update_val(h, klass->obj_type);
+
+    if (for_id) {
+        if (klass->cmd_obj_hash_update)
+            klass->cmd_obj_hash_update(obj, TRUE, h);
+        else if (klass->cmd_plobj_id_hash_update)
+            klass->cmd_plobj_id_hash_update(&obj->object, h);
+        else {
+            /* The klass doesn't implement ID compare. It means, to use pointer
+             * equality. */
+            nm_hash_update_val(h, obj);
+        }
+        return;
+    }
+
     if (klass->cmd_obj_hash_update)
-        klass->cmd_obj_hash_update(obj, h);
-    else if (klass->cmd_plobj_hash_update)
-        klass->cmd_plobj_hash_update(&obj->object, h);
+        klass->cmd_obj_hash_update(obj, FALSE, h);
     else
-        nm_hash_update_val(h, obj);
+        klass->cmd_plobj_hash_update(&obj->object, h);
 }
 
 static void
-_vt_cmd_obj_hash_update_link(const NMPObject *obj, NMHashState *h)
+_vt_cmd_obj_hash_update_link(const NMPObject *obj, gboolean for_id, NMHashState *h)
 {
     nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LINK);
 
+    if (for_id) {
+        nm_hash_update_val(h, obj->link.ifindex);
+        return;
+    }
+
     nm_platform_link_hash_update(&obj->link, h);
     nm_hash_update_vals(h, obj->_link.netlink.is_in_netlink, obj->_link.udev.device);
     if (obj->_link.netlink.lnk)
@@ -1169,10 +1237,30 @@ _vt_cmd_obj_hash_update_link(const NMPObject *obj, NMHashState *h)
 }
 
 static void
-_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, NMHashState *h)
+_vt_cmd_obj_hash_update_ip4_route(const NMPObject *obj, gboolean for_id, NMHashState *h)
+{
+    guint i;
+
+    nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE);
+
+    nm_platform_ip4_route_hash_update(&obj->ip4_route,
+                                      for_id ? NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
+                                             : NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
+                                      h);
+    for (i = 1u; i < obj->ip4_route.n_nexthops; i++)
+        nm_platform_ip4_rt_nexthop_hash_update(&obj->_ip4_route.extra_nexthops[i - 1u], for_id, h);
+}
+
+static void
+_vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, gboolean for_id, NMHashState *h)
 {
     nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_VLAN);
 
+    if (for_id) {
+        nm_hash_update_val(h, obj);
+        return;
+    }
+
     nm_platform_lnk_vlan_hash_update(&obj->lnk_vlan, h);
     _vlan_xgress_qos_mappings_hash_update(obj->_lnk_vlan.n_ingress_qos_map,
                                           obj->_lnk_vlan.ingress_qos_map,
@@ -1183,12 +1271,17 @@ _vt_cmd_obj_hash_update_lnk_vlan(const NMPObject *obj, NMHashState *h)
 }
 
 static void
-_vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, NMHashState *h)
+_vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, gboolean for_id, NMHashState *h)
 {
     guint i;
 
     nm_assert(NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD);
 
+    if (for_id) {
+        nm_hash_update_val(h, obj);
+        return;
+    }
+
     nm_platform_lnk_wireguard_hash_update(&obj->lnk_wireguard, h);
 
     nm_hash_update_val(h, obj->_lnk_wireguard.peers_len);
@@ -1199,47 +1292,85 @@ _vt_cmd_obj_hash_update_lnk_wireguard(const NMPObject *obj, NMHashState *h)
 int
 nmp_object_cmp_full(const NMPObject *obj1, const NMPObject *obj2, NMPObjectCmpFlags flags)
 {
-    const NMPClass *klass1;
+    const NMPClass *klass;
     const NMPClass *klass2;
     NMPObject       obj_stackcopy;
 
+    nm_assert(
+        !NM_FLAGS_ANY(flags, ~(NMP_OBJECT_CMP_FLAGS_ID | NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX)));
+
+    /* The ID flag (currently) cannot be combined with other flags. That's partly because
+     * it's not implemented, but also because some objects use only pointer equality. So it's
+     * not clear how that combines with other flags (well, they'd be ignored). */
+    nm_assert(!NM_FLAGS_HAS(flags, NMP_OBJECT_CMP_FLAGS_ID) || flags == NMP_OBJECT_CMP_FLAGS_ID);
+
     NM_CMP_SELF(obj1, obj2);
 
     g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), -1);
     g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), 1);
 
-    klass1 = NMP_OBJECT_GET_CLASS(obj1);
+    klass = NMP_OBJECT_GET_CLASS(obj1);
+
+    nm_assert(klass);
+
+    nm_assert((!!klass->cmd_plobj_id_cmp) == (!!klass->cmd_plobj_id_hash_update));
+    nm_assert((!!klass->cmd_obj_cmp) == (!!klass->cmd_obj_hash_update));
+    nm_assert((!!klass->cmd_plobj_cmp) == (!!klass->cmd_plobj_hash_update));
+
+    nm_assert((!!klass->cmd_obj_cmp) ^ (!!klass->cmd_plobj_cmp));
+    nm_assert((!klass->cmd_obj_cmp) || (!klass->cmd_plobj_id_cmp));
+
     klass2 = NMP_OBJECT_GET_CLASS(obj2);
 
-    if (klass1 != klass2) {
-        nm_assert(klass1->obj_type != klass2->obj_type);
-        return klass1->obj_type < klass2->obj_type ? -1 : 1;
+    if (klass != klass2) {
+        nm_assert(klass2);
+        NM_CMP_DIRECT(klass->obj_type, klass2->obj_type);
+        return nm_assert_unreachable_val(0);
+    }
+
+    if (NM_FLAGS_HAS(flags, NMP_OBJECT_CMP_FLAGS_ID)) {
+        if (klass->cmd_obj_cmp)
+            return klass->cmd_obj_cmp(obj1, obj2, TRUE);
+        if (klass->cmd_plobj_id_cmp)
+            return klass->cmd_plobj_id_cmp(&obj1->object, &obj2->object);
+
+        /* the klass doesn't implement ID cmp(). That means, different objects
+         * never compare equal, but the cmp() according to their pointer value. */
+        NM_CMP_DIRECT_PTR(obj1, obj2);
+        return nm_assert_unreachable_val(0);
     }
 
     if (NM_FLAGS_HAS(flags, NMP_OBJECT_CMP_FLAGS_IGNORE_IFINDEX)) {
-        if (!NM_IN_SET(klass1,
-                       nmp_class_from_type(NMP_OBJECT_TYPE_IP4_ADDRESS),
-                       nmp_class_from_type(NMP_OBJECT_TYPE_IP6_ADDRESS),
-                       nmp_class_from_type(NMP_OBJECT_TYPE_IP4_ROUTE),
-                       nmp_class_from_type(NMP_OBJECT_TYPE_IP6_ROUTE))) {
+        if (!NM_IN_SET(klass->obj_type,
+                       NMP_OBJECT_TYPE_IP4_ADDRESS,
+                       NMP_OBJECT_TYPE_IP6_ADDRESS,
+                       NMP_OBJECT_TYPE_IP4_ROUTE,
+                       NMP_OBJECT_TYPE_IP6_ROUTE)) {
             /* This flag is currently only implemented for certain types.
              * That is, because we just create a stack copy, and that naive
              * approach only knows for types where we know that it works. */
         } else if (obj1->obj_with_ifindex.ifindex != obj2->obj_with_ifindex.ifindex) {
-            nmp_object_stackinit(&obj_stackcopy, klass1->obj_type, &obj2->obj_with_ifindex);
+            nmp_object_stackinit(&obj_stackcopy, klass->obj_type, &obj2->obj_with_ifindex);
             obj_stackcopy.obj_with_ifindex.ifindex = obj1->obj_with_ifindex.ifindex;
-            obj2                                   = &obj_stackcopy;
+            if (klass->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE)
+                obj_stackcopy._ip4_route.extra_nexthops = obj2->_ip4_route.extra_nexthops;
+            obj2 = &obj_stackcopy;
         }
     }
 
-    if (klass1->cmd_obj_cmp)
-        return klass1->cmd_obj_cmp(obj1, obj2);
-    return klass1->cmd_plobj_cmp(&obj1->object, &obj2->object);
+    if (klass->cmd_obj_cmp)
+        return klass->cmd_obj_cmp(obj1, obj2, FALSE);
+    return klass->cmd_plobj_cmp(&obj1->object, &obj2->object);
 }
 
 static int
-_vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2)
+_vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
 {
+    if (for_id) {
+        NM_CMP_FIELD(obj1, obj2, link.ifindex);
+        return 0;
+    }
+
     NM_CMP_RETURN(nm_platform_link_cmp(&obj1->link, &obj2->link));
     NM_CMP_DIRECT(obj1->_link.netlink.is_in_netlink, obj2->_link.netlink.is_in_netlink);
     NM_CMP_RETURN(nmp_object_cmp(obj1->_link.netlink.lnk, obj2->_link.netlink.lnk));
@@ -1261,10 +1392,37 @@ _vt_cmd_obj_cmp_link(const NMPObject *obj1, const NMPObject *obj2)
 }
 
 static int
-_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2)
+_vt_cmd_obj_cmp_ip4_route(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
+{
+    int   c;
+    guint i;
+
+    c = nm_platform_ip4_route_cmp(&obj1->ip4_route,
+                                  &obj2->ip4_route,
+                                  for_id ? NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID
+                                         : NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
+    NM_CMP_RETURN_DIRECT(c);
+
+    for (i = 1u; i < obj1->ip4_route.n_nexthops; i++) {
+        c = nm_platform_ip4_rt_nexthop_cmp(&obj1->_ip4_route.extra_nexthops[i - 1u],
+                                           &obj2->_ip4_route.extra_nexthops[i - 1u],
+                                           for_id);
+        NM_CMP_RETURN_DIRECT(c);
+    }
+
+    return 0;
+}
+
+static int
+_vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
 {
     int c;
 
+    if (for_id) {
+        NM_CMP_DIRECT_PTR(obj1, obj2);
+        return nm_assert_unreachable_val(0);
+    }
+
     c = nm_platform_lnk_vlan_cmp(&obj1->lnk_vlan, &obj2->lnk_vlan);
     if (c)
         return c;
@@ -1287,10 +1445,15 @@ _vt_cmd_obj_cmp_lnk_vlan(const NMPObject *obj1, const NMPObject *obj2)
 }
 
 static int
-_vt_cmd_obj_cmp_lnk_wireguard(const NMPObject *obj1, const NMPObject *obj2)
+_vt_cmd_obj_cmp_lnk_wireguard(const NMPObject *obj1, const NMPObject *obj2, gboolean for_id)
 {
     guint i;
 
+    if (for_id) {
+        NM_CMP_DIRECT_PTR(obj1, obj2);
+        return nm_assert_unreachable_val(0);
+    }
+
     NM_CMP_RETURN(nm_platform_lnk_wireguard_cmp(&obj1->lnk_wireguard, &obj2->lnk_wireguard));
 
     NM_CMP_FIELD(obj1, obj2, _lnk_wireguard.peers_len);
@@ -1354,6 +1517,28 @@ _vt_cmd_obj_copy_link(NMPObject *dst, const NMPObject *src)
 }
 
 static void
+_vt_cmd_obj_copy_ip4_route(NMPObject *dst, const NMPObject *src)
+{
+    nm_assert(dst != src);
+
+    if (src->ip4_route.n_nexthops <= 1) {
+        nm_clear_g_free((gpointer *) &dst->_ip4_route.extra_nexthops);
+    } else if (src->ip4_route.n_nexthops != dst->ip4_route.n_nexthops
+               || !nm_memeq_n(src->_ip4_route.extra_nexthops,
+                              src->ip4_route.n_nexthops - 1u,
+                              dst->_ip4_route.extra_nexthops,
+                              dst->ip4_route.n_nexthops - 1u,
+                              sizeof(NMPlatformIP4RtNextHop))) {
+        nm_clear_g_free((gpointer *) &dst->_ip4_route.extra_nexthops);
+        dst->_ip4_route.extra_nexthops =
+            nm_memdup(src->_ip4_route.extra_nexthops,
+                      sizeof(NMPlatformIP4RtNextHop) * (src->ip4_route.n_nexthops - 1u));
+    }
+
+    dst->ip4_route = src->ip4_route;
+}
+
+static void
 _vt_cmd_obj_copy_lnk_vlan(NMPObject *dst, const NMPObject *src)
 {
     dst->lnk_vlan = src->lnk_vlan;
@@ -1451,39 +1636,6 @@ nmp_object_clone(const NMPObject *obj, gboolean id_only)
     return dst;
 }
 
-int
-nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2)
-{
-    const NMPClass *klass, *klass2;
-
-    NM_CMP_SELF(obj1, obj2);
-
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj1), FALSE);
-    g_return_val_if_fail(NMP_OBJECT_IS_VALID(obj2), FALSE);
-
-    klass = NMP_OBJECT_GET_CLASS(obj1);
-    nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
-
-    klass2 = NMP_OBJECT_GET_CLASS(obj2);
-    nm_assert(klass);
-    if (klass != klass2) {
-        nm_assert(klass2);
-        NM_CMP_DIRECT(klass->obj_type, klass2->obj_type);
-        /* resort to pointer comparison */
-        NM_CMP_DIRECT_PTR(klass, klass2);
-        return 0;
-    }
-
-    if (!klass->cmd_plobj_id_cmp) {
-        /* the klass doesn't implement ID cmp(). That means, different objects
-         * never compare equal, but the cmp() according to their pointer value. */
-        NM_CMP_DIRECT_PTR(obj1, obj2);
-        return 0;
-    }
-
-    return klass->cmd_plobj_id_cmp(&obj1->object, &obj2->object);
-}
-
 #define _vt_cmd_plobj_id_cmp(type, plat_type, cmd)                        \
     static int _vt_cmd_plobj_id_cmp_##type(const NMPlatformObject *_obj1, \
                                            const NMPlatformObject *_obj2) \
@@ -1499,8 +1651,6 @@ nmp_object_id_cmp(const NMPObject *obj1, const NMPObject *obj2)
     }                                                                     \
     _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
-_vt_cmd_plobj_id_cmp(link, NMPlatformLink, { NM_CMP_FIELD(obj1, obj2, ifindex); });
-
 static int
 _vt_cmd_plobj_id_cmp_ip4_address(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
 {
@@ -1528,14 +1678,6 @@ _vt_cmd_plobj_id_cmp(tfilter, NMPlatformTfilter, {
 });
 
 static int
-_vt_cmd_plobj_id_cmp_ip4_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
-{
-    return nm_platform_ip4_route_cmp((const NMPlatformIP4Route *) obj1,
-                                     (const NMPlatformIP4Route *) obj2,
-                                     NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID);
-}
-
-static int
 _vt_cmd_plobj_id_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
 {
     return nm_platform_ip6_route_cmp((const NMPlatformIP6Route *) obj1,
@@ -1582,28 +1724,6 @@ _vt_cmd_plobj_id_cmp(mptcp_addr, NMPlatformMptcpAddr, {
     NM_CMP_FIELD(obj1, obj2, port);
 });
 
-void
-nmp_object_id_hash_update(const NMPObject *obj, NMHashState *h)
-{
-    const NMPClass *klass;
-
-    g_return_if_fail(NMP_OBJECT_IS_VALID(obj));
-
-    klass = NMP_OBJECT_GET_CLASS(obj);
-
-    nm_assert(!klass->cmd_plobj_id_hash_update == !klass->cmd_plobj_id_cmp);
-
-    if (!klass->cmd_plobj_id_hash_update) {
-        /* The klass doesn't implement ID compare. It means, to use pointer
-         * equality. */
-        nm_hash_update_val(h, obj);
-        return;
-    }
-
-    nm_hash_update_val(h, klass->obj_type);
-    klass->cmd_plobj_id_hash_update(&obj->object, h);
-}
-
 guint
 nmp_object_id_hash(const NMPObject *obj)
 {
@@ -1627,8 +1747,6 @@ nmp_object_id_hash(const NMPObject *obj)
     }                                                                                             \
     _NM_DUMMY_STRUCT_FOR_TRAILING_SEMICOLON
 
-_vt_cmd_plobj_id_hash_update(link, NMPlatformLink, { nm_hash_update_val(h, obj->ifindex); });
-
 _vt_cmd_plobj_id_hash_update(ip4_address, NMPlatformIP4Address, {
     nm_hash_update_vals(
         h,
@@ -1636,7 +1754,7 @@ _vt_cmd_plobj_id_hash_update(ip4_address, NMPlatformIP4Address, {
         obj->plen,
         obj->address,
         /* for IPv4 we must also consider the net-part of the peer-address (IFA_ADDRESS) */
-        nm_utils_ip4_address_clear_host_address(obj->peer_address, obj->plen));
+        nm_ip4_addr_clear_host_address(obj->peer_address, obj->plen));
 });
 
 _vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, {
@@ -1647,10 +1765,6 @@ _vt_cmd_plobj_id_hash_update(ip6_address, NMPlatformIP6Address, {
         obj->address);
 });
 
-_vt_cmd_plobj_id_hash_update(ip4_route, NMPlatformIP4Route, {
-    nm_platform_ip4_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
-});
-
 _vt_cmd_plobj_id_hash_update(ip6_route, NMPlatformIP6Route, {
     nm_platform_ip6_route_hash_update(obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID, h);
 });
@@ -1674,19 +1788,19 @@ _vt_cmd_plobj_id_hash_update(mptcp_addr, NMPlatformMptcpAddr, {
 });
 
 static void
-_vt_cmd_plobj_hash_update_ip4_route(const NMPlatformObject *obj, NMHashState *h)
+_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h)
 {
-    return nm_platform_ip4_route_hash_update((const NMPlatformIP4Route *) obj,
+    return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj,
                                              NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
                                              h);
 }
 
-static void
-_vt_cmd_plobj_hash_update_ip6_route(const NMPlatformObject *obj, NMHashState *h)
+static int
+_vt_cmd_plobj_cmp_ip6_route(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
 {
-    return nm_platform_ip6_route_hash_update((const NMPlatformIP6Route *) obj,
-                                             NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL,
-                                             h);
+    return nm_platform_ip6_route_cmp((const NMPlatformIP6Route *) obj1,
+                                     (const NMPlatformIP6Route *) obj2,
+                                     NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL);
 }
 
 static void
@@ -1697,6 +1811,24 @@ _vt_cmd_plobj_hash_update_routing_rule(const NMPlatformObject *obj, NMHashState
                                                 h);
 }
 
+static inline int
+_vt_cmd_plobj_cmp_routing_rule(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+    return nm_platform_routing_rule_cmp((const NMPlatformRoutingRule *) obj1,
+                                        (const NMPlatformRoutingRule *) obj2,
+                                        NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL);
+}
+
+static int
+_vt_cmd_plobj_cmp_qdisc(const NMPlatformObject *obj1, const NMPlatformObject *obj2)
+{
+    return nm_platform_qdisc_cmp((const NMPlatformQdisc *) obj1,
+                                 (const NMPlatformQdisc *) obj2,
+                                 TRUE);
+}
+
+/*****************************************************************************/
+
 guint
 nmp_object_indirect_id_hash(gconstpointer a)
 {
@@ -1859,7 +1991,7 @@ _vt_dedup_obj_destroy(NMDedupMultiObj *obj)
     klass = o->_class;
     if (klass->cmd_obj_dispose)
         klass->cmd_obj_dispose(o);
-    g_slice_free1(klass->sizeof_data + G_STRUCT_OFFSET(NMPObject, object), o);
+    g_slice_free1(_NMP_OBJECT_STRUCT_SIZE(klass), o);
 }
 
 static const NMDedupMultiObj *
@@ -2827,6 +2959,7 @@ nmp_cache_update_netlink_route(NMPCache         *cache,
                                NMPObject        *obj_hand_over,
                                gboolean          is_dump,
                                guint16           nlmsgflags,
+                               gboolean          route_is_alive,
                                const NMPObject **out_obj_old,
                                const NMPObject **out_obj_new,
                                const NMPObject **out_obj_replace,
@@ -2845,30 +2978,60 @@ nmp_cache_update_netlink_route(NMPCache         *cache,
     nm_assert(cache);
     nm_assert(NMP_OBJECT_IS_VALID(obj_hand_over));
     nm_assert(!NMP_OBJECT_IS_STACKINIT(obj_hand_over));
-    /* A link object from netlink must have the udev related fields unset.
-     * We could implement to handle that, but there is no need to support such
-     * a use-case */
     nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(obj_hand_over),
                         NMP_OBJECT_TYPE_IP4_ROUTE,
                         NMP_OBJECT_TYPE_IP6_ROUTE));
     nm_assert(nm_dedup_multi_index_obj_find(cache->multi_idx, obj_hand_over) != obj_hand_over);
 
+    if (NM_FLAGS_HAS(nlmsgflags, NLM_F_REPLACE)) {
+        /* This means, that the message indicates that another route was replaced.
+         * Since we don't cache all routes (see "route_is_alive"), we cannot know
+         * with certainty which route was replaced.
+         *
+         * Even if we would cache *all* routes (which we cannot, if kernel adds new
+         * routing features that modify the known nmp_object_id_equal()), it would
+         * be hard to find the right route that was replaced. Well, probably we
+         * would have to keep NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID sorted by order
+         * of notifications, which is hard. The code below actually makes an effort
+         * to do that, but it's not actually used, because we just resync.
+         *
+         * The only proper solution for this would be to improve kernel with [1]
+         * and [2].
+         *
+         * [1] https://bugzilla.redhat.com/show_bug.cgi?id=1337855
+         * [2] https://bugzilla.redhat.com/show_bug.cgi?id=1337860
+         *
+         * We need to resync.
+         */
+        if (NMP_OBJECT_GET_TYPE(obj_hand_over) == NMP_OBJECT_TYPE_IP4_ROUTE
+            && !nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj_hand_over)) {
+            /* For IPv4, we can do a small optimization. We skip the resync, if we have
+             * no conflicting routes (by weak-id).
+             *
+             * This optimization does not work for IPv6 (maybe should be fixed).
+             */
+        } else {
+            entry_replace   = NULL;
+            resync_required = TRUE;
+            goto out;
+        }
+    }
+
     entry_old = _lookup_entry(cache, obj_hand_over);
     entry_new = NULL;
 
     NM_SET_OUT(out_obj_old, nmp_object_ref(nm_dedup_multi_entry_get_obj(entry_old)));
 
-    if (!entry_old) {
-        if (!nmp_object_is_alive(obj_hand_over))
-            goto update_done;
+    is_alive = route_is_alive && nmp_object_is_alive(obj_hand_over);
 
-        _idxcache_update(cache, NULL, obj_hand_over, is_dump, &entry_new);
-        ops_type = NMP_CACHE_OPS_ADDED;
+    if (!entry_old) {
+        if (is_alive) {
+            _idxcache_update(cache, NULL, obj_hand_over, is_dump, &entry_new);
+            ops_type = NMP_CACHE_OPS_ADDED;
+        }
         goto update_done;
     }
 
-    is_alive = nmp_object_is_alive(obj_hand_over);
-
     if (!is_alive) {
         /* the update would make @entry_old invalid. Remove it. */
         _idxcache_update(cache, entry_old, NULL, FALSE, NULL);
@@ -2901,19 +3064,11 @@ update_done:
     if (is_dump)
         goto out;
 
-    if (!entry_new) {
-        if (NM_FLAGS_HAS(nlmsgflags, NLM_F_REPLACE)
-            && nmp_cache_lookup_all(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, obj_hand_over)) {
-            /* hm. @obj_hand_over was not added, meaning it was not alive.
-             * However, we track some other objects with the same weak-id.
-             * It's unclear what that means. To be sure, resync. */
-            resync_required = TRUE;
-        }
+    if (!entry_new)
         goto out;
-    }
 
-    /* FIXME: for routes, we only maintain the order correctly for the BY_WEAK_ID
-     * index. For all other indexes their order becomes messed up. */
+    /* For routes, we only maintain the order correctly for the BY_WEAK_ID
+     * index. For all other indexes, their order is not preserved. */
     entry_cur =
         _lookup_entry_with_idx_type(cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj);
     if (!entry_cur) {
@@ -3138,29 +3293,23 @@ typedef int (*CmdPlobjCmpFunc)(const NMPlatformObject *obj1, const NMPlatformObj
 const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
     [NMP_OBJECT_TYPE_LINK - 1] =
         {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_LINK,
-            .sizeof_data              = sizeof(NMPObjectLink),
-            .sizeof_public            = sizeof(NMPlatformLink),
-            .obj_type_name            = "link",
-            .rtm_gettype              = RTM_GETLINK,
-            .signal_type_id           = NM_PLATFORM_SIGNAL_ID_LINK,
-            .signal_type              = NM_PLATFORM_SIGNAL_LINK_CHANGED,
-            .supported_cache_ids      = _supported_cache_ids_link,
-            .cmd_obj_hash_update      = _vt_cmd_obj_hash_update_link,
-            .cmd_obj_cmp              = _vt_cmd_obj_cmp_link,
-            .cmd_obj_copy             = _vt_cmd_obj_copy_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_obj_to_string        = _vt_cmd_obj_to_string_link,
-            .cmd_plobj_id_copy        = _vt_cmd_plobj_id_copy_link,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_link,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_link,
-            .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_link,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_link_to_string,
-            .cmd_plobj_hash_update    = (CmdPlobjHashUpdateFunc) nm_platform_link_hash_update,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_link_cmp,
+            .parent              = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type            = NMP_OBJECT_TYPE_LINK,
+            .sizeof_data         = sizeof(NMPObjectLink),
+            .sizeof_public       = sizeof(NMPlatformLink),
+            .obj_type_name       = "link",
+            .rtm_gettype         = RTM_GETLINK,
+            .signal_type_id      = NM_PLATFORM_SIGNAL_ID_LINK,
+            .signal_type         = NM_PLATFORM_SIGNAL_LINK_CHANGED,
+            .supported_cache_ids = _supported_cache_ids_link,
+            .cmd_obj_hash_update = _vt_cmd_obj_hash_update_link,
+            .cmd_obj_cmp         = _vt_cmd_obj_cmp_link,
+            .cmd_obj_copy        = _vt_cmd_obj_copy_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_obj_to_string   = _vt_cmd_obj_to_string_link,
+            .cmd_plobj_id_copy   = _vt_cmd_plobj_id_copy_link,
         },
     [NMP_OBJECT_TYPE_IP4_ADDRESS - 1] =
         {
@@ -3206,23 +3355,22 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
         },
     [NMP_OBJECT_TYPE_IP4_ROUTE - 1] =
         {
-            .parent                   = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type                 = NMP_OBJECT_TYPE_IP4_ROUTE,
-            .sizeof_data              = sizeof(NMPObjectIP4Route),
-            .sizeof_public            = sizeof(NMPlatformIP4Route),
-            .obj_type_name            = "ip4-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,
-            .supported_cache_ids      = _supported_cache_ids_ipx_route,
-            .cmd_obj_is_alive         = _vt_cmd_obj_is_alive_ipx_route,
-            .cmd_plobj_id_cmp         = _vt_cmd_plobj_id_cmp_ip4_route,
-            .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_route,
-            .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_ip4_route_to_string,
-            .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_ip4_route_to_string,
-            .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_ip4_route,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_ip4_route_cmp_full,
+            .parent              = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type            = NMP_OBJECT_TYPE_IP4_ROUTE,
+            .sizeof_data         = sizeof(NMPObjectIP4Route),
+            .sizeof_public       = sizeof(NMPlatformIP4Route),
+            .obj_type_name       = "ip4-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,
+            .supported_cache_ids = _supported_cache_ids_ipx_route,
+            .cmd_obj_is_alive    = _vt_cmd_obj_is_alive_ipx_route,
+            .cmd_obj_hash_update = _vt_cmd_obj_hash_update_ip4_route,
+            .cmd_obj_cmp         = _vt_cmd_obj_cmp_ip4_route,
+            .cmd_obj_copy        = _vt_cmd_obj_copy_ip4_route,
+            .cmd_obj_dispose     = _vt_cmd_obj_dispose_ip4_route,
+            .cmd_obj_to_string   = _vt_cmd_obj_to_string_ip4_route,
         },
     [NMP_OBJECT_TYPE_IP6_ROUTE - 1] =
         {
@@ -3242,7 +3390,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_ip6_route_to_string,
             .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_ip6_route_to_string,
             .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_ip6_route,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_ip6_route_cmp_full,
+            .cmd_plobj_cmp            = _vt_cmd_plobj_cmp_ip6_route,
         },
     [NMP_OBJECT_TYPE_ROUTING_RULE - 1] =
         {
@@ -3261,7 +3409,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .cmd_plobj_to_string_id   = (CmdPlobjToStringIdFunc) nm_platform_routing_rule_to_string,
             .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_routing_rule_to_string,
             .cmd_plobj_hash_update    = _vt_cmd_plobj_hash_update_routing_rule,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_routing_rule_cmp_full,
+            .cmd_plobj_cmp            = _vt_cmd_plobj_cmp_routing_rule,
         },
     [NMP_OBJECT_TYPE_QDISC - 1] =
         {
@@ -3280,7 +3428,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .cmd_plobj_to_string_id   = _vt_cmd_plobj_to_string_id_qdisc,
             .cmd_plobj_to_string      = (CmdPlobjToStringFunc) nm_platform_qdisc_to_string,
             .cmd_plobj_hash_update    = (CmdPlobjHashUpdateFunc) nm_platform_qdisc_hash_update,
-            .cmd_plobj_cmp            = (CmdPlobjCmpFunc) nm_platform_qdisc_cmp,
+            .cmd_plobj_cmp            = _vt_cmd_plobj_cmp_qdisc,
         },
     [NMP_OBJECT_TYPE_TFILTER - 1] =
         {
@@ -3460,20 +3608,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
         },
     [NMP_OBJECT_TYPE_LNK_VLAN - 1] =
         {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .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_hash_update   = _vt_cmd_obj_hash_update_lnk_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   = (CmdPlobjToStringFunc) nm_platform_lnk_vlan_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vlan_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vlan_cmp,
+            .parent              = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .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_hash_update = _vt_cmd_obj_hash_update_lnk_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,
         },
     [NMP_OBJECT_TYPE_LNK_VRF - 1] =
         {
@@ -3487,6 +3632,30 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
             .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vrf_hash_update,
             .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vrf_cmp,
         },
+    [NMP_OBJECT_TYPE_LNK_VTI - 1] =
+        {
+            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type              = NMP_OBJECT_TYPE_LNK_VTI,
+            .sizeof_data           = sizeof(NMPObjectLnkVti),
+            .sizeof_public         = sizeof(NMPlatformLnkVti),
+            .obj_type_name         = "vti",
+            .lnk_link_type         = NM_LINK_TYPE_VTI,
+            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_vti_to_string,
+            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vti_hash_update,
+            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vti_cmp,
+        },
+    [NMP_OBJECT_TYPE_LNK_VTI6 - 1] =
+        {
+            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type              = NMP_OBJECT_TYPE_LNK_VTI6,
+            .sizeof_data           = sizeof(NMPObjectLnkVti6),
+            .sizeof_public         = sizeof(NMPlatformLnkVti6),
+            .obj_type_name         = "vti6",
+            .lnk_link_type         = NM_LINK_TYPE_VTI6,
+            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_vti6_to_string,
+            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_vti6_hash_update,
+            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_vti6_cmp,
+        },
     [NMP_OBJECT_TYPE_LNK_VXLAN - 1] =
         {
             .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
@@ -3501,20 +3670,17 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = {
         },
     [NMP_OBJECT_TYPE_LNK_WIREGUARD - 1] =
         {
-            .parent                = DEDUP_MULTI_OBJ_CLASS_INIT(),
-            .obj_type              = NMP_OBJECT_TYPE_LNK_WIREGUARD,
-            .sizeof_data           = sizeof(NMPObjectLnkWireGuard),
-            .sizeof_public         = sizeof(NMPlatformLnkWireGuard),
-            .obj_type_name         = "wireguard",
-            .lnk_link_type         = NM_LINK_TYPE_WIREGUARD,
-            .cmd_obj_hash_update   = _vt_cmd_obj_hash_update_lnk_wireguard,
-            .cmd_obj_cmp           = _vt_cmd_obj_cmp_lnk_wireguard,
-            .cmd_obj_copy          = _vt_cmd_obj_copy_lnk_wireguard,
-            .cmd_obj_dispose       = _vt_cmd_obj_dispose_lnk_wireguard,
-            .cmd_obj_to_string     = _vt_cmd_obj_to_string_lnk_wireguard,
-            .cmd_plobj_to_string   = (CmdPlobjToStringFunc) nm_platform_lnk_wireguard_to_string,
-            .cmd_plobj_hash_update = (CmdPlobjHashUpdateFunc) nm_platform_lnk_wireguard_hash_update,
-            .cmd_plobj_cmp         = (CmdPlobjCmpFunc) nm_platform_lnk_wireguard_cmp,
+            .parent              = DEDUP_MULTI_OBJ_CLASS_INIT(),
+            .obj_type            = NMP_OBJECT_TYPE_LNK_WIREGUARD,
+            .sizeof_data         = sizeof(NMPObjectLnkWireGuard),
+            .sizeof_public       = sizeof(NMPlatformLnkWireGuard),
+            .obj_type_name       = "wireguard",
+            .lnk_link_type       = NM_LINK_TYPE_WIREGUARD,
+            .cmd_obj_hash_update = _vt_cmd_obj_hash_update_lnk_wireguard,
+            .cmd_obj_cmp         = _vt_cmd_obj_cmp_lnk_wireguard,
+            .cmd_obj_copy        = _vt_cmd_obj_copy_lnk_wireguard,
+            .cmd_obj_dispose     = _vt_cmd_obj_dispose_lnk_wireguard,
+            .cmd_obj_to_string   = _vt_cmd_obj_to_string_lnk_wireguard,
         },
     [NMP_OBJECT_TYPE_LNK_BOND - 1] =
         {