// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2015 - 2018 Red Hat, Inc. */ #include "nm-default.h" #include "nmp-object.h" #include #include #include #include #include "nm-utils.h" #include "nm-glib-aux/nm-secret-utils.h" #include "nm-core-utils.h" #include "nm-platform-utils.h" #include "wifi/nm-wifi-utils.h" #include "wpan/nm-wpan-utils.h" /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_PLATFORM #define _NMLOG(level, obj, ...) \ G_STMT_START { \ const NMLogLevel __level = (level); \ \ if (nm_logging_enabled (__level, _NMLOG_DOMAIN)) { \ const NMPObject *const __obj = (obj); \ \ _nm_log (__level, _NMLOG_DOMAIN, 0, NULL, NULL, \ "nmp-object[%p/%s]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ __obj, \ (__obj ? NMP_OBJECT_GET_CLASS (__obj)->obj_type_name : "???") \ _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } \ } G_STMT_END /*****************************************************************************/ typedef struct { NMDedupMultiIdxType parent; NMPCacheIdType cache_id_type; } DedupMultiIdxType; struct _NMPCache { /* the cache contains only one hash table for all object types, and similarly * it contains only one NMMultiIndex. * This works, because different object types don't ever compare equal and * because their index ids also don't overlap. * * For routes and addresses, the cache contains an address if (and only if) the * object was reported via netlink. * For links, the cache contain a link if it was reported by either netlink * or udev. That means, a link object can be alive, even if it was already * removed via netlink. * * This effectively merges the udev-device cache into the NMPCache. */ NMDedupMultiIndex *multi_idx; /* an idx_type entry for each NMP_CACHE_ID_TYPE. Note that NONE (zero) * is skipped, so the index is shifted by one: idx_type[cache_id_type - 1]. * * Don't bother, use _idx_type_get() instead! */ DedupMultiIdxType idx_types[NMP_CACHE_ID_TYPE_MAX]; gboolean use_udev; }; /*****************************************************************************/ int nm_sock_addr_union_cmp (const NMSockAddrUnion *a, const NMSockAddrUnion *b) { nm_assert (!a || NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); nm_assert (!b || NM_IN_SET (b->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); NM_CMP_SELF (a, b); NM_CMP_FIELD (a, b, sa.sa_family); switch (a->sa.sa_family) { case AF_INET: NM_CMP_DIRECT (ntohl (a->in.sin_addr.s_addr), ntohl (b->in.sin_addr.s_addr)); NM_CMP_DIRECT (htons (a->in.sin_port), htons (b->in.sin_port)); break; case AF_INET6: NM_CMP_DIRECT_IN6ADDR (&a->in6.sin6_addr, &b->in6.sin6_addr); NM_CMP_DIRECT (htons (a->in6.sin6_port), htons (b->in6.sin6_port)); NM_CMP_FIELD (a, b, in6.sin6_scope_id); NM_CMP_FIELD (a, b, in6.sin6_flowinfo); break; } return 0; } void nm_sock_addr_union_hash_update (const NMSockAddrUnion *a, NMHashState *h) { if (!a) { nm_hash_update_val (h, 1241364739u); return; } nm_assert (NM_IN_SET (a->sa.sa_family, AF_UNSPEC, AF_INET, AF_INET6)); switch (a->sa.sa_family) { case AF_INET: nm_hash_update_vals (h, a->in.sin_family, a->in.sin_addr.s_addr, a->in.sin_port); return; case AF_INET6: nm_hash_update_vals (h, a->in6.sin6_family, a->in6.sin6_addr, a->in6.sin6_port, a->in6.sin6_scope_id, a->in6.sin6_flowinfo); return; default: nm_hash_update_val (h, a->sa.sa_family); return; } } /** * nm_sock_addr_union_cpy: * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of * error). * @src: (allow-none): the source buffer with an sockaddr to copy. It may be unaligned in * memory. If not %NULL, the buffer must be at least large enough to contain * sa.sa_family, and then, depending on sa.sa_family, it must be large enough * to hold struct sockaddr_in or struct sockaddr_in6. * * @dst will always be fully initialized (including setting all un-used bytes to zero). */ void nm_sock_addr_union_cpy (NMSockAddrUnion *dst, gconstpointer src /* unaligned (const NMSockAddrUnion *) */) { struct sockaddr sa; gsize src_len; nm_assert (dst); *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; if (!src) return; memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family)); if (sa.sa_family == AF_INET) src_len = sizeof (struct sockaddr_in); else if (sa.sa_family == AF_INET6) src_len = sizeof (struct sockaddr_in6); else return; memcpy (dst, src, src_len); nm_assert (dst->sa.sa_family == sa.sa_family); } /** * nm_sock_addr_union_cpy_untrusted: * @dst: the destination #NMSockAddrUnion. It will always be fully initialized, * to one of the address families AF_INET, AF_INET6, or AF_UNSPEC (in case of * error). * @src: the source buffer with an sockaddr to copy. It may be unaligned in * memory. * @src_len: the length of @src in bytes. * * The function requires @src_len to be either sizeof(struct sockaddr_in) or sizeof (struct sockaddr_in6). * If that's the case, then @src will be interpreted as such structure (unaligned), and * accessed. It will check sa.sa_family to match the expected sizes, and if it does, the * struct will be copied. * * On any failure, @dst will be set to sa.sa_family AF_UNSPEC. * @dst will always be fully initialized (including setting all un-used bytes to zero). */ void nm_sock_addr_union_cpy_untrusted (NMSockAddrUnion *dst, gconstpointer src /* unaligned (const NMSockAddrUnion *) */, gsize src_len) { int f_expected; struct sockaddr sa; nm_assert (dst); *dst = (NMSockAddrUnion) NM_SOCK_ADDR_UNION_INIT_UNSPEC; if (src_len == sizeof (struct sockaddr_in)) f_expected = AF_INET; else if (src_len == sizeof (struct sockaddr_in6)) f_expected = AF_INET6; else return; memcpy (&sa.sa_family, &((struct sockaddr *) src)->sa_family, sizeof (sa.sa_family)); if (sa.sa_family != f_expected) return; memcpy (dst, src, src_len); nm_assert (dst->sa.sa_family == sa.sa_family); } const char * nm_sock_addr_union_to_string (const NMSockAddrUnion *sa, char *buf, gsize len) { char s_addr[NM_UTILS_INET_ADDRSTRLEN]; char s_scope_id[40]; if (!nm_utils_to_string_buffer_init_null (sa, &buf, &len)) return buf; /* maybe we should use getnameinfo(), but here implement it ourself. * * We want to see the actual bytes for debugging (as we understand them), * and now what getnameinfo() makes of it. Also, it's simpler this way. */ switch (sa->sa.sa_family) { case AF_INET: g_snprintf (buf, len, "%s:%u", _nm_utils_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), ( sa->in6.sin6_scope_id != 0 ? nm_sprintf_buf (s_scope_id, "%u", sa->in6.sin6_scope_id) : ""), (guint) htons (sa->in6.sin6_port)); break; case AF_UNSPEC: g_snprintf (buf, len, "unspec"); break; default: g_snprintf (buf, len, "{addr-family:%u}", (unsigned) sa->sa.sa_family); break; } return buf; } /*****************************************************************************/ static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class; static void _idx_obj_id_hash_update (const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj, NMHashState *h) { const NMPObject *o = (NMPObject *) obj; nm_assert (idx_type && idx_type->klass == &_dedup_multi_idx_type_class); nm_assert (NMP_OBJECT_GET_TYPE (o) != NMP_OBJECT_TYPE_UNKNOWN); nmp_object_id_hash_update (o, h); } static gboolean _idx_obj_id_equal (const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj_a, const NMDedupMultiObj *obj_b) { const NMPObject *o_a = (NMPObject *) obj_a; const NMPObject *o_b = (NMPObject *) obj_b; nm_assert (idx_type && idx_type->klass == &_dedup_multi_idx_type_class); nm_assert (NMP_OBJECT_GET_TYPE (o_a) != NMP_OBJECT_TYPE_UNKNOWN); nm_assert (NMP_OBJECT_GET_TYPE (o_b) != NMP_OBJECT_TYPE_UNKNOWN); return nmp_object_id_equal (o_a, o_b); } static guint _idx_obj_part (const DedupMultiIdxType *idx_type, const NMPObject *obj_a, const NMPObject *obj_b, NMHashState *h) { NMPObjectType obj_type; /* the hash/equals functions are strongly related. So, keep them * side-by-side and do it all in _idx_obj_part(). */ nm_assert (idx_type); nm_assert (idx_type->parent.klass == &_dedup_multi_idx_type_class); nm_assert (obj_a); nm_assert (NMP_OBJECT_GET_TYPE (obj_a) != NMP_OBJECT_TYPE_UNKNOWN); nm_assert (!obj_b || (NMP_OBJECT_GET_TYPE (obj_b) != NMP_OBJECT_TYPE_UNKNOWN)); nm_assert (!h || !obj_b); switch (idx_type->cache_id_type) { case NMP_CACHE_ID_TYPE_OBJECT_TYPE: if (obj_b) return NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b); if (h) { nm_hash_update_vals (h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE (obj_a)); } return 1; case NMP_CACHE_ID_TYPE_LINK_BY_IFNAME: if (NMP_OBJECT_GET_TYPE (obj_a) != NMP_OBJECT_TYPE_LINK) { /* first check, whether obj_a is suitable for this idx_type. * If not, return 0 (which is correct for partitionable(), hash() and equal() * functions. */ if (h) nm_hash_update_val (h, obj_a); return 0; } if (obj_b) { /* we are in equal() mode. Compare obj_b with obj_a. */ return NMP_OBJECT_GET_TYPE (obj_b) == NMP_OBJECT_TYPE_LINK && nm_streq (obj_a->link.name, obj_b->link.name); } if (h) { nm_hash_update_val (h, idx_type->cache_id_type); nm_hash_update_strarr (h, obj_a->link.name); } /* just return 1, to indicate that obj_a is partitionable by this idx_type. */ return 1; case NMP_CACHE_ID_TYPE_DEFAULT_ROUTES: if ( !NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_a), NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) || !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj_a->ip_route) || !nmp_object_is_visible (obj_a)) { if (h) nm_hash_update_val (h, obj_a); return 0; } if (obj_b) { return NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b) && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj_b->ip_route) && nmp_object_is_visible (obj_b); } if (h) { nm_hash_update_vals (h, idx_type->cache_id_type, NMP_OBJECT_GET_TYPE (obj_a)); } return 1; case NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX: if ( !NM_IN_SET (NMP_OBJECT_GET_TYPE (obj_a), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, NMP_OBJECT_TYPE_QDISC, NMP_OBJECT_TYPE_TFILTER) || !nmp_object_is_visible (obj_a)) { if (h) nm_hash_update_val (h, obj_a); return 0; } nm_assert (NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex > 0); if (obj_b) { return NMP_OBJECT_GET_TYPE (obj_a) == NMP_OBJECT_GET_TYPE (obj_b) && NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_a)->ifindex == NMP_OBJECT_CAST_OBJ_WITH_IFINDEX (obj_b)->ifindex && nmp_object_is_visible (obj_b); } if (h) { nm_hash_update_vals (h, idx_type->cache_id_type, obj_a->obj_with_ifindex.ifindex); } return 1; case NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID: obj_type = NMP_OBJECT_GET_TYPE (obj_a); if ( !NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE) || NMP_OBJECT_CAST_IP_ROUTE (obj_a)->ifindex <= 0) { if (h) nm_hash_update_val (h, obj_a); return 0; } if (obj_b) { return obj_type == NMP_OBJECT_GET_TYPE (obj_b) && NMP_OBJECT_CAST_IP_ROUTE (obj_b)->ifindex > 0 && (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE ? (nm_platform_ip4_route_cmp (&obj_a->ip4_route, &obj_b->ip4_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0) : (nm_platform_ip6_route_cmp (&obj_a->ip6_route, &obj_b->ip6_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID) == 0)); } if (h) { nm_hash_update_val (h, idx_type->cache_id_type); if (obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) nm_platform_ip4_route_hash_update (&obj_a->ip4_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, h); else nm_platform_ip6_route_hash_update (&obj_a->ip6_route, NM_PLATFORM_IP_ROUTE_CMP_TYPE_WEAK_ID, h); } return 1; case NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY: obj_type = NMP_OBJECT_GET_TYPE (obj_a); /* currently, only routing rules are supported for this cache-id-type. */ if ( obj_type != NMP_OBJECT_TYPE_ROUTING_RULE || !NM_IN_SET (obj_a->routing_rule.addr_family, AF_INET, AF_INET6)) { if (h) nm_hash_update_val (h, obj_a); return 0; } if (obj_b) { return NMP_OBJECT_GET_TYPE (obj_b) == NMP_OBJECT_TYPE_ROUTING_RULE && obj_a->routing_rule.addr_family == obj_b->routing_rule.addr_family; } if (h) { nm_hash_update_vals (h, idx_type->cache_id_type, obj_a->routing_rule.addr_family); } return 1; case NMP_CACHE_ID_TYPE_NONE: case __NMP_CACHE_ID_TYPE_MAX: break; } nm_assert_not_reached (); return 0; } static gboolean _idx_obj_partitionable (const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj) { return _idx_obj_part ((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, NULL) != 0; } static void _idx_obj_partition_hash_update (const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj, NMHashState *h) { _idx_obj_part ((DedupMultiIdxType *) idx_type, (NMPObject *) obj, NULL, h); } static gboolean _idx_obj_partition_equal (const NMDedupMultiIdxType *idx_type, const NMDedupMultiObj *obj_a, const NMDedupMultiObj *obj_b) { return _idx_obj_part ((DedupMultiIdxType *) idx_type, (NMPObject *) obj_a, (NMPObject *) obj_b, NULL); } static const NMDedupMultiIdxTypeClass _dedup_multi_idx_type_class = { .idx_obj_id_hash_update = _idx_obj_id_hash_update, .idx_obj_id_equal = _idx_obj_id_equal, .idx_obj_partitionable = _idx_obj_partitionable, .idx_obj_partition_hash_update = _idx_obj_partition_hash_update, .idx_obj_partition_equal = _idx_obj_partition_equal, }; static void _dedup_multi_idx_type_init (DedupMultiIdxType *idx_type, NMPCacheIdType cache_id_type) { nm_dedup_multi_idx_type_init ((NMDedupMultiIdxType *) idx_type, &_dedup_multi_idx_type_class); idx_type->cache_id_type = cache_id_type; } /*****************************************************************************/ static void _vlan_xgress_qos_mappings_hash_update (guint n_map, const NMVlanQosMapping *map, NMHashState *h) { /* ensure no padding. */ G_STATIC_ASSERT (sizeof (NMVlanQosMapping) == 2 * sizeof (guint32)); nm_hash_update_val (h, n_map); if (n_map) nm_hash_update (h, map, n_map * sizeof (*map)); } 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, NMVlanQosMapping **dst_map, guint src_n_map, const NMVlanQosMapping *src_map) { if (src_n_map == 0) { nm_clear_g_free (dst_map); *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) { nm_clear_g_free (dst_map); *dst_n_map = src_n_map; if (src_n_map > 0) *dst_map = nm_memdup (src_map, sizeof (*src_map) * src_n_map); } } /*****************************************************************************/ static void _wireguard_allowed_ip_hash_update (const NMPWireGuardAllowedIP *ip, NMHashState *h) { nm_hash_update_vals (h, ip->family, ip->mask); if (ip->family == AF_INET) nm_hash_update_val (h, ip->addr.addr4); else if (ip->family == AF_INET6) nm_hash_update_val (h, ip->addr.addr6); } static int _wireguard_allowed_ip_cmp (const NMPWireGuardAllowedIP *a, const NMPWireGuardAllowedIP *b) { NM_CMP_SELF (a, b); NM_CMP_FIELD (a, b, family); NM_CMP_FIELD (a, b, mask); if (a->family == AF_INET) NM_CMP_FIELD (a, b, addr.addr4); else if (a->family == AF_INET6) NM_CMP_FIELD_IN6ADDR (a, b, addr.addr6); return 0; } static void _wireguard_peer_hash_update (const NMPWireGuardPeer *peer, NMHashState *h) { guint i; nm_hash_update (h, peer->public_key, sizeof (peer->public_key)); nm_hash_update (h, peer->preshared_key, sizeof (peer->preshared_key)); nm_hash_update_vals (h, peer->persistent_keepalive_interval, peer->allowed_ips_len, peer->rx_bytes, peer->tx_bytes, peer->last_handshake_time.tv_sec, peer->last_handshake_time.tv_nsec); nm_sock_addr_union_hash_update (&peer->endpoint, h); for (i = 0; i < peer->allowed_ips_len; i++) _wireguard_allowed_ip_hash_update (&peer->allowed_ips[i], h); } static int _wireguard_peer_cmp (const NMPWireGuardPeer *a, const NMPWireGuardPeer *b) { guint i; NM_CMP_SELF (a, b); NM_CMP_FIELD (a, b, last_handshake_time.tv_sec); NM_CMP_FIELD (a, b, last_handshake_time.tv_nsec); NM_CMP_FIELD (a, b, rx_bytes); NM_CMP_FIELD (a, b, tx_bytes); NM_CMP_FIELD (a, b, allowed_ips_len); NM_CMP_FIELD (a, b, persistent_keepalive_interval); NM_CMP_FIELD (a, b, endpoint.sa.sa_family); NM_CMP_FIELD_MEMCMP (a, b, public_key); NM_CMP_FIELD_MEMCMP (a, b, preshared_key); NM_CMP_RETURN (nm_sock_addr_union_cmp (&a->endpoint, &b->endpoint)); for (i = 0; i < a->allowed_ips_len; i++) { NM_CMP_RETURN (_wireguard_allowed_ip_cmp (&a->allowed_ips[i], &b->allowed_ips[i])); } return 0; } /*****************************************************************************/ static const char * _link_get_driver (struct udev_device *udevice, const char *kind, int ifindex) { const char *driver = NULL; nm_assert (kind == g_intern_string (kind)); if (udevice) { driver = nmp_utils_udev_get_driver (udevice); if (driver) return driver; } if (kind) return kind; if (ifindex > 0) { NMPUtilsEthtoolDriverInfo driver_info; if (nmp_utils_ethtool_get_driver_info (ifindex, &driver_info)) { if (driver_info.driver[0]) return g_intern_string (driver_info.driver); } } return "unknown"; } void _nmp_object_fixup_link_udev_fields (NMPObject **obj_new, NMPObject *obj_orig, gboolean use_udev) { const char *driver = NULL; gboolean initialized = FALSE; NMPObject *obj; nm_assert (obj_orig || *obj_new); nm_assert (obj_new); nm_assert (!obj_orig || NMP_OBJECT_GET_TYPE (obj_orig) == NMP_OBJECT_TYPE_LINK); nm_assert (!*obj_new || NMP_OBJECT_GET_TYPE (*obj_new) == NMP_OBJECT_TYPE_LINK); obj = *obj_new ?: obj_orig; /* The link contains internal fields that are combined by * properties from netlink and udev. Update those properties */ /* When a link is not in netlink, its udev fields don't matter. */ if (obj->_link.netlink.is_in_netlink) { driver = _link_get_driver (obj->_link.udev.device, obj->link.kind, obj->link.ifindex); if (obj->_link.udev.device) initialized = TRUE; else if (!use_udev) { /* If we don't use udev, we immediately mark the link as initialized. * * For that, we consult @use_udev argument, that is cached via * nmp_cache_use_udev_get(). It is on purpose not to test * for a writable /sys on every call. A minor reason for that is * performance, but the real reason is reproducibility. * */ initialized = TRUE; } } if ( nm_streq0 (obj->link.driver, driver) && obj->link.initialized == initialized) return; if (!*obj_new) obj = *obj_new = nmp_object_clone (obj, FALSE); obj->link.driver = driver; obj->link.initialized = initialized; } static void _nmp_object_fixup_link_master_connected (NMPObject **obj_new, NMPObject *obj_orig, const NMPCache *cache) { NMPObject *obj; nm_assert (obj_orig || *obj_new); nm_assert (obj_new); nm_assert (!obj_orig || NMP_OBJECT_GET_TYPE (obj_orig) == NMP_OBJECT_TYPE_LINK); nm_assert (!*obj_new || NMP_OBJECT_GET_TYPE (*obj_new) == NMP_OBJECT_TYPE_LINK); obj = *obj_new ?: obj_orig; if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL)) { if (!*obj_new) obj = *obj_new = nmp_object_clone (obj, FALSE); obj->link.connected = !obj->link.connected; } } /*****************************************************************************/ static void _vt_cmd_obj_dispose_link (NMPObject *obj) { if (obj->_link.udev.device) { udev_device_unref (obj->_link.udev.device); obj->_link.udev.device = NULL; } g_clear_object (&obj->_link.ext_data); 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 void _wireguard_clear (NMPObjectLnkWireGuard *lnk) { guint i; nm_explicit_bzero (lnk->_public.private_key, sizeof (lnk->_public.private_key)); for (i = 0; i < lnk->peers_len; i++) { NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &lnk->peers[i]; nm_explicit_bzero (peer->preshared_key, sizeof (peer->preshared_key)); } g_free ((gpointer) lnk->peers); g_free ((gpointer) lnk->_allowed_ips_buf); } static void _vt_cmd_obj_dispose_lnk_wireguard (NMPObject *obj) { _wireguard_clear (&obj->_lnk_wireguard); } 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->parent._ref_count = 1; return obj; } NMPObject * nmp_object_new (NMPObjectType obj_type, gconstpointer plobj) { const NMPClass *klass = nmp_class_from_type (obj_type); NMPObject *obj; obj = _nmp_object_new_from_class (klass); if (plobj) memcpy (&obj->object, plobj, klass->sizeof_public); return obj; } NMPObject * nmp_object_new_link (int ifindex) { NMPObject *obj; obj = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL); obj->link.ifindex = ifindex; return obj; } /*****************************************************************************/ static void _nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass) { nm_assert (obj); nm_assert (klass); *obj = (NMPObject) { .parent = { .klass = (const NMDedupMultiObjClass *) klass, ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, }, }; } static NMPObject * _nmp_object_stackinit_from_type (NMPObject *obj, NMPObjectType obj_type) { const NMPClass *klass; nm_assert (obj); klass = nmp_class_from_type (obj_type); nm_assert (klass); *obj = (NMPObject) { .parent = { .klass = (const NMDedupMultiObjClass *) klass, ._ref_count = NM_OBJ_REF_COUNT_STACKINIT, }, }; return obj; } const NMPObject * nmp_object_stackinit (NMPObject *obj, NMPObjectType obj_type, gconstpointer plobj) { const NMPClass *klass = nmp_class_from_type (obj_type); _nmp_object_stackinit_from_class (obj, klass); if (plobj) memcpy (&obj->object, plobj, klass->sizeof_public); return obj; } const NMPObject * nmp_object_stackinit_id (NMPObject *obj, const NMPObject *src) { const NMPClass *klass; nm_assert (NMP_OBJECT_IS_VALID (src)); nm_assert (obj); klass = NMP_OBJECT_GET_CLASS (src); _nmp_object_stackinit_from_class (obj, klass); if (klass->cmd_plobj_id_copy) klass->cmd_plobj_id_copy (&obj->object, &src->object); return obj; } const NMPObject * nmp_object_stackinit_id_link (NMPObject *obj, int ifindex) { _nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_LINK); obj->link.ifindex = ifindex; return obj; } const NMPObject * nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, guint8 plen, guint32 peer_address) { _nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_IP4_ADDRESS); obj->ip4_address.ifindex = ifindex; obj->ip4_address.address = address; obj->ip4_address.plen = plen; obj->ip4_address.peer_address = peer_address; return obj; } const NMPObject * nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address) { _nmp_object_stackinit_from_type (obj, NMP_OBJECT_TYPE_IP6_ADDRESS); obj->ip4_address.ifindex = ifindex; if (address) obj->ip6_address.address = *address; return obj; } /*****************************************************************************/ 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_utils_to_string_buffer)]; 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_snprintf (buf, buf_size, "[%s,%p,%u,%calive,%cvisible; %s]", klass->obj_type_name, 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))); 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"); } } 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 *b = buf; 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: nm_utils_strbuf_append (&b, &buf_size, "[%s,%p,%u,%calive,%cvisible,%cin-nl,%p; ", klass->obj_type_name, obj, obj->parent._ref_count, nmp_object_is_alive (obj) ? '+' : '-', nmp_object_is_visible (obj) ? '+' : '-', obj->_link.netlink.is_in_netlink ? '+' : '-', obj->_link.udev.device); NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object, b, buf_size); nm_utils_strbuf_seek_end (&b, &buf_size); if (obj->_link.netlink.lnk) { nm_utils_strbuf_append_str (&b, &buf_size, "; "); nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_ALL, b, buf_size); nm_utils_strbuf_seek_end (&b, &buf_size); } nm_utils_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); if (obj->_link.netlink.lnk) { nm_utils_strbuf_seek_end (&b, &buf_size); nm_utils_strbuf_append_str (&b, &buf_size, "; "); nmp_object_to_string (obj->_link.netlink.lnk, NMP_OBJECT_TO_STRING_PUBLIC, b, 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; 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,%u,%calive,%cvisible; %s]", klass->obj_type_name, 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; case NMP_OBJECT_TO_STRING_PUBLIC: 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"); } } static const char * _vt_cmd_obj_to_string_lnk_wireguard (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size) { const NMPClass *klass; char buf2[sizeof (_nm_utils_to_string_buffer)]; char *b; guint i; 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: b = buf; nm_utils_strbuf_append (&b, &buf_size, "[%s,%p,%u,%calive,%cvisible; %s" "%s", klass->obj_type_name, 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)), obj->_lnk_wireguard.peers_len > 0 ? " peers {" : ""); for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) { const NMPWireGuardPeer *peer = &obj->_lnk_wireguard.peers[i]; nm_utils_strbuf_append_str (&b, &buf_size, " { "); nm_platform_wireguard_peer_to_string (peer, b, buf_size); nm_utils_strbuf_seek_end (&b, &buf_size); nm_utils_strbuf_append_str (&b, &buf_size, " }"); } if (obj->_lnk_wireguard.peers_len) nm_utils_strbuf_append_str (&b, &buf_size, " }"); 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"); } } #define _vt_cmd_plobj_to_string_id(type, plat_type, ...) \ static const char * \ _vt_cmd_plobj_to_string_id_##type (const NMPlatformObject *_obj, char *buf, 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]; \ \ 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%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 (ip6_address, NMPlatformIP6Address, "%d: %s", obj->ifindex, _nm_utils_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) { const NMPClass *klass; g_return_if_fail (NMP_OBJECT_IS_VALID (obj)); klass = NMP_OBJECT_GET_CLASS (obj); nm_hash_update_val (h, klass->obj_type); 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); else nm_hash_update_val (h, obj); } static void _vt_cmd_obj_hash_update_link (const NMPObject *obj, NMHashState *h) { nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LINK); nm_platform_link_hash_update (&obj->link, h); nm_hash_update_vals (h, obj->_link.netlink.is_in_netlink, obj->_link.wireguard_family_id, obj->_link.udev.device); if (obj->_link.netlink.lnk) nmp_object_hash_update (obj->_link.netlink.lnk, h); } static void _vt_cmd_obj_hash_update_lnk_vlan (const NMPObject *obj, NMHashState *h) { nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_VLAN); 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, h); _vlan_xgress_qos_mappings_hash_update (obj->_lnk_vlan.n_egress_qos_map, obj->_lnk_vlan.egress_qos_map, h); } static void _vt_cmd_obj_hash_update_lnk_wireguard (const NMPObject *obj, NMHashState *h) { guint i; nm_assert (NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_LNK_WIREGUARD); nm_platform_lnk_wireguard_hash_update (&obj->lnk_wireguard, h); nm_hash_update_val (h, obj->_lnk_wireguard.peers_len); for (i = 0; i < obj->_lnk_wireguard.peers_len; i++) _wireguard_peer_hash_update (&obj->_lnk_wireguard.peers[i], h); } int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2) { const NMPClass *klass1, *klass2; 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); 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 (klass1->cmd_obj_cmp) return klass1->cmd_obj_cmp (obj1, obj2); return klass1->cmd_plobj_cmp (&obj1->object, &obj2->object); } static int _vt_cmd_obj_cmp_link (const NMPObject *obj1, const NMPObject *obj2) { 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)); NM_CMP_DIRECT (obj1->_link.wireguard_family_id, obj2->_link.wireguard_family_id); 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. * * Have this check as very last. */ return (obj1->_link.udev.device < obj2->_link.udev.device) ? -1 : 1; } return 0; } static int _vt_cmd_obj_cmp_lnk_vlan (const NMPObject *obj1, const NMPObject *obj2) { int c; c = nm_platform_lnk_vlan_cmp (&obj1->lnk_vlan, &obj2->lnk_vlan); if (c) return c; 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; 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); return c; } static int _vt_cmd_obj_cmp_lnk_wireguard (const NMPObject *obj1, const NMPObject *obj2) { guint i; NM_CMP_RETURN (nm_platform_lnk_wireguard_cmp (&obj1->lnk_wireguard, &obj2->lnk_wireguard)); NM_CMP_FIELD (obj1, obj2, _lnk_wireguard.peers_len); for (i = 0; i < obj1->_lnk_wireguard.peers_len; i++) NM_CMP_RETURN (_wireguard_peer_cmp (&obj1->_lnk_wireguard.peers[i], &obj2->_lnk_wireguard.peers[i])); return 0; } /* @src is a const object, which is not entirely correct for link types, where * we increase the ref count for src->_link.udev.device. * Hence, nmp_object_copy() can violate the const promise of @src. * */ void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only) { g_return_if_fail (NMP_OBJECT_IS_VALID (dst)); g_return_if_fail (NMP_OBJECT_IS_VALID (src)); g_return_if_fail (!NMP_OBJECT_IS_STACKINIT (dst)); if (src != dst) { const NMPClass *klass = NMP_OBJECT_GET_CLASS (dst); g_return_if_fail (klass == NMP_OBJECT_GET_CLASS (src)); 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) udev_device_ref (src->_link.udev.device); if (dst->_link.udev.device) udev_device_unref (dst->_link.udev.device); dst->_link.udev.device = src->_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.netlink.lnk = src->_link.netlink.lnk; } if (dst->_link.ext_data != src->_link.ext_data) { if (dst->_link.ext_data) g_clear_object (&dst->_link.ext_data); if (src->_link.ext_data) dst->_link.ext_data = g_object_ref (src->_link.ext_data); } 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, NM_UNCONST_PPTR (NMVlanQosMapping, &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, NM_UNCONST_PPTR (NMVlanQosMapping, &dst->_lnk_vlan.egress_qos_map), src->_lnk_vlan.n_egress_qos_map, src->_lnk_vlan.egress_qos_map); } static void _vt_cmd_obj_copy_lnk_wireguard (NMPObject *dst, const NMPObject *src) { guint i; nm_assert (dst != src); _wireguard_clear (&dst->_lnk_wireguard); dst->_lnk_wireguard = src->_lnk_wireguard; dst->_lnk_wireguard.peers = nm_memdup (dst->_lnk_wireguard.peers, sizeof (NMPWireGuardPeer) * dst->_lnk_wireguard.peers_len); dst->_lnk_wireguard._allowed_ips_buf = nm_memdup (dst->_lnk_wireguard._allowed_ips_buf, sizeof (NMPWireGuardAllowedIP) * dst->_lnk_wireguard._allowed_ips_buf_len); /* all the peers' pointers point into the buffer. They need to be readjusted. */ for (i = 0; i < dst->_lnk_wireguard.peers_len; i++) { NMPWireGuardPeer *peer = (NMPWireGuardPeer *) &dst->_lnk_wireguard.peers[i]; if (peer->allowed_ips_len == 0) { nm_assert (!peer->allowed_ips); continue; } nm_assert (dst->_lnk_wireguard._allowed_ips_buf_len > 0); nm_assert (src->_lnk_wireguard._allowed_ips_buf); nm_assert (peer->allowed_ips >= src->_lnk_wireguard._allowed_ips_buf); nm_assert (&peer->allowed_ips[peer->allowed_ips_len] <= &src->_lnk_wireguard._allowed_ips_buf[src->_lnk_wireguard._allowed_ips_buf_len]); peer->allowed_ips = &dst->_lnk_wireguard._allowed_ips_buf[peer->allowed_ips - src->_lnk_wireguard._allowed_ips_buf]; } nm_assert (nmp_object_equal (src, dst)); } #define _vt_cmd_plobj_id_copy(type, plat_type, cmd) \ static void \ _vt_cmd_plobj_id_copy_##type (NMPlatformObject *_dst, const NMPlatformObject *_src) \ { \ plat_type *const dst = (plat_type *) _dst; \ const plat_type *const src = (const plat_type *) _src; \ { cmd } \ } _vt_cmd_plobj_id_copy (link, NMPlatformLink, { dst->ifindex = src->ifindex; }); _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->address = src->address; }); _vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, { *dst = *src; nm_assert (nm_platform_ip4_route_cmp (dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); }); _vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, { *dst = *src; nm_assert (nm_platform_ip6_route_cmp (dst, src, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID) == 0); }); _vt_cmd_plobj_id_copy (routing_rule, NMPlatformRoutingRule, { *dst = *src; nm_assert (nm_platform_routing_rule_cmp (dst, src, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID) == 0); }); /* Uses internally nmp_object_copy(), hence it also violates the const * promise for @obj. * */ NMPObject * nmp_object_clone (const NMPObject *obj, gboolean id_only) { NMPObject *dst; if (!obj) return NULL; g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL); dst = _nmp_object_new_from_class (NMP_OBJECT_GET_CLASS (obj)); nmp_object_copy (dst, obj, 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) \ { \ const plat_type *const obj1 = (const plat_type *) _obj1; \ const plat_type *const obj2 = (const plat_type *) _obj2; \ \ NM_CMP_SELF (obj1, obj2); \ { cmd; } \ return 0; \ } _vt_cmd_plobj_id_cmp (link, NMPlatformLink, NM_CMP_FIELD (obj1, obj2, ifindex); ) _vt_cmd_plobj_id_cmp (ip4_address, NMPlatformIP4Address, NM_CMP_FIELD (obj1, obj2, ifindex); NM_CMP_FIELD (obj1, obj2, plen); NM_CMP_FIELD (obj1, obj2, address); /* for IPv4 addresses, you can add the same local address with differing peer-adddress * (IFA_ADDRESS), provided that their net-part differs. */ NM_CMP_DIRECT_IN4ADDR_SAME_PREFIX (obj1->peer_address, obj2->peer_address, obj1->plen); ) _vt_cmd_plobj_id_cmp (ip6_address, NMPlatformIP6Address, NM_CMP_FIELD (obj1, obj2, ifindex); /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ NM_CMP_FIELD_IN6ADDR (obj1, obj2, address); ) _vt_cmd_plobj_id_cmp (qdisc, NMPlatformQdisc, NM_CMP_FIELD (obj1, obj2, ifindex); NM_CMP_FIELD (obj1, obj2, parent); ) _vt_cmd_plobj_id_cmp (tfilter, NMPlatformTfilter, NM_CMP_FIELD (obj1, obj2, ifindex); NM_CMP_FIELD (obj1, obj2, handle); ) static int _vt_cmd_plobj_id_cmp_ip4_route (const NMPlatformObject *obj1, const NMPlatformObject *obj2) { return nm_platform_ip4_route_cmp ((NMPlatformIP4Route *) obj1, (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 ((NMPlatformIP6Route *) obj1, (NMPlatformIP6Route *) obj2, NM_PLATFORM_IP_ROUTE_CMP_TYPE_ID); } static int _vt_cmd_plobj_id_cmp_routing_rule (const NMPlatformObject *obj1, const NMPlatformObject *obj2) { return nm_platform_routing_rule_cmp ((NMPlatformRoutingRule *) obj1, (NMPlatformRoutingRule *) obj2, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID); } 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) { NMHashState h; if (!obj) return nm_hash_static (914932607u); nm_hash_init (&h, 914932607u); nmp_object_id_hash_update (obj, &h); return nm_hash_complete (&h); } #define _vt_cmd_plobj_id_hash_update(type, plat_type, cmd) \ static void \ _vt_cmd_plobj_id_hash_update_##type (const NMPlatformObject *_obj, NMHashState *h) \ { \ const plat_type *const obj = (const plat_type *) _obj; \ { cmd; } \ } _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, obj->ifindex, 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)); }) _vt_cmd_plobj_id_hash_update (ip6_address, NMPlatformIP6Address, { nm_hash_update_vals (h, obj->ifindex, /* for IPv6 addresses, the prefix length is not part of the primary identifier. */ 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); }) _vt_cmd_plobj_id_hash_update (routing_rule, NMPlatformRoutingRule, { nm_platform_routing_rule_hash_update (obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID, h); }) _vt_cmd_plobj_id_hash_update (qdisc, NMPlatformQdisc, { nm_hash_update_vals (h, obj->ifindex, obj->parent); }) _vt_cmd_plobj_id_hash_update (tfilter, NMPlatformTfilter, { nm_hash_update_vals (h, obj->ifindex, obj->handle); }) static void _vt_cmd_plobj_hash_update_ip4_route (const NMPlatformObject *obj, NMHashState *h) { return nm_platform_ip4_route_hash_update ((const NMPlatformIP4Route *) obj, NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL, h); } static void _vt_cmd_plobj_hash_update_ip6_route (const NMPlatformObject *obj, NMHashState *h) { 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_routing_rule (const NMPlatformObject *obj, NMHashState *h) { return nm_platform_routing_rule_hash_update ((const NMPlatformRoutingRule *) obj, NM_PLATFORM_ROUTING_RULE_CMP_TYPE_FULL, h); } gboolean nmp_object_is_alive (const NMPObject *obj) { const NMPClass *klass; /* for convenience, allow NULL. */ if (!obj) return FALSE; klass = NMP_OBJECT_GET_CLASS (obj); return !klass->cmd_obj_is_alive || klass->cmd_obj_is_alive (obj); } static gboolean _vt_cmd_obj_is_alive_link (const NMPObject *obj) { return NMP_OBJECT_CAST_LINK (obj)->ifindex > 0 && (obj->_link.netlink.is_in_netlink || obj->_link.udev.device); } static gboolean _vt_cmd_obj_is_alive_ipx_address (const NMPObject *obj) { return NMP_OBJECT_CAST_IP_ADDRESS (obj)->ifindex > 0; } static gboolean _vt_cmd_obj_is_alive_ipx_route (const NMPObject *obj) { /* We want to ignore routes that are RTM_F_CLONED but we still * let nmp_object_from_nl() create such route objects, instead of * returning NULL right away. * * The idea is, that if we have the same route (according to its id) * in the cache with !RTM_F_CLONED, an update that changes the route * to be RTM_F_CLONED must remove the instance. * * If nmp_object_from_nl() would just return NULL, we couldn't look * into the cache to see if it contains a route that now disappears * (because it changed to be cloned). * * Instead we create a dead object, and nmp_cache_update_netlink() * will remove the old version of the update. **/ return NMP_OBJECT_CAST_IP_ROUTE (obj)->ifindex > 0 && !NM_FLAGS_HAS (obj->ip_route.r_rtm_flags, RTM_F_CLONED); } static gboolean _vt_cmd_obj_is_alive_routing_rule (const NMPObject *obj) { return NM_IN_SET (obj->routing_rule.addr_family, AF_INET, AF_INET6); } static gboolean _vt_cmd_obj_is_alive_qdisc (const NMPObject *obj) { return NMP_OBJECT_CAST_QDISC (obj)->ifindex > 0; } static gboolean _vt_cmd_obj_is_alive_tfilter (const NMPObject *obj) { return NMP_OBJECT_CAST_TFILTER (obj)->ifindex > 0; } gboolean nmp_object_is_visible (const NMPObject *obj) { const NMPClass *klass; /* for convenience, allow NULL. */ if (!obj) return FALSE; klass = NMP_OBJECT_GET_CLASS (obj); /* a dead object is never visible. */ if ( klass->cmd_obj_is_alive && !klass->cmd_obj_is_alive (obj)) return FALSE; return !klass->cmd_obj_is_visible || klass->cmd_obj_is_visible (obj); } static gboolean _vt_cmd_obj_is_visible_link (const NMPObject *obj) { return obj->_link.netlink.is_in_netlink && obj->link.name[0]; } /*****************************************************************************/ static const guint8 _supported_cache_ids_object[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, 0, }; static const guint8 _supported_cache_ids_link[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, 0, }; static const guint8 _supported_cache_ids_ipx_address[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, 0, }; static const guint8 _supported_cache_ids_ipx_route[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX, NMP_CACHE_ID_TYPE_DEFAULT_ROUTES, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, 0, }; static const guint8 _supported_cache_ids_routing_rules[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY, 0, }; /*****************************************************************************/ static void _vt_dedup_obj_destroy (NMDedupMultiObj *obj) { NMPObject *o = (NMPObject *) obj; const NMPClass *klass; nm_assert (o->parent._ref_count == 0); nm_assert (!o->parent._multi_idx); 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); } static const NMDedupMultiObj * _vt_dedup_obj_clone (const NMDedupMultiObj *obj) { return (const NMDedupMultiObj *) nmp_object_clone ((const NMPObject *) obj, FALSE); } #define DEDUP_MULTI_OBJ_CLASS_INIT() \ { \ .obj_clone = _vt_dedup_obj_clone, \ .obj_destroy = _vt_dedup_obj_destroy, \ .obj_full_hash_update = (void (*)(const NMDedupMultiObj *obj, NMHashState *h)) nmp_object_hash_update, \ .obj_full_equal = (gboolean (*)(const NMDedupMultiObj *obj_a, const NMDedupMultiObj *obj_b)) nmp_object_equal, \ } /*****************************************************************************/ static NMDedupMultiIdxType * _idx_type_get (const NMPCache *cache, NMPCacheIdType cache_id_type) { nm_assert (cache); nm_assert (cache_id_type > NMP_CACHE_ID_TYPE_NONE); nm_assert (cache_id_type <= NMP_CACHE_ID_TYPE_MAX); nm_assert ((int) cache_id_type - 1 >= 0); nm_assert ((int) cache_id_type - 1 < G_N_ELEMENTS (cache->idx_types)); return (NMDedupMultiIdxType *) &cache->idx_types[cache_id_type - 1]; } gboolean nmp_cache_use_udev_get (const NMPCache *cache) { g_return_val_if_fail (cache, TRUE); return cache->use_udev; } /*****************************************************************************/ gboolean nmp_cache_link_connected_for_slave (int ifindex_master, const NMPObject *slave) { nm_assert (NMP_OBJECT_GET_TYPE (slave) == NMP_OBJECT_TYPE_LINK); return ifindex_master > 0 && slave->link.master == ifindex_master && slave->link.connected && nmp_object_is_visible (slave); } /** * nmp_cache_link_connected_needs_toggle: * @cache: the platform cache * @master: the link object, that is checked whether its connected property * needs to be toggled. * @potential_slave: (allow-none): an additional link object that is treated * as if it was inside @cache. If given, it shaddows a link in the cache * with the same ifindex. * @ignore_slave: (allow-none): if set, the check will pretend that @ignore_slave * is not in the cache. * * NMPlatformLink has two connected flags: (master->link.flags&IFF_LOWER_UP) (as reported * from netlink) and master->link.connected. For bond and bridge master, kernel reports * those links as IFF_LOWER_UP if they have no slaves attached. We want to present instead * a combined @connected flag that shows masters without slaves as down. * * Check if the connected flag of @master should be toggled according to the content * of @cache (including @potential_slave). * * Returns: %TRUE, if @master->link.connected should be flipped/toggled. **/ gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave) { gboolean is_lower_up = FALSE; if ( !master || NMP_OBJECT_GET_TYPE (master) != NMP_OBJECT_TYPE_LINK || master->link.ifindex <= 0 || !nmp_object_is_visible (master) || !NM_IN_SET (master->link.type, NM_LINK_TYPE_BRIDGE, NM_LINK_TYPE_BOND)) return FALSE; /* if native IFF_LOWER_UP is down, link.connected must also be down * regardless of the slaves. */ if (!NM_FLAGS_HAS (master->link.n_ifi_flags, IFF_LOWER_UP)) return !!master->link.connected; if (potential_slave && NMP_OBJECT_GET_TYPE (potential_slave) != NMP_OBJECT_TYPE_LINK) potential_slave = NULL; if ( potential_slave && nmp_cache_link_connected_for_slave (master->link.ifindex, potential_slave)) is_lower_up = TRUE; else { NMPLookup lookup; NMDedupMultiIter iter; const NMPlatformLink *link = NULL; nmp_cache_iter_for_each_link (&iter, nmp_cache_lookup (cache, nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK)), &link) { const NMPObject *obj = NMP_OBJECT_UP_CAST ((NMPlatformObject *) link); if ( (!potential_slave || potential_slave->link.ifindex != link->ifindex) && ignore_slave != obj && nmp_cache_link_connected_for_slave (master->link.ifindex, obj)) { is_lower_up = TRUE; break; } } } return !!master->link.connected != is_lower_up; } /** * nmp_cache_link_connected_needs_toggle_by_ifindex: * @cache: * @master_ifindex: the ifindex of a potential master that should be checked * whether it needs toggling. * @potential_slave: (allow-none): passed to nmp_cache_link_connected_needs_toggle(). * It considers @potential_slave as being inside the cache, replacing an existing * link with the same ifindex. * @ignore_slave: (allow-onne): passed to nmp_cache_link_connected_needs_toggle(). * * The flag obj->link.connected depends on the state of other links in the * @cache. See also nmp_cache_link_connected_needs_toggle(). Given an ifindex * of a master, check if the cache contains such a master link that needs * toggling of the connected flag. * * Returns: NULL if there is no master link with ifindex @master_ifindex that should be toggled. * Otherwise, return the link object from inside the cache with the given ifindex. * The connected flag of that master should be toggled. */ const NMPObject * nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave) { const NMPObject *master; if (master_ifindex > 0) { master = nmp_cache_lookup_link (cache, master_ifindex); if (nmp_cache_link_connected_needs_toggle (cache, master, potential_slave, ignore_slave)) return master; } return NULL; } /*****************************************************************************/ static const NMDedupMultiEntry * _lookup_entry_with_idx_type (const NMPCache *cache, NMPCacheIdType cache_id_type, const NMPObject *obj) { const NMDedupMultiEntry *entry; nm_assert (cache); nm_assert (NMP_OBJECT_IS_VALID (obj)); entry = nm_dedup_multi_index_lookup_obj (cache->multi_idx, _idx_type_get (cache, cache_id_type), obj); nm_assert (!entry || ( NMP_OBJECT_IS_VALID (entry->obj) && NMP_OBJECT_GET_CLASS (entry->obj) == NMP_OBJECT_GET_CLASS (obj))); return entry; } static const NMDedupMultiEntry * _lookup_entry (const NMPCache *cache, const NMPObject *obj) { return _lookup_entry_with_idx_type (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj); } const NMDedupMultiEntry * nmp_cache_lookup_entry_with_idx_type (const NMPCache *cache, NMPCacheIdType cache_id_type, const NMPObject *obj) { g_return_val_if_fail (cache, NULL); g_return_val_if_fail (obj, NULL); g_return_val_if_fail (cache_id_type > NMP_CACHE_ID_TYPE_NONE && cache_id_type <= NMP_CACHE_ID_TYPE_MAX, NULL); return _lookup_entry_with_idx_type (cache, cache_id_type, obj); } const NMDedupMultiEntry * nmp_cache_lookup_entry (const NMPCache *cache, const NMPObject *obj) { g_return_val_if_fail (cache, NULL); g_return_val_if_fail (obj, NULL); return _lookup_entry (cache, obj); } const NMDedupMultiEntry * nmp_cache_lookup_entry_link (const NMPCache *cache, int ifindex) { NMPObject obj_needle; g_return_val_if_fail (cache, NULL); g_return_val_if_fail (ifindex > 0, NULL); nmp_object_stackinit_id_link (&obj_needle, ifindex); return _lookup_entry (cache, &obj_needle); } const NMPObject * nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj) { return nm_dedup_multi_entry_get_obj (nmp_cache_lookup_entry (cache, obj)); } const NMPObject * nmp_cache_lookup_link (const NMPCache *cache, int ifindex) { return nm_dedup_multi_entry_get_obj (nmp_cache_lookup_entry_link (cache, ifindex)); } /*****************************************************************************/ const NMDedupMultiHeadEntry * nmp_cache_lookup_all (const NMPCache *cache, NMPCacheIdType cache_id_type, const NMPObject *select_obj) { nm_assert (cache); nm_assert (NMP_OBJECT_IS_VALID (select_obj)); return nm_dedup_multi_index_lookup_head (cache->multi_idx, _idx_type_get (cache, cache_id_type), select_obj); } static const NMPLookup * _L (const NMPLookup *lookup) { #if NM_MORE_ASSERTS DedupMultiIdxType idx_type; nm_assert (lookup); _dedup_multi_idx_type_init (&idx_type, lookup->cache_id_type); nm_assert (idx_type.parent.klass->idx_obj_partitionable ((NMDedupMultiIdxType *) &idx_type, (NMDedupMultiObj *) &lookup->selector_obj)); #endif return lookup; } const NMPLookup * nmp_lookup_init_obj_type (NMPLookup *lookup, NMPObjectType obj_type) { nm_assert (lookup); switch (obj_type) { case NMP_OBJECT_TYPE_LINK: case NMP_OBJECT_TYPE_IP4_ADDRESS: case NMP_OBJECT_TYPE_IP6_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: case NMP_OBJECT_TYPE_ROUTING_RULE: case NMP_OBJECT_TYPE_QDISC: case NMP_OBJECT_TYPE_TFILTER: _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_TYPE; return _L (lookup); default: nm_assert_not_reached (); return NULL; } } const NMPLookup * nmp_lookup_init_link_by_ifname (NMPLookup *lookup, const char *ifname) { NMPObject *o; nm_assert (lookup); nm_assert (ifname); nm_assert (strlen (ifname) < IFNAMSIZ); o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_LINK); if (g_strlcpy (o->link.name, ifname, sizeof (o->link.name)) >= sizeof (o->link.name)) g_return_val_if_reached (NULL); lookup->cache_id_type = NMP_CACHE_ID_TYPE_LINK_BY_IFNAME; return _L (lookup); } const NMPLookup * nmp_lookup_init_object (NMPLookup *lookup, NMPObjectType obj_type, int ifindex) { NMPObject *o; nm_assert (lookup); nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE, NMP_OBJECT_TYPE_QDISC, NMP_OBJECT_TYPE_TFILTER)); if (ifindex <= 0) { return nmp_lookup_init_obj_type (lookup, obj_type); } o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); o->obj_with_ifindex.ifindex = ifindex; lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_IFINDEX; return _L (lookup); } const NMPLookup * nmp_lookup_init_route_default (NMPLookup *lookup, NMPObjectType obj_type) { NMPObject *o; nm_assert (lookup); nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_IP4_ROUTE, NMP_OBJECT_TYPE_IP6_ROUTE)); o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); o->ip_route.ifindex = 1; lookup->cache_id_type = NMP_CACHE_ID_TYPE_DEFAULT_ROUTES; return _L (lookup); } const NMPLookup * nmp_lookup_init_route_by_weak_id (NMPLookup *lookup, const NMPObject *obj) { const NMPlatformIP4Route *r4; const NMPlatformIP6Route *r6; nm_assert (lookup); switch (NMP_OBJECT_GET_TYPE (obj)) { case NMP_OBJECT_TYPE_IP4_ROUTE: r4 = NMP_OBJECT_CAST_IP4_ROUTE (obj); return nmp_lookup_init_ip4_route_by_weak_id (lookup, r4->network, r4->plen, r4->metric, r4->tos); case NMP_OBJECT_TYPE_IP6_ROUTE: r6 = NMP_OBJECT_CAST_IP6_ROUTE (obj); return nmp_lookup_init_ip6_route_by_weak_id (lookup, &r6->network, r6->plen, r6->metric, &r6->src, r6->src_plen); default: nm_assert_not_reached (); return NULL; } } const NMPLookup * nmp_lookup_init_ip4_route_by_weak_id (NMPLookup *lookup, in_addr_t network, guint plen, guint32 metric, guint8 tos) { NMPObject *o; nm_assert (lookup); o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP4_ROUTE); o->ip4_route.ifindex = 1; o->ip4_route.plen = plen; o->ip4_route.metric = metric; if (network) o->ip4_route.network = network; o->ip4_route.tos = tos; lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; return _L (lookup); } const NMPLookup * nmp_lookup_init_ip6_route_by_weak_id (NMPLookup *lookup, const struct in6_addr *network, guint plen, guint32 metric, const struct in6_addr *src, guint8 src_plen) { NMPObject *o; nm_assert (lookup); o = _nmp_object_stackinit_from_type (&lookup->selector_obj, NMP_OBJECT_TYPE_IP6_ROUTE); o->ip6_route.ifindex = 1; o->ip6_route.plen = plen; o->ip6_route.metric = metric; if (network) o->ip6_route.network = *network; if (src) o->ip6_route.src = *src; o->ip6_route.src_plen = src_plen; lookup->cache_id_type = NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID; return _L (lookup); } const NMPLookup * nmp_lookup_init_object_by_addr_family (NMPLookup *lookup, NMPObjectType obj_type, int addr_family) { NMPObject *o; nm_assert (lookup); nm_assert (NM_IN_SET (obj_type, NMP_OBJECT_TYPE_ROUTING_RULE)); if (addr_family == AF_UNSPEC) return nmp_lookup_init_obj_type (lookup, obj_type); nm_assert_addr_family (addr_family); o = _nmp_object_stackinit_from_type (&lookup->selector_obj, obj_type); NMP_OBJECT_CAST_ROUTING_RULE (o)->addr_family = addr_family; lookup->cache_id_type = NMP_CACHE_ID_TYPE_OBJECT_BY_ADDR_FAMILY; return _L (lookup); } /*****************************************************************************/ GArray * nmp_cache_lookup_to_array (const NMDedupMultiHeadEntry *head_entry, NMPObjectType obj_type, gboolean visible_only) { const NMPClass *klass = nmp_class_from_type (obj_type); NMDedupMultiIter iter; const NMPObject *o; GArray *array; g_return_val_if_fail (klass, NULL); array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, head_entry ? head_entry->len : 0); nmp_cache_iter_for_each (&iter, head_entry, &o) { nm_assert (NMP_OBJECT_GET_CLASS (o) == klass); if ( visible_only && !nmp_object_is_visible (o)) continue; g_array_append_vals (array, &o->object, 1); } return array; } /*****************************************************************************/ const NMPObject * nmp_cache_lookup_link_full (const NMPCache *cache, int ifindex, const char *ifname, gboolean visible_only, NMLinkType link_type, NMPObjectMatchFn match_fn, gpointer user_data) { NMPObject obj_needle; const NMPObject *obj; NMDedupMultiIter iter; const NMDedupMultiHeadEntry *head_entry; const NMPlatformLink *link = NULL; NMPLookup lookup; if (ifindex > 0) { obj = nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex)); if ( !obj || (visible_only && !nmp_object_is_visible (obj)) || (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) || (ifname && strcmp (obj->link.name, ifname)) || (match_fn && !match_fn (obj, user_data))) return NULL; return obj; } else if (!ifname && !match_fn) return NULL; else { const NMPObject *obj_best = NULL; if (ifname) { if (strlen (ifname) >= IFNAMSIZ) return NULL; nmp_lookup_init_link_by_ifname (&lookup, ifname); } else nmp_lookup_init_obj_type (&lookup, NMP_OBJECT_TYPE_LINK); head_entry = nmp_cache_lookup (cache, &lookup); nmp_cache_iter_for_each_link (&iter, head_entry, &link) { obj = NMP_OBJECT_UP_CAST (link); if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) continue; if (visible_only && !nmp_object_is_visible (obj)) continue; if (match_fn && !match_fn (obj, user_data)) continue; /* if there are multiple candidates, prefer the visible ones. */ if ( visible_only || nmp_object_is_visible (obj)) return obj; if (!obj_best) obj_best = obj; } return obj_best; } } /*****************************************************************************/ static NMDedupMultiIdxMode _obj_get_add_mode (const NMPObject *obj) { /* new objects are usually appended to the list. Except for * addresses, which are prepended during `ip address add`. * * Actually, for routes it is more complicated, because depending on * `ip route append`, `ip route replace`, `ip route prepend`, the object * will be added at the tail, at the front, or even replace an element * in the list. However, that is handled separately by nmp_cache_update_netlink_route() * and of no concern here. */ if (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ADDRESS, NMP_OBJECT_TYPE_IP6_ADDRESS)) return NM_DEDUP_MULTI_IDX_MODE_PREPEND; return NM_DEDUP_MULTI_IDX_MODE_APPEND; } static void _idxcache_update_order_for_dump (NMPCache *cache, const NMDedupMultiEntry *entry) { const NMPClass *klass; const guint8 *i_idx_type; const NMDedupMultiEntry *entry2; nm_dedup_multi_entry_reorder (entry, NULL, TRUE); klass = NMP_OBJECT_GET_CLASS (entry->obj); for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { NMPCacheIdType id_type = *i_idx_type; if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) continue; entry2 = nm_dedup_multi_index_lookup_obj (cache->multi_idx, _idx_type_get (cache, id_type), entry->obj); if (!entry2) continue; nm_assert (entry2 != entry); nm_assert (entry2->obj == entry->obj); nm_dedup_multi_entry_reorder (entry2, NULL, TRUE); } } static void _idxcache_update_other_cache_ids (NMPCache *cache, NMPCacheIdType cache_id_type, const NMPObject *obj_old, const NMPObject *obj_new, gboolean is_dump) { const NMDedupMultiEntry *entry_new; const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_order; NMDedupMultiIdxType *idx_type; nm_assert (obj_new || obj_old); nm_assert (!obj_new || NMP_OBJECT_GET_TYPE (obj_new) != NMP_OBJECT_TYPE_UNKNOWN); nm_assert (!obj_old || NMP_OBJECT_GET_TYPE (obj_old) != NMP_OBJECT_TYPE_UNKNOWN); nm_assert (!obj_old || !obj_new || NMP_OBJECT_GET_CLASS (obj_new) == NMP_OBJECT_GET_CLASS (obj_old)); nm_assert (!obj_old || !obj_new || !nmp_object_equal (obj_new, obj_old)); nm_assert (!obj_new || obj_new == nm_dedup_multi_index_obj_find (cache->multi_idx, obj_new)); nm_assert (!obj_old || obj_old == nm_dedup_multi_index_obj_find (cache->multi_idx, obj_old)); idx_type = _idx_type_get (cache, cache_id_type); if (obj_old) { entry_old = nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type, obj_old); if (!obj_new) { if (entry_old) nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old); return; } } else entry_old = NULL; if (obj_new) { if ( obj_old && nm_dedup_multi_idx_type_id_equal (idx_type, obj_old, obj_new) && nm_dedup_multi_idx_type_partition_equal (idx_type, obj_old, obj_new)) { /* optimize. We just looked up the @obj_old entry and @obj_new compares equal * according to idx_obj_id_equal(). entry_new is the same as entry_old. */ entry_new = entry_old; } else { entry_new = nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type, obj_new); } if (entry_new) entry_order = entry_new; else if ( entry_old && nm_dedup_multi_idx_type_partition_equal (idx_type, entry_old->obj, obj_new)) entry_order = entry_old; else entry_order = NULL; nm_dedup_multi_index_add_full (cache->multi_idx, idx_type, obj_new, is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : _obj_get_add_mode (obj_new), is_dump ? NULL : entry_order, entry_new ?: NM_DEDUP_MULTI_ENTRY_MISSING, entry_new ? entry_new->head : (entry_order ? entry_order->head : NULL), &entry_new, NULL); #if NM_MORE_ASSERTS if (entry_new) { nm_assert (idx_type->klass->idx_obj_partitionable); nm_assert (idx_type->klass->idx_obj_partition_equal); nm_assert (idx_type->klass->idx_obj_partitionable (idx_type, entry_new->obj)); nm_assert (idx_type->klass->idx_obj_partition_equal (idx_type, (gpointer) obj_new, entry_new->obj)); } #endif } else entry_new = NULL; if ( entry_old && entry_old != entry_new) nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old); } static void _idxcache_update (NMPCache *cache, const NMDedupMultiEntry *entry_old, NMPObject *obj_new, gboolean is_dump, const NMDedupMultiEntry **out_entry_new) { const NMPClass *klass; const guint8 *i_idx_type; NMDedupMultiIdxType *idx_type_o = _idx_type_get (cache, NMP_CACHE_ID_TYPE_OBJECT_TYPE); const NMDedupMultiEntry *entry_new = NULL; nm_auto_nmpobj const NMPObject *obj_old = NULL; /* we update an object in the cache. * * Note that @entry_old MUST be what is currently tracked in multi_idx, and it must * have the same ID as @obj_new. */ nm_assert (cache); nm_assert (entry_old || obj_new); nm_assert (!obj_new || nmp_object_is_alive (obj_new)); nm_assert (!entry_old || entry_old == nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type_o, entry_old->obj)); nm_assert (!obj_new || entry_old == nm_dedup_multi_index_lookup_obj (cache->multi_idx, idx_type_o, obj_new)); nm_assert (!entry_old || entry_old->head->idx_type == idx_type_o); nm_assert ( !entry_old || !obj_new || nm_dedup_multi_idx_type_partition_equal (idx_type_o, entry_old->obj, obj_new)); nm_assert ( !entry_old || !obj_new || nm_dedup_multi_idx_type_id_equal (idx_type_o, entry_old->obj, obj_new)); nm_assert ( !entry_old || !obj_new || ( obj_new->parent.klass == ((const NMPObject *) entry_old->obj)->parent.klass && !obj_new->parent.klass->obj_full_equal ((NMDedupMultiObj *) obj_new, entry_old->obj))); /* keep a reference to the pre-existing entry */ if (entry_old) obj_old = nmp_object_ref (entry_old->obj); /* first update the main index NMP_CACHE_ID_TYPE_OBJECT_TYPE. * We already know the pre-existing @entry old, so all that * nm_dedup_multi_index_add_full() effectively does, is update the * obj reference. * * We also get the new boxed object, which we need below. */ if (obj_new) { nm_auto_nmpobj NMPObject *obj_old2 = NULL; nm_dedup_multi_index_add_full (cache->multi_idx, idx_type_o, obj_new, is_dump ? NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE : _obj_get_add_mode (obj_new), NULL, entry_old ?: NM_DEDUP_MULTI_ENTRY_MISSING, NULL, &entry_new, (const NMDedupMultiObj **) &obj_old2); nm_assert (entry_new); nm_assert (obj_old == obj_old2); nm_assert (!entry_old || entry_old == entry_new); } else nm_dedup_multi_index_remove_entry (cache->multi_idx, entry_old); /* now update all other indexes. We know the previously boxed entry, and the * newly boxed one. */ klass = NMP_OBJECT_GET_CLASS (entry_new ? entry_new->obj : obj_old); for (i_idx_type = klass->supported_cache_ids; *i_idx_type; i_idx_type++) { NMPCacheIdType id_type = *i_idx_type; if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) continue; _idxcache_update_other_cache_ids (cache, id_type, obj_old, entry_new ? entry_new->obj : NULL, is_dump); } NM_SET_OUT (out_entry_new, entry_new); } NMPCacheOpsType nmp_cache_remove (NMPCache *cache, const NMPObject *obj_needle, gboolean equals_by_ptr, gboolean only_dirty, const NMPObject **out_obj_old) { const NMDedupMultiEntry *entry_old; const NMPObject *obj_old; entry_old = _lookup_entry (cache, obj_needle); if (!entry_old) { NM_SET_OUT (out_obj_old, NULL); return NMP_CACHE_OPS_UNCHANGED; } obj_old = entry_old->obj; NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); if ( equals_by_ptr && obj_old != obj_needle) { /* We found an identical object, but we only delete it if it's the same pointer as * @obj_needle. */ return NMP_CACHE_OPS_UNCHANGED; } if ( only_dirty && !entry_old->dirty) { /* the entry is not dirty. Skip. */ return NMP_CACHE_OPS_UNCHANGED; } _idxcache_update (cache, entry_old, NULL, FALSE, NULL); return NMP_CACHE_OPS_REMOVED; } NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, const NMPObject **out_obj_old, const NMPObject **out_obj_new) { const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new = NULL; const NMPObject *obj_old; nm_auto_nmpobj NMPObject *obj_new = NULL; entry_old = _lookup_entry (cache, obj_needle); if (!entry_old) { NM_SET_OUT (out_obj_old, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_UNCHANGED; } obj_old = entry_old->obj; if (NMP_OBJECT_GET_TYPE (obj_needle) == NMP_OBJECT_TYPE_LINK) { /* 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 * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink * flag. */ NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); if (!obj_old->_link.netlink.is_in_netlink) { nm_assert (obj_old->_link.udev.device); NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; } if (!obj_old->_link.udev.device) { /* the update would make @obj_old invalid. Remove it. */ _idxcache_update (cache, entry_old, NULL, FALSE, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_REMOVED; } obj_new = nmp_object_clone (obj_old, FALSE); obj_new->_link.netlink.is_in_netlink = FALSE; _nmp_object_fixup_link_master_connected (&obj_new, NULL, cache); _nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev); _idxcache_update (cache, entry_old, obj_new, FALSE, &entry_new); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_UPDATED; } NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); NM_SET_OUT (out_obj_new, NULL); _idxcache_update (cache, entry_old, NULL, FALSE, NULL); return NMP_CACHE_OPS_REMOVED; } /** * nmp_cache_update_netlink: * @cache: the platform cache * @obj_hand_over: a #NMPObject instance as received from netlink and created via * nmp_object_from_nl(). Especially for link, it must not have the udev * replated fields set. * This instance will be modified and might be put into the cache. When * calling nmp_cache_update_netlink() you hand @obj over to the cache. * Except, that the cache will increment the ref count as appropriate. You * must still unref the obj to release your part of the ownership. * @is_dump: whether this update comes during a dump of object of the same kind. * kernel dumps objects in a certain order, which matters especially for routes. * Before a dump we mark all objects as dirty, and remove all untouched objects * afterwards. Hence, during a dump, every update should move the object to the * end of the list, to obtain the correct order. That means, to use NM_DEDUP_MULTI_IDX_MODE_APPEND_FORCE, * instead of NM_DEDUP_MULTI_IDX_MODE_APPEND. * @out_obj_old: (allow-none) (out): return the object with same ID as @obj_hand_over, * that was in the cache before update. If an object is returned, the caller must * unref it afterwards. * @out_obj_new: (allow-none) (out): return the object from the cache after update. * The caller must unref this object. * * Returns: how the cache changed. * * Even if there was no change in the cache (NMP_CACHE_OPS_UNCHANGED), @out_obj_old * and @out_obj_new will be set accordingly. **/ NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj_hand_over, gboolean is_dump, const NMPObject **out_obj_old, const NMPObject **out_obj_new) { const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new; const NMPObject *obj_old; gboolean is_alive; 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 (NMP_OBJECT_GET_TYPE (obj_hand_over) != NMP_OBJECT_TYPE_LINK || ( !obj_hand_over->_link.udev.device && !obj_hand_over->link.driver)); nm_assert (nm_dedup_multi_index_obj_find (cache->multi_idx, obj_hand_over) != obj_hand_over); entry_old = _lookup_entry (cache, obj_hand_over); if (!entry_old) { NM_SET_OUT (out_obj_old, NULL); if (!nmp_object_is_alive (obj_hand_over)) { NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_UNCHANGED; } if (NMP_OBJECT_GET_TYPE (obj_hand_over) == NMP_OBJECT_TYPE_LINK) { _nmp_object_fixup_link_master_connected (&obj_hand_over, NULL, cache); _nmp_object_fixup_link_udev_fields (&obj_hand_over, NULL, cache->use_udev); } _idxcache_update (cache, entry_old, obj_hand_over, is_dump, &entry_new); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_ADDED; } obj_old = entry_old->obj; if (NMP_OBJECT_GET_TYPE (obj_hand_over) == NMP_OBJECT_TYPE_LINK) { if (!obj_hand_over->_link.netlink.is_in_netlink) { if (!obj_old->_link.netlink.is_in_netlink) { nm_assert (obj_old->_link.udev.device); NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; } if (obj_old->_link.udev.device) { /* @obj_hand_over is not in netlink. * * This is similar to nmp_cache_remove_netlink(), but there we preserve the * preexisting netlink properties. The use case of that is when kernel_get_object() * cannot load an object (based on the id of a needle). * * Here we keep the data provided from @obj_hand_over. The usecase is when receiving * a valid @obj_hand_over instance from netlink with RTM_DELROUTE. */ is_alive = TRUE; } else is_alive = FALSE; } else is_alive = TRUE; if (is_alive) { _nmp_object_fixup_link_master_connected (&obj_hand_over, NULL, cache); /* Merge the netlink parts with what we have from udev. */ udev_device_unref (obj_hand_over->_link.udev.device); obj_hand_over->_link.udev.device = obj_old->_link.udev.device ? udev_device_ref (obj_old->_link.udev.device) : NULL; _nmp_object_fixup_link_udev_fields (&obj_hand_over, NULL, cache->use_udev); if (obj_hand_over->_link.netlink.lnk) { nm_auto_nmpobj const NMPObject *lnk_old = obj_hand_over->_link.netlink.lnk; /* let's dedup/intern the lnk object. */ obj_hand_over->_link.netlink.lnk = nm_dedup_multi_index_obj_intern (cache->multi_idx, lnk_old); } } } else is_alive = nmp_object_is_alive (obj_hand_over); NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); if (!is_alive) { /* the update would make @obj_old invalid. Remove it. */ _idxcache_update (cache, entry_old, NULL, FALSE, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_REMOVED; } if (nmp_object_equal (obj_old, obj_hand_over)) { if (is_dump) _idxcache_update_order_for_dump (cache, entry_old); nm_dedup_multi_entry_set_dirty (entry_old, FALSE); NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; } _idxcache_update (cache, entry_old, obj_hand_over, is_dump, &entry_new); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_UPDATED; } NMPCacheOpsType nmp_cache_update_netlink_route (NMPCache *cache, NMPObject *obj_hand_over, gboolean is_dump, guint16 nlmsgflags, const NMPObject **out_obj_old, const NMPObject **out_obj_new, const NMPObject **out_obj_replace, gboolean *out_resync_required) { NMDedupMultiIter iter; const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new; const NMDedupMultiEntry *entry_cur; const NMDedupMultiEntry *entry_replace; const NMDedupMultiHeadEntry *head_entry; gboolean is_alive; NMPCacheOpsType ops_type = NMP_CACHE_OPS_UNCHANGED; gboolean resync_required; 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); 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; _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); ops_type = NMP_CACHE_OPS_REMOVED; goto update_done; } if (nmp_object_equal (entry_old->obj, obj_hand_over)) { if (is_dump) _idxcache_update_order_for_dump (cache, entry_old); nm_dedup_multi_entry_set_dirty (entry_old, FALSE); goto update_done; } _idxcache_update (cache, entry_old, obj_hand_over, is_dump, &entry_new); ops_type = NMP_CACHE_OPS_UPDATED; update_done: NM_SET_OUT (out_obj_new, nmp_object_ref (nm_dedup_multi_entry_get_obj (entry_new))); /* a RTM_GETROUTE event may signal that another object was replaced. * Find out whether that is the case and return it as @obj_replaced. * * Also, fixup the order of @entry_new within NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID * index. For most parts, we don't care about the order of objects (including routes). * But NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID we must keep in the correct order, to * properly find @obj_replaced. */ resync_required = FALSE; entry_replace = NULL; 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; } 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. */ entry_cur = _lookup_entry_with_idx_type (cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_new->obj); if (!entry_cur) { nm_assert_not_reached (); goto out; } nm_assert (entry_cur->obj == entry_new->obj); head_entry = entry_cur->head; nm_assert (head_entry == nmp_cache_lookup_all (cache, NMP_CACHE_ID_TYPE_ROUTES_BY_WEAK_ID, entry_cur->obj)); if (head_entry->len == 1) { /* there is only one object, and we expect it to be @obj_new. */ nm_assert (nm_dedup_multi_head_entry_get_idx (head_entry, 0) == entry_cur); goto out; } switch (nlmsgflags & (NLM_F_REPLACE | NLM_F_EXCL | NLM_F_CREATE | NLM_F_APPEND)) { case NLM_F_REPLACE: /* ip route change */ /* get the first element (but skip @obj_new). */ nm_dedup_multi_iter_init (&iter, head_entry); if (!nm_dedup_multi_iter_next (&iter)) nm_assert_not_reached (); if (iter.current == entry_cur) { if (!nm_dedup_multi_iter_next (&iter)) nm_assert_not_reached (); } entry_replace = iter.current; nm_assert ( entry_replace && entry_cur != entry_replace); nm_dedup_multi_entry_reorder (entry_cur, entry_replace, FALSE); break; case NLM_F_CREATE | NLM_F_APPEND: /* ip route append */ nm_dedup_multi_entry_reorder (entry_cur, NULL, TRUE); break; case NLM_F_CREATE: /* ip route prepend */ nm_dedup_multi_entry_reorder (entry_cur, NULL, FALSE); break; default: /* this is an unexpected case, probably a bug that we need to handle better. */ resync_required = TRUE; break; } out: NM_SET_OUT (out_obj_replace, nmp_object_ref (nm_dedup_multi_entry_get_obj (entry_replace))); NM_SET_OUT (out_resync_required, resync_required); return ops_type; } NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache, int ifindex, struct udev_device *udevice, const NMPObject **out_obj_old, const NMPObject **out_obj_new) { const NMPObject *obj_old; nm_auto_nmpobj NMPObject *obj_new = NULL; const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new; entry_old = nmp_cache_lookup_entry_link (cache, ifindex); if (!entry_old) { if (!udevice) { NM_SET_OUT (out_obj_old, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_UNCHANGED; } obj_new = nmp_object_new (NMP_OBJECT_TYPE_LINK, NULL); obj_new->link.ifindex = ifindex; obj_new->_link.udev.device = udev_device_ref (udevice); _nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev); _idxcache_update (cache, NULL, obj_new, FALSE, &entry_new); NM_SET_OUT (out_obj_old, NULL); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_ADDED; } else { obj_old = entry_old->obj; NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); if (obj_old->_link.udev.device == udevice) { NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; } if (!udevice && !obj_old->_link.netlink.is_in_netlink) { /* the update would make @obj_old invalid. Remove it. */ _idxcache_update (cache, entry_old, NULL, FALSE, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_REMOVED; } obj_new = nmp_object_clone (obj_old, FALSE); udev_device_unref (obj_new->_link.udev.device); obj_new->_link.udev.device = udevice ? udev_device_ref (udevice) : NULL; _nmp_object_fixup_link_udev_fields (&obj_new, NULL, cache->use_udev); _idxcache_update (cache, entry_old, obj_new, FALSE, &entry_new); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_UPDATED; } } NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, const NMPObject **out_obj_old, const NMPObject **out_obj_new) { const NMDedupMultiEntry *entry_old; const NMDedupMultiEntry *entry_new = NULL; const NMPObject *obj_old; nm_auto_nmpobj NMPObject *obj_new = NULL; entry_old = nmp_cache_lookup_entry_link (cache, ifindex); if (!entry_old) { NM_SET_OUT (out_obj_old, NULL); NM_SET_OUT (out_obj_new, NULL); return NMP_CACHE_OPS_UNCHANGED; } obj_old = entry_old->obj; if (!nmp_cache_link_connected_needs_toggle (cache, obj_old, NULL, NULL)) { NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); NM_SET_OUT (out_obj_new, nmp_object_ref (obj_old)); return NMP_CACHE_OPS_UNCHANGED; } obj_new = nmp_object_clone (obj_old, FALSE); obj_new->link.connected = !obj_old->link.connected; NM_SET_OUT (out_obj_old, nmp_object_ref (obj_old)); _idxcache_update (cache, entry_old, obj_new, FALSE, &entry_new); NM_SET_OUT (out_obj_new, nmp_object_ref (entry_new->obj)); return NMP_CACHE_OPS_UPDATED; } /*****************************************************************************/ void nmp_cache_dirty_set_all_main (NMPCache *cache, const NMPLookup *lookup) { const NMDedupMultiHeadEntry *head_entry; NMDedupMultiIter iter; nm_assert (cache); nm_assert (lookup); head_entry = nmp_cache_lookup (cache, lookup); nm_dedup_multi_iter_init (&iter, head_entry); while (nm_dedup_multi_iter_next (&iter)) { const NMDedupMultiEntry *main_entry; main_entry = nmp_cache_reresolve_main_entry (cache, iter.current, lookup); nm_dedup_multi_entry_set_dirty (main_entry, TRUE); } } /*****************************************************************************/ NMPCache * nmp_cache_new (NMDedupMultiIndex *multi_idx, gboolean use_udev) { NMPCache *cache = g_slice_new0 (NMPCache); guint i; for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) _dedup_multi_idx_type_init ((DedupMultiIdxType *) _idx_type_get (cache, i), i); cache->multi_idx = nm_dedup_multi_index_ref (multi_idx); cache->use_udev = !!use_udev; return cache; } void nmp_cache_free (NMPCache *cache) { guint i; for (i = NMP_CACHE_ID_TYPE_NONE + 1; i <= NMP_CACHE_ID_TYPE_MAX; i++) nm_dedup_multi_index_remove_idx (cache->multi_idx, _idx_type_get (cache, i)); nm_dedup_multi_index_unref (cache->multi_idx); g_slice_free (NMPCache, cache); } /*****************************************************************************/ void ASSERT_nmp_cache_is_consistent (const NMPCache *cache) { } /*****************************************************************************/ 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 = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_link_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_link_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp, }, [NMP_OBJECT_TYPE_IP4_ADDRESS - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_IP4_ADDRESS, .sizeof_data = sizeof (NMPObjectIP4Address), .sizeof_public = sizeof (NMPlatformIP4Address), .obj_type_name = "ip4-address", .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, .supported_cache_ids = _supported_cache_ids_ipx_address, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_address, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip4_address, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip4_address, .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_address, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip4_address_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_ip4_address_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp, }, [NMP_OBJECT_TYPE_IP6_ADDRESS - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_IP6_ADDRESS, .sizeof_data = sizeof (NMPObjectIP6Address), .sizeof_public = sizeof (NMPlatformIP6Address), .obj_type_name = "ip6-address", .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, .supported_cache_ids = _supported_cache_ids_ipx_address, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_address, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_address, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_address, .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_address, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_ip6_address_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_ip6_address_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp }, [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_copy = _vt_cmd_plobj_id_copy_ip4_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 = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) 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_hash_update = _vt_cmd_plobj_hash_update_ip4_route, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp_full, }, [NMP_OBJECT_TYPE_IP6_ROUTE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_IP6_ROUTE, .sizeof_data = sizeof (NMPObjectIP6Route), .sizeof_public = sizeof (NMPlatformIP6Route), .obj_type_name = "ip6-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, .supported_cache_ids = _supported_cache_ids_ipx_route, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_route, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_ip6_route, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_ip6_route, .cmd_plobj_to_string_id = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) 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_hash_update = _vt_cmd_plobj_hash_update_ip6_route, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp_full, }, [NMP_OBJECT_TYPE_ROUTING_RULE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_ROUTING_RULE, .sizeof_data = sizeof (NMPObjectRoutingRule), .sizeof_public = sizeof (NMPlatformRoutingRule), .obj_type_name = "routing-rule", .rtm_gettype = RTM_GETRULE, .signal_type_id = NM_PLATFORM_SIGNAL_ID_ROUTING_RULE, .signal_type = NM_PLATFORM_SIGNAL_ROUTING_RULE_CHANGED, .supported_cache_ids = _supported_cache_ids_routing_rules, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_routing_rule, .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_routing_rule, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_routing_rule, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_routing_rule, .cmd_plobj_to_string_id = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_routing_rule_to_string, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_routing_rule_to_string, .cmd_plobj_hash_update = _vt_cmd_plobj_hash_update_routing_rule, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_routing_rule_cmp_full, }, [NMP_OBJECT_TYPE_QDISC - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_QDISC, .sizeof_data = sizeof (NMPObjectQdisc), .sizeof_public = sizeof (NMPlatformQdisc), .obj_type_name = "qdisc", .rtm_gettype = RTM_GETQDISC, .signal_type_id = NM_PLATFORM_SIGNAL_ID_QDISC, .signal_type = NM_PLATFORM_SIGNAL_QDISC_CHANGED, .supported_cache_ids = _supported_cache_ids_object, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_qdisc, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_qdisc, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_qdisc, .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_qdisc, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_qdisc_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_qdisc_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_qdisc_cmp, }, [NMP_OBJECT_TYPE_TFILTER - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_TFILTER, .sizeof_data = sizeof (NMPObjectTfilter), .sizeof_public = sizeof (NMPlatformTfilter), .obj_type_name = "tfilter", .rtm_gettype = RTM_GETTFILTER, .signal_type_id = NM_PLATFORM_SIGNAL_ID_TFILTER, .signal_type = NM_PLATFORM_SIGNAL_TFILTER_CHANGED, .supported_cache_ids = _supported_cache_ids_object, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_tfilter, .cmd_plobj_id_cmp = _vt_cmd_plobj_id_cmp_tfilter, .cmd_plobj_id_hash_update = _vt_cmd_plobj_id_hash_update_tfilter, .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_tfilter, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_tfilter_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_tfilter_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_tfilter_cmp, }, [NMP_OBJECT_TYPE_LNK_GRE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp, }, [NMP_OBJECT_TYPE_LNK_GRETAP - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_GRETAP, .sizeof_data = sizeof (NMPObjectLnkGre), .sizeof_public = sizeof (NMPlatformLnkGre), .obj_type_name = "gretap", .lnk_link_type = NM_LINK_TYPE_GRETAP, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_gre_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_gre_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_gre_cmp, }, [NMP_OBJECT_TYPE_LNK_INFINIBAND - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_infiniband_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_infiniband_cmp, }, [NMP_OBJECT_TYPE_LNK_IP6TNL - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp, }, [NMP_OBJECT_TYPE_LNK_IP6GRE - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRE, .sizeof_data = sizeof (NMPObjectLnkIp6Tnl), .sizeof_public = sizeof (NMPlatformLnkIp6Tnl), .obj_type_name = "ip6gre", .lnk_link_type = NM_LINK_TYPE_IP6GRE, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp, }, [NMP_OBJECT_TYPE_LNK_IP6GRETAP - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_IP6GRETAP, .sizeof_data = sizeof (NMPObjectLnkIp6Tnl), .sizeof_public = sizeof (NMPlatformLnkIp6Tnl), .obj_type_name = "ip6gretap", .lnk_link_type = NM_LINK_TYPE_IP6GRETAP, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_ip6tnl_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ip6tnl_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ip6tnl_cmp, }, [NMP_OBJECT_TYPE_LNK_IPIP - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_ipip_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_ipip_cmp, }, [NMP_OBJECT_TYPE_LNK_MACSEC - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_MACSEC, .sizeof_data = sizeof (NMPObjectLnkMacsec), .sizeof_public = sizeof (NMPlatformLnkMacsec), .obj_type_name = "macsec", .lnk_link_type = NM_LINK_TYPE_MACSEC, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_macsec_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macsec_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macsec_cmp, }, [NMP_OBJECT_TYPE_LNK_MACVLAN - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macvlan_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp, }, [NMP_OBJECT_TYPE_LNK_MACVTAP - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_MACVTAP, .sizeof_data = sizeof (NMPObjectLnkMacvtap), .sizeof_public = sizeof (NMPlatformLnkMacvlan), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_macvlan_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_macvlan_cmp, }, [NMP_OBJECT_TYPE_LNK_SIT - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_sit_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_sit_cmp, }, [NMP_OBJECT_TYPE_LNK_TUN - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_TUN, .sizeof_data = sizeof (NMPObjectLnkTun), .sizeof_public = sizeof (NMPlatformLnkTun), .obj_type_name = "tun", .lnk_link_type = NM_LINK_TYPE_TUN, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_tun_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_tun_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_tun_cmp, }, [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 = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vlan_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vlan_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vlan_cmp, }, [NMP_OBJECT_TYPE_LNK_VRF - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .obj_type = NMP_OBJECT_TYPE_LNK_VRF, .sizeof_data = sizeof (NMPObjectLnkVrf), .sizeof_public = sizeof (NMPlatformLnkVrf), .obj_type_name = "vrf", .lnk_link_type = NM_LINK_TYPE_VRF, .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_vrf_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vrf_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vrf_cmp, }, [NMP_OBJECT_TYPE_LNK_VXLAN - 1] = { .parent = DEDUP_MULTI_OBJ_CLASS_INIT(), .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_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_vxlan_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_vxlan_cmp, }, [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 = (const char *(*) (const NMPlatformObject *obj, char *buf, gsize len)) nm_platform_lnk_wireguard_to_string, .cmd_plobj_hash_update = (void (*) (const NMPlatformObject *obj, NMHashState *h)) nm_platform_lnk_wireguard_hash_update, .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_lnk_wireguard_cmp, }, };