diff options
author | Thomas Haller <thaller@redhat.com> | 2015-04-14 23:14:06 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2015-06-17 11:23:51 +0200 |
commit | 53f98e7f9e58767e22fada651b12bf3f74aa76ed (patch) | |
tree | 4afad5ce08eac10cc81e9ea99271dbbe5dae3767 | |
parent | d1e7554a90ee9bae08ed44a287a0d59b1ad2ce2b (diff) | |
download | NetworkManager-53f98e7f9e58767e22fada651b12bf3f74aa76ed.tar.gz |
platform: implement NMPObject and NMPCache
NMPObject is a simple "object" implemenation around NMPlatformObject.
They are ref-counted and have a class-pointer. Several basic functions
like equality, hash, to-string are implemented.
NMPCache is can be used to store the NMPObject. Objects are indexed
via their primary id, but there is also multi-lookup via NMCacheId
and NMMultiIndex.
Part of the implementation is inside "nm-linux-platform.c",
because it depends on utility functions from there.
-rw-r--r-- | src/nm-types.h | 3 | ||||
-rw-r--r-- | src/platform/nm-linux-platform.c | 398 | ||||
-rw-r--r-- | src/platform/nm-platform.c | 8 | ||||
-rw-r--r-- | src/platform/nm-platform.h | 6 | ||||
-rw-r--r-- | src/platform/nmp-object.c | 1927 | ||||
-rw-r--r-- | src/platform/nmp-object.h | 344 | ||||
-rw-r--r-- | src/platform/tests/test-nmp-object.c | 383 |
7 files changed, 3064 insertions, 5 deletions
diff --git a/src/nm-types.h b/src/nm-types.h index ddcee4daba..2f36bee45a 100644 --- a/src/nm-types.h +++ b/src/nm-types.h @@ -52,6 +52,9 @@ typedef enum { /* In priority order; higher number == higher priority */ NM_IP_CONFIG_SOURCE_UNKNOWN, + /* platform internal flag used to mark routes with RTM_F_CLONED. */ + _NM_IP_CONFIG_SOURCE_RTM_F_CLONED, + /* platform internal flag used to mark routes with protocol RTPROT_KERNEL. */ _NM_IP_CONFIG_SOURCE_RTPROT_KERNEL, diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index 264af4b42c..ce49083310 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -15,7 +15,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * - * Copyright (C) 2012-2013 Red Hat, Inc. + * Copyright (C) 2012-2015 Red Hat, Inc. */ #include "config.h" @@ -436,7 +436,7 @@ nm_linux_platform_setup (void) /******************************************************************/ -static ObjectType +ObjectType _nlo_get_object_type (const struct nl_object *object) { const char *type_str; @@ -1011,6 +1011,86 @@ init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllin return TRUE; } +gboolean +_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +{ + NMPlatformLink *obj = (NMPlatformLink *) _obj; + NMPObjectLink *obj_priv = (NMPObjectLink *) _obj; + struct rtnl_link *nlo = (struct rtnl_link *) _nlo; + const char *name; + struct nl_addr *nladdr; + + nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0); + + obj->ifindex = rtnl_link_get_ifindex (nlo); + + if (id_only) + return TRUE; + + name = rtnl_link_get_name (nlo); + if (name) + g_strlcpy (obj->name, name, sizeof (obj->name)); + obj->type = link_extract_type (platform, nlo); + obj->kind = g_intern_string (rtnl_link_get_type (nlo)); + obj->flags = rtnl_link_get_flags (nlo); + obj->up = NM_FLAGS_HAS (obj->flags, IFF_UP); + obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP); + obj->arp = !NM_FLAGS_HAS (obj->flags, IFF_NOARP); + obj->master = rtnl_link_get_master (nlo); + obj->parent = rtnl_link_get_link (nlo); + obj->mtu = rtnl_link_get_mtu (nlo); + obj->arptype = rtnl_link_get_arptype (nlo); + + if (obj->type == NM_LINK_TYPE_VLAN) + obj->vlan_id = rtnl_link_vlan_get_id (nlo); + + if ((nladdr = rtnl_link_get_addr (nlo))) { + unsigned int l = 0; + + l = nl_addr_get_len (nladdr); + if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) { + G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data)); + memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l); + obj->addr.len = l; + } + } + +#if HAVE_LIBNL_INET6_ADDR_GEN_MODE + if (_support_user_ipv6ll_get ()) { + guint8 mode = 0; + + if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0) + obj->inet6_addr_gen_mode_inv = ~mode; + } +#endif + +#if HAVE_LIBNL_INET6_TOKEN + if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) { + if ( nl_addr_get_family (nladdr) == AF_INET6 + && nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) { + struct in6_addr *addr; + NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid; + + addr = nl_addr_get_binary_addr (nladdr); + iid->id_u8[7] = addr->s6_addr[15]; + iid->id_u8[6] = addr->s6_addr[14]; + iid->id_u8[5] = addr->s6_addr[13]; + iid->id_u8[4] = addr->s6_addr[12]; + iid->id_u8[3] = addr->s6_addr[11]; + iid->id_u8[2] = addr->s6_addr[10]; + iid->id_u8[1] = addr->s6_addr[9]; + iid->id_u8[0] = addr->s6_addr[8]; + obj->inet6_token.is_valid = TRUE; + } + nl_addr_put (nladdr); + } +#endif + + obj_priv->netlink.is_in_netlink = TRUE; + + return TRUE; +} + /* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break * the carrier detection. This hack makes nm-platform think they don't have the * IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all @@ -1194,6 +1274,66 @@ _init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr address->preferred = _get_remaining_time (address->timestamp, a_preferred); } +static guint32 +_extend_lifetime (guint32 lifetime, guint32 seconds) +{ + guint64 v; + + if ( lifetime == NM_PLATFORM_LIFETIME_PERMANENT + || seconds == 0) + return lifetime; + + v = (guint64) lifetime + (guint64) seconds; + return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1); +} + +/* The rtnl_addr object contains relative lifetimes @valid and @preferred + * that count in seconds, starting from the moment when the kernel constructed + * the netlink message. + * + * There is also a field rtnl_addr_last_update_time(), which is the absolute + * time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address + * was modified (wrapping every 497 days). + * Immediately at the time when the address was last modified, #NOW and @last_update_time + * are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time. + * However, this is not true in general. As time goes by, whenever kernel sends a new address + * via netlink, the lifetimes keep counting down. + **/ +static void +_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr, + guint32 *out_timestamp, + guint32 *out_lifetime, + guint32 *out_preferred) +{ + guint32 timestamp = 0; + gint32 now; + guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr); + guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr); + + if ( lifetime != NM_PLATFORM_LIFETIME_PERMANENT + || preferred != NM_PLATFORM_LIFETIME_PERMANENT) { + if (preferred > lifetime) + preferred = lifetime; + timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now); + + if (now == 0) { + /* strange. failed to detect the last-update time and assumed that timestamp is 1. */ + nm_assert (timestamp == 1); + now = nm_utils_get_monotonic_timestamp_s (); + } + if (timestamp < now) { + guint32 diff = now - timestamp; + + lifetime = _extend_lifetime (lifetime, diff); + preferred = _extend_lifetime (preferred, diff); + } else + nm_assert (timestamp == now); + } + *out_timestamp = timestamp; + *out_lifetime = lifetime; + *out_preferred = preferred; +} + static gboolean init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr) { @@ -1229,6 +1369,44 @@ init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr) return TRUE; } +gboolean +_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +{ + NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj; + struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo; + struct nl_addr *nladdr = rtnl_addr_get_local (nlo); + struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo); + const char *label; + + if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address)) + g_return_val_if_reached (FALSE); + + obj->ifindex = rtnl_addr_get_ifindex (nlo); + obj->plen = rtnl_addr_get_prefixlen (nlo); + memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address)); + + if (id_only) + return TRUE; + + obj->source = NM_IP_CONFIG_SOURCE_KERNEL; + _nlo_rtnl_addr_get_lifetimes (nlo, + &obj->timestamp, + &obj->lifetime, + &obj->preferred); + if (nlpeer) { + if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address)) + g_warn_if_reached (); + else + memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address)); + } + label = rtnl_addr_get_label (nlo); + /* Check for ':'; we're only interested in labels used as interface aliases */ + if (label && strchr (label, ':')) + g_strlcpy (obj->label, label, sizeof (obj->label)); + + return TRUE; +} + static gboolean init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr) { @@ -1258,6 +1436,41 @@ init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr) return TRUE; } +gboolean +_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +{ + NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj; + struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo; + struct nl_addr *nladdr = rtnl_addr_get_local (nlo); + struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo); + + if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address)) + g_return_val_if_reached (FALSE); + + obj->ifindex = rtnl_addr_get_ifindex (nlo); + obj->plen = rtnl_addr_get_prefixlen (nlo); + memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address)); + + if (id_only) + return TRUE; + + obj->source = NM_IP_CONFIG_SOURCE_KERNEL; + _nlo_rtnl_addr_get_lifetimes (nlo, + &obj->timestamp, + &obj->lifetime, + &obj->preferred); + obj->flags = rtnl_addr_get_flags (nlo); + + if (nlpeer) { + if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address)) + g_warn_if_reached (); + else + memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address)); + } + + return TRUE; +} + static guint source_to_rtprot (NMIPConfigSource source) { @@ -1350,6 +1563,61 @@ init_ip4_route (NMPlatformIP4Route *route, struct rtnl_route *rtnlroute) return TRUE; } +gboolean +_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +{ + NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj; + struct rtnl_route *nlo = (struct rtnl_route *) _nlo; + struct nl_addr *dst, *gw; + struct rtnl_nexthop *nexthop; + + if (rtnl_route_get_type (nlo) != RTN_UNICAST || + rtnl_route_get_table (nlo) != RT_TABLE_MAIN || + rtnl_route_get_tos (nlo) != 0 || + rtnl_route_get_nnexthops (nlo) != 1) + return FALSE; + + nexthop = rtnl_route_nexthop_n (nlo, 0); + if (!nexthop) + g_return_val_if_reached (FALSE); + + dst = rtnl_route_get_dst (nlo); + if (!dst) + g_return_val_if_reached (FALSE); + + if (nl_addr_get_len (dst)) { + if (nl_addr_get_len (dst) != sizeof (obj->network)) + g_return_val_if_reached (FALSE); + memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network)); + } + obj->ifindex = rtnl_route_nh_get_ifindex (nexthop); + obj->plen = nl_addr_get_prefixlen (dst); + obj->metric = rtnl_route_get_priority (nlo); + obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo)); + + gw = rtnl_route_nh_get_gateway (nexthop); + if (gw) { + if (nl_addr_get_len (gw) != sizeof (obj->gateway)) + g_warn_if_reached (); + else + memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway)); + } + rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss); + if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) { + /* we must not straight way reject cloned routes, because we might have cached + * a non-cloned route. If we now receive an update of the route with the route + * being cloned, we must still return the object, so that we can remove the old + * one from the cache. + * + * This happens, because this route is not nmp_object_is_alive(). + * */ + obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED; + } else + obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE); + + return TRUE; +} + static gboolean init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute) { @@ -1390,6 +1658,56 @@ init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute) return TRUE; } +gboolean +_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache) +{ + NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj; + struct rtnl_route *nlo = (struct rtnl_route *) _nlo; + struct nl_addr *dst, *gw; + struct rtnl_nexthop *nexthop; + + if (rtnl_route_get_type (nlo) != RTN_UNICAST || + rtnl_route_get_table (nlo) != RT_TABLE_MAIN || + rtnl_route_get_tos (nlo) != 0 || + rtnl_route_get_nnexthops (nlo) != 1) + return FALSE; + + nexthop = rtnl_route_nexthop_n (nlo, 0); + if (!nexthop) + g_return_val_if_reached (FALSE); + + dst = rtnl_route_get_dst (nlo); + if (!dst) + g_return_val_if_reached (FALSE); + + if (nl_addr_get_len (dst)) { + if (nl_addr_get_len (dst) != sizeof (obj->network)) + g_return_val_if_reached (FALSE); + memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network)); + } + obj->ifindex = rtnl_route_nh_get_ifindex (nexthop); + obj->plen = nl_addr_get_prefixlen (dst); + obj->metric = rtnl_route_get_priority (nlo); + + if (id_only) + return TRUE; + + gw = rtnl_route_nh_get_gateway (nexthop); + if (gw) { + if (nl_addr_get_len (gw) != sizeof (obj->gateway)) + g_warn_if_reached (); + else + memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway)); + } + rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss); + if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) + obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED; + else + obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE); + + return TRUE; +} + static char to_string_buffer[255]; #define SET_AND_RETURN_STRING_BUFFER(...) \ @@ -2296,6 +2614,16 @@ build_rtnl_link (int ifindex, const char *name, NMLinkType type) return (struct nl_object *) rtnllink; } +struct nl_object * +_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) +{ + const NMPlatformLink *obj = (const NMPlatformLink *) _obj; + + return build_rtnl_link (obj->ifindex, + obj->name[0] ? obj->name : NULL, + obj->type); +} + static gboolean link_get_by_name (NMPlatform *platform, const char *name, NMPlatformLink *out_link) { @@ -3782,6 +4110,40 @@ build_rtnl_addr (NMPlatform *platform, return (struct nl_object *) rtnladdr_copy; } +struct nl_object * +_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) +{ + const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj; + + return build_rtnl_addr (platform, + AF_INET, + obj->ifindex, + &obj->address, + obj->peer_address ? &obj->peer_address : NULL, + obj->plen, + obj->lifetime, + obj->preferred, + 0, + obj->label[0] ? obj->label : NULL); +} + +struct nl_object * +_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) +{ + const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj; + + return build_rtnl_addr (platform, + AF_INET6, + obj->ifindex, + &obj->address, + !IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL, + obj->plen, + obj->lifetime, + obj->preferred, + 0, + NULL); +} + static gboolean ip4_address_add (NMPlatform *platform, int ifindex, @@ -4036,6 +4398,38 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source, return (struct nl_object *) rtnlroute; } +struct nl_object * +_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) +{ + const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj; + + return build_rtnl_route (AF_INET, + obj->ifindex, + obj->source, + &obj->network, + obj->plen, + &obj->gateway, + NULL, + obj->metric, + obj->mss); +} + +struct nl_object * +_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only) +{ + const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj; + + return build_rtnl_route (AF_INET6, + obj->ifindex, + obj->source, + &obj->network, + obj->plen, + &obj->gateway, + NULL, + obj->metric, + obj->mss); +} + static gboolean ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source, in_addr_t network, int plen, in_addr_t gateway, diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c index 23498cf5a9..caad16e2c8 100644 --- a/src/platform/nm-platform.c +++ b/src/platform/nm-platform.c @@ -2393,6 +2393,8 @@ source_to_string (NMIPConfigSource source) switch (source) { case _NM_IP_CONFIG_SOURCE_RTPROT_KERNEL: return "rtprot-kernel"; + case _NM_IP_CONFIG_SOURCE_RTM_F_CLONED: + return "rtm-f-cloned"; case NM_IP_CONFIG_SOURCE_KERNEL: return "kernel"; case NM_IP_CONFIG_SOURCE_SHARED: @@ -2844,7 +2846,11 @@ nm_platform_link_cmp (const NMPlatformLink *a, const NMPlatformLink *b) _CMP_FIELD (a, b, inet6_addr_gen_mode_inv); _CMP_FIELD (a, b, inet6_token.is_valid); _CMP_FIELD_STR_INTERNED (a, b, kind); - _CMP_FIELD_STR0 (a, b, udi); + + /* udi is not an interned string, but NMRefString. Hence, + * do a pointer comparison first. */ + _CMP_FIELD_STR_INTERNED (a, b, udi); + _CMP_FIELD_STR_INTERNED (a, b, driver); if (a->addr.len) _CMP_FIELD_MEMCMP_LEN (a, b, addr.data, a->addr.len); diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h index c6145c56d2..62247bc31c 100644 --- a/src/platform/nm-platform.h +++ b/src/platform/nm-platform.h @@ -91,8 +91,10 @@ struct _NMPlatformLink { /* NMPlatform initializes this field with a static string. */ const char *kind; - /* Beware: NMPlatform initializes this string with an allocated string. - * Handle it properly (i.e. don't keep a reference to it). */ + /* Beware: NMPlatform initializes this string with an allocated string + * (NMRefString). Handle it properly (i.e. don't keep a reference to it + * without incrementing the ref-counter). + * This property depends on @initialized. */ const char *udi; /* NMPlatform initializes this field with a static string. */ diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c index 8c90deaec0..38eef91452 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -20,4 +20,1931 @@ #include "nmp-object.h" +#include <unistd.h> + +#include "nm-platform-utils.h" +#include "NetworkManagerUtils.h" +#include "nm-utils.h" +#include "nm-logging.h" + +/*********************************************************************************************/ + +#define _LOG_DOMAIN LOGD_PLATFORM + +#define _LOG(level, domain, obj, ...) \ + G_STMT_START { \ + const NMLogLevel __level = (level); \ + const NMLogDomain __domain = (domain); \ + \ + if (nm_logging_enabled (__level, __domain)) { \ + const NMPObject *const __obj = (obj); \ + \ + _nm_log (__level, __domain, 0, \ + "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 +#define _LOG_LEVEL_ENABLED(level, domain) \ + ( nm_logging_enabled ((level), (domain)) ) + +#ifdef NM_MORE_LOGGING +#define _LOGT_ENABLED() _LOG_LEVEL_ENABLED (LOGL_TRACE, _LOG_DOMAIN) +#define _LOGT(obj, ...) _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__) +#else +#define _LOGT_ENABLED() FALSE +#define _LOGT(obj, ...) G_STMT_START { if (FALSE) { _LOG (LOGL_TRACE, _LOG_DOMAIN, obj, __VA_ARGS__); } } G_STMT_END +#endif + +#define _LOGD(obj, ...) _LOG (LOGL_DEBUG, _LOG_DOMAIN, obj, __VA_ARGS__) +#define _LOGI(obj, ...) _LOG (LOGL_INFO , _LOG_DOMAIN, obj, __VA_ARGS__) +#define _LOGW(obj, ...) _LOG (LOGL_WARN , _LOG_DOMAIN, obj, __VA_ARGS__) +#define _LOGE(obj, ...) _LOG (LOGL_ERR , _LOG_DOMAIN, obj, __VA_ARGS__) + +/*********************************************************************************************/ + +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. + */ + + GHashTable *idx_main; + NMMultiIndex *idx_multi; + + gboolean use_udev; +}; + +/******************************************************************/ + +static inline guint +_id_hash_ip6_addr (const struct in6_addr *addr) +{ + guint hash = (guint) 0x897da53981a13ULL; + int i; + + for (i = 0; i < sizeof (*addr); i++) + hash = (hash * 33) + ((const guint8 *) addr)[i]; + return hash; +} + +static const char * +_link_get_driver (GUdevDevice *udev_device, const char *kind, const char *ifname) +{ + const char *driver = NULL; + + nm_assert (kind == g_intern_string (kind)); + + if (udev_device) { + driver = nmp_utils_udev_get_driver (udev_device); + if (driver) + return driver; + } + + if (kind) + return kind; + + if (ifname) { + char *d; + + if (nmp_utils_ethtool_get_driver_info (ifname, &d, NULL, NULL)) { + driver = d && d[0] ? g_intern_string (d) : NULL; + g_free (d); + if (driver) + return driver; + } + } + + return "unknown"; +} + +void +_nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev) +{ + const char *driver = NULL; + const char *udi = NULL; + gboolean initialized = FALSE; + + nm_assert (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK); + + /* The link contains internal fields that are combined by + * properties from netlink and udev. Update those properties */ + + /* When a link is not in netlink, it's 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.name); + if (obj->_link.udev.device) { + udi = g_udev_device_get_sysfs_path (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. + * + * If you want to support changing of whether udev is enabled, + * reset the value via nmp_cache_use_udev_set() carefully -- and + * possibly update the links in the cache accordingly. + * */ + initialized = TRUE; + } + } + + obj->link.driver = driver; + obj->link.udi = nm_ref_string_replace (obj->link.udi, udi); + obj->link.initialized = initialized; +} + +static void +_nmp_object_fixup_link_master_connected (NMPObject *obj, const NMPCache *cache) +{ + nm_assert (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK); + + if (nmp_cache_link_connected_needs_toggle (cache, obj, NULL, NULL)) + obj->link.connected = !obj->link.connected; +} + +/******************************************************************/ + +const NMPClass * +nmp_class_from_type (ObjectType obj_type) +{ + g_return_val_if_fail (obj_type > OBJECT_TYPE_UNKNOWN && obj_type <= OBJECT_TYPE_MAX, NULL); + + return &_nmp_classes[obj_type - 1]; +} + +/******************************************************************/ + +NMPObject * +nmp_object_ref (NMPObject *obj) +{ + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL); + g_return_val_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT, NULL); + obj->_ref_count++; + + _LOGT (obj, "ref: %d", obj->_ref_count); + + return obj; +} + +void +nmp_object_unref (NMPObject *obj) +{ + if (obj) { + g_return_if_fail (obj->_ref_count > 0); + g_return_if_fail (obj->_ref_count != NMP_REF_COUNT_STACKINIT); + _LOGT (obj, "%s: %d", + obj->_ref_count <= 1 ? "destroy" : "unref", + obj->_ref_count - 1); + if (--obj->_ref_count <= 0) { + const NMPClass *klass = obj->_class; + + nm_assert (!obj->is_cached); + if (klass->cmd_obj_dispose) + klass->cmd_obj_dispose (obj); + g_slice_free1 (klass->sizeof_data + G_STRUCT_OFFSET (NMPObject, object), obj); + } + } +} + +static void +_vt_cmd_obj_dispose_link (NMPObject *obj) +{ + nm_ref_string_unref (obj->link.udi); + g_clear_object (&obj->_link.udev.device); +} + +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->_ref_count = 1; + _LOGT (obj, "new"); + return obj; +} + +NMPObject * +nmp_object_new (ObjectType obj_type, const NMPlatformObject *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 (OBJECT_TYPE_LINK, NULL); + obj->link.ifindex = ifindex; + return obj; +} + +/******************************************************************/ + +static const NMPObject * +_nmp_object_stackinit_from_class (NMPObject *obj, const NMPClass *klass) +{ + nm_assert (klass); + + memset (obj, 0, sizeof (NMPObject)); + obj->_class = klass; + obj->_ref_count = NMP_REF_COUNT_STACKINIT; + return obj; +} + +const NMPObject * +nmp_object_stackinit (NMPObject *obj, ObjectType obj_type, const NMPlatformObject *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) +{ + nm_assert (NMP_OBJECT_IS_VALID (src)); + nm_assert (obj); + + NMP_OBJECT_GET_CLASS (src)->cmd_obj_stackinit_id (obj, src); + return obj; +} + +const NMPObject * +nmp_object_stackinit_id_link (NMPObject *obj, int ifindex) +{ + nmp_object_stackinit (obj, OBJECT_TYPE_LINK, NULL); + obj->link.ifindex = ifindex; + return obj; +} + +static void +_vt_cmd_obj_stackinit_id_link (NMPObject *obj, const NMPObject *src) +{ + nmp_object_stackinit_id_link (obj, src->link.ifindex); +} + +const NMPObject * +nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen) +{ + nmp_object_stackinit (obj, OBJECT_TYPE_IP4_ADDRESS, NULL); + obj->ip4_address.ifindex = ifindex; + obj->ip4_address.address = address; + obj->ip4_address.plen = plen; + return obj; +} + +static void +_vt_cmd_obj_stackinit_id_ip4_address (NMPObject *obj, const NMPObject *src) +{ + nmp_object_stackinit_id_ip4_address (obj, src->ip_address.ifindex, src->ip4_address.address, src->ip_address.plen); +} + +const NMPObject * +nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen) +{ + nmp_object_stackinit (obj, OBJECT_TYPE_IP6_ADDRESS, NULL); + obj->ip4_address.ifindex = ifindex; + if (address) + obj->ip6_address.address = *address; + obj->ip6_address.plen = plen; + return obj; +} + +static void +_vt_cmd_obj_stackinit_id_ip6_address (NMPObject *obj, const NMPObject *src) +{ + nmp_object_stackinit_id_ip6_address (obj, src->ip_address.ifindex, &src->ip6_address.address, src->ip_address.plen); +} + +const NMPObject * +nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric) +{ + nmp_object_stackinit (obj, OBJECT_TYPE_IP4_ROUTE, NULL); + obj->ip4_route.ifindex = ifindex; + obj->ip4_route.network = network; + obj->ip4_route.plen = plen; + obj->ip4_route.metric = metric; + return obj; +} + +static void +_vt_cmd_obj_stackinit_id_ip4_route (NMPObject *obj, const NMPObject *src) +{ + nmp_object_stackinit_id_ip4_route (obj, src->ip_route.ifindex, src->ip4_route.network, src->ip_route.plen, src->ip_route.metric); +} + +const NMPObject * +nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric) +{ + nmp_object_stackinit (obj, OBJECT_TYPE_IP6_ROUTE, NULL); + obj->ip6_route.ifindex = ifindex; + if (network) + obj->ip6_route.network = *network; + obj->ip6_route.plen = plen; + obj->ip6_route.metric = metric; + return obj; +} + +static void +_vt_cmd_obj_stackinit_id_ip6_route (NMPObject *obj, const NMPObject *src) +{ + nmp_object_stackinit_id_ip6_route (obj, src->ip_route.ifindex, &src->ip6_route.network, src->ip_route.plen, src->ip_route.metric); +} + +/******************************************************************/ + +const char * +nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size) +{ + const NMPClass *klass; + char buf2[sizeof (_nm_platform_to_string_buffer)]; + char buf3[sizeof (_nm_platform_to_string_buffer)]; + const char *str; + + if (!buf) { + buf = _nm_platform_to_string_buffer; + buf_size = sizeof (_nm_platform_to_string_buffer); + } + + if (!obj) { + g_strlcpy (buf, "NULL", buf_size); + return buf; + } + + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), NULL); + + klass = NMP_OBJECT_GET_CLASS (obj); + + switch (to_string_mode) { + case NMP_OBJECT_TO_STRING_ID: + return klass->cmd_plobj_to_string_id (&obj->object, buf, buf_size); + case NMP_OBJECT_TO_STRING_ALL: + g_strlcpy (buf2, NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object), sizeof (buf2)); + + if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) { + g_snprintf (buf3, sizeof (buf3), + ",%cin-nl,%p", + obj->_link.netlink.is_in_netlink ? '+' : '-', + obj->_link.udev.device); + } else + buf3[0] = '\0'; + + g_snprintf (buf, buf_size, + "[%s,%p,%d,%ccache,%calive,%cvisible%s; %s]", + klass->obj_type_name, obj, obj->_ref_count, + obj->is_cached ? '+' : '-', + nmp_object_is_alive (obj) ? '+' : '-', + nmp_object_is_visible (obj) ? '+' : '-', + buf3, buf2); + return buf; + case NMP_OBJECT_TO_STRING_PUBLIC: + str = NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_string (&obj->object); + if (str != buf) + g_strlcpy (buf, str, buf_size); + 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; \ + char buf1[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + (void) buf1; \ + g_snprintf (buf, buf_len, \ + __VA_ARGS__); \ + return buf; \ +} +_vt_cmd_plobj_to_string_id (link, NMPlatformLink, "%d", obj->ifindex); +_vt_cmd_plobj_to_string_id (ip4_address, NMPlatformIP4Address, "%d: %s/%d", obj->ifindex, nm_utils_inet4_ntop ( obj->address, buf1), obj->plen); +_vt_cmd_plobj_to_string_id (ip6_address, NMPlatformIP6Address, "%d: %s/%d", obj->ifindex, nm_utils_inet6_ntop (&obj->address, buf1), obj->plen); +_vt_cmd_plobj_to_string_id (ip4_route, NMPlatformIP4Route, "%d: %s/%d %d", obj->ifindex, nm_utils_inet4_ntop ( obj->network, buf1), obj->plen, obj->metric); +_vt_cmd_plobj_to_string_id (ip6_route, NMPlatformIP6Route, "%d: %s/%d %d", obj->ifindex, nm_utils_inet6_ntop (&obj->network, buf1), obj->plen, obj->metric); + +int +nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2) +{ + if (obj1 == obj2) + return 0; + if (!obj1) + return -1; + if (!obj2) + return 1; + + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), -1); + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), 1); + + if (NMP_OBJECT_GET_CLASS (obj1) != NMP_OBJECT_GET_CLASS (obj2)) + return NMP_OBJECT_GET_CLASS (obj1) < NMP_OBJECT_GET_CLASS (obj2) ? -1 : 1; + + return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object); +} + +gboolean +nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass; + + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj1), FALSE); + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj2), FALSE); + + if (obj1 == obj2) + return TRUE; + + klass = NMP_OBJECT_GET_CLASS (obj1); + + if (klass != NMP_OBJECT_GET_CLASS (obj2)) + return FALSE; + + return klass->cmd_obj_equal (obj1, obj2); +} + +static gboolean +_vt_cmd_obj_equal_plain (const NMPObject *obj1, const NMPObject *obj2) +{ + return NMP_OBJECT_GET_CLASS (obj1)->cmd_plobj_cmp (&obj1->object, &obj2->object) == 0; +} + +static gboolean +_vt_cmd_obj_equal_link (const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj1); + + if (klass->cmd_plobj_cmp (&obj1->object, &obj2->object) != 0) + return FALSE; + if (obj1->_link.netlink.is_in_netlink != obj2->_link.netlink.is_in_netlink) + return FALSE; + if (obj1->_link.udev.device != obj2->_link.udev.device) + return FALSE; + return TRUE; +} + +/* @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) + klass->cmd_plobj_id_copy (&dst->object, &src->object); + else + klass->cmd_obj_copy (dst, src); + } +} + +#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; +}); +_vt_cmd_plobj_id_copy (ip6_address, NMPlatformIP6Address, { + dst->ifindex = src->ifindex; + dst->plen = src->plen; + dst->address = src->address; +}); +_vt_cmd_plobj_id_copy (ip4_route, NMPlatformIP4Route, { + dst->ifindex = src->ifindex; + dst->plen = src->plen; + dst->metric = src->metric; + dst->network = src->network; +}); +_vt_cmd_plobj_id_copy (ip6_route, NMPlatformIP6Route, { + dst->ifindex = src->ifindex; + dst->plen = src->plen; + dst->metric = src->metric; + dst->network = src->network; +}); + +static void +_vt_cmd_obj_copy_plain (NMPObject *dst, const NMPObject *src) +{ + memcpy (&dst->object, &src->object, NMP_OBJECT_GET_CLASS (dst)->sizeof_data); +} + +static void +_vt_cmd_obj_copy_link (NMPObject *dst, const NMPObject *src) +{ + if (dst->_link.udev.device != src->_link.udev.device) { + if (dst->_link.udev.device) + g_object_unref (dst->_link.udev.device); + if (src->_link.udev.device) + g_object_ref (src->_link.udev.device); + } + if (dst->link.udi != src->link.udi) { + nm_ref_string_unref (dst->link.udi); + nm_ref_string_ref (src->link.udi); + } + dst->_link = src->_link; +} + +/* 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; +} + +gboolean +nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2) +{ + const NMPClass *klass; + + if (obj1 == obj2) + return TRUE; + if (!obj1 || !obj2) + return FALSE; + + 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); + return klass == NMP_OBJECT_GET_CLASS (obj2) + && klass->cmd_plobj_id_equal (&obj1->object, &obj2->object); +} + +#define _vt_cmd_plobj_id_equal(type, plat_type, cmd) \ +static gboolean \ +_vt_cmd_plobj_id_equal_##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; \ + return (cmd); \ +} +_vt_cmd_plobj_id_equal (link, NMPlatformLink, + obj1->ifindex == obj2->ifindex); +_vt_cmd_plobj_id_equal (ip4_address, NMPlatformIP4Address, + obj1->ifindex == obj2->ifindex + && obj1->plen == obj2->plen + && obj1->address == obj2->address); +_vt_cmd_plobj_id_equal (ip6_address, NMPlatformIP6Address, + obj1->ifindex == obj2->ifindex + && obj1->plen == obj2->plen + && IN6_ARE_ADDR_EQUAL (&obj1->address, &obj2->address)); +_vt_cmd_plobj_id_equal (ip4_route, NMPlatformIP4Route, + obj1->ifindex == obj2->ifindex + && obj1->plen == obj2->plen + && obj1->metric == obj2->metric + && obj1->network == obj2->network); +_vt_cmd_plobj_id_equal (ip6_route, NMPlatformIP6Route, + obj1->ifindex == obj2->ifindex + && obj1->plen == obj2->plen + && obj1->metric == obj2->metric + && IN6_ARE_ADDR_EQUAL( &obj1->network, &obj2->network)); + +guint +nmp_object_id_hash (const NMPObject *obj) +{ + if (!obj) + return 0; + + g_return_val_if_fail (NMP_OBJECT_IS_VALID (obj), 0); + return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_id_hash (&obj->object); +} + +#define _vt_cmd_plobj_id_hash(type, plat_type, cmd) \ +static guint \ +_vt_cmd_plobj_id_hash_##type (const NMPlatformObject *_obj) \ +{ \ + const plat_type *const obj = (const plat_type *) _obj; \ + guint hash; \ + { cmd; } \ + return hash; \ +} +_vt_cmd_plobj_id_hash (link, NMPlatformLink, { + /* libnl considers: + * .oo_id_attrs = LINK_ATTR_IFINDEX | LINK_ATTR_FAMILY, + */ + hash = (guint) 3982791431; + hash = hash + ((guint) obj->ifindex); +}) +_vt_cmd_plobj_id_hash (ip4_address, NMPlatformIP4Address, { + /* libnl considers: + * .oo_id_attrs = (ADDR_ATTR_FAMILY | ADDR_ATTR_IFINDEX | + * ADDR_ATTR_LOCAL | ADDR_ATTR_PREFIXLEN), + */ + hash = (guint) 3591309853; + hash = hash + ((guint) obj->ifindex); + hash = hash * 33 + ((guint) obj->plen); + hash = hash * 33 + ((guint) obj->address); +}) +_vt_cmd_plobj_id_hash (ip6_address, NMPlatformIP6Address, { + hash = (guint) 2907861637; + hash = hash + ((guint) obj->ifindex); + hash = hash * 33 + ((guint) obj->plen); + hash = hash * 33 + _id_hash_ip6_addr (&obj->address); +}) +_vt_cmd_plobj_id_hash (ip4_route, NMPlatformIP4Route, { + /* libnl considers: + * .oo_id_attrs = (ROUTE_ATTR_FAMILY | ROUTE_ATTR_TOS | + * ROUTE_ATTR_TABLE | ROUTE_ATTR_DST | + * ROUTE_ATTR_PRIO), + */ + hash = (guint) 2569857221; + hash = hash + ((guint) obj->ifindex); + hash = hash * 33 + ((guint) obj->plen); + hash = hash * 33 + ((guint) obj->metric); + hash = hash * 33 + ((guint) obj->network); +}) +_vt_cmd_plobj_id_hash (ip6_route, NMPlatformIP6Route, { + hash = (guint) 3999787007; + hash = hash + ((guint) obj->ifindex); + hash = hash * 33 + ((guint) obj->plen); + hash = hash * 33 + ((guint) obj->metric); + hash = hash * 33 + _id_hash_ip6_addr (&obj->network); +}) + +gboolean +nmp_object_is_alive (const NMPObject *obj) +{ + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_alive (obj); +} + +static gboolean +_vt_cmd_obj_is_alive_link (const NMPObject *obj) +{ + return obj->object.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 obj->object.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 is cloned). + * + * Instead we create a dead object, and nmp_cache_update_netlink() + * will remove the old version of the update. + **/ + return obj->object.ifindex > 0 && (obj->ip_route.source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED); +} + +gboolean +nmp_object_is_visible (const NMPObject *obj) + +{ + /* for convenience, allow NULL. */ + if (!obj) + return FALSE; + + return NMP_OBJECT_GET_CLASS (obj)->cmd_obj_is_visible (obj); +} + +static gboolean +_vt_cmd_obj_is_visible_link (const NMPObject *obj) +{ + return obj->object.ifindex > 0 && obj->_link.netlink.is_in_netlink; +} + +static gboolean +_vt_cmd_obj_is_visible_ipx_address (const NMPObject *obj) +{ + return obj->object.ifindex > 0; +} + +static gboolean +_vt_cmd_obj_is_visible_ipx_route (const NMPObject *obj) +{ + NMIPConfigSource source = obj->ip_route.source; + + return obj->object.ifindex > 0 && (source != _NM_IP_CONFIG_SOURCE_RTPROT_KERNEL && source != _NM_IP_CONFIG_SOURCE_RTM_F_CLONED); +} + +/******************************************************************/ + +/** + * nmp_object_from_nl: + * @platform: platform instance (needed to lookup sysctl) + * @nlo: + * @id_only: if %TRUE, only fill the id fields of the object and leave the + * other fields unset. This is useful to create a needle to lookup a matching + * item in the cache. + * @complete_from_cache: sometimes the netlink object doesn't contain all information. + * If true, look them up in the cache and preserve the original value. + * + * Convert a libnl object to a platform object. + * Returns: a NMPObject containing @nlo. If @id_only is %TRUE, only the id fields + * are defined. + **/ +NMPObject * +nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache) +{ + ObjectType obj_type = _nlo_get_object_type (nlo); + NMPObject *obj; + + if (obj_type == OBJECT_TYPE_UNKNOWN) + return NULL; + + obj = nmp_object_new (obj_type, NULL); + + if (!NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_init_from_nl (platform, &obj->object, nlo, id_only, complete_from_cache)) { + nmp_object_unref (obj); + return NULL; + } + return obj; +} + +struct nl_object * +nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only) +{ + return NMP_OBJECT_GET_CLASS (obj)->cmd_plobj_to_nl (platform, &obj->object, id_only); +} + +/******************************************************************/ + +gboolean +nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b) +{ + /* just memcmp() the entire id. This is potentially dangerous, because + * the struct is not __attribute__((packed)) and not all types have the + * same size. It is important, to memset() the entire struct to 0, + * not only the relevant fields. + * + * You anyway should use the nmp_cache_id_init_*() functions on a stack-allocated + * struct. */ + return memcmp (a, b, sizeof (NMPCacheId)) == 0; +} + +guint +nmp_cache_id_hash (const NMPCacheId *id) +{ + guint hash = 5381; + guint i; + + for (i = 0; i < sizeof (NMPCacheId); i++) + hash = ((hash << 5) + hash) + ((char *) id)[i]; /* hash * 33 + c */ + return hash; +} + +NMPCacheId * +nmp_cache_id_clone (const NMPCacheId *id) +{ + NMPCacheId *id2; + + id2 = g_slice_new (NMPCacheId); + memcpy (id2, id, sizeof (NMPCacheId)); + return id2; +} + +void +nmp_cache_id_destroy (NMPCacheId *id) +{ + g_slice_free (NMPCacheId, id); +} + +/******************************************************************/ + +NMPCacheId _nmp_cache_id_static; + +NMPCacheId * +nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type) +{ + memset (id, 0, sizeof (NMPCacheId)); + id->_id_type = id_type; + return id; +} + +NMPCacheId * +nmp_cache_id_init_object_type (NMPCacheId *id, ObjectType obj_type) +{ + nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_OBJECT_TYPE); + id->object_type.obj_type = obj_type; + return id; +} + +NMPCacheId * +nmp_cache_id_init_links (NMPCacheId *id, gboolean visible_only) +{ + if (visible_only) + return nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY); + else + return nmp_cache_id_init_object_type (id, OBJECT_TYPE_LINK); +} + +NMPCacheId * +nmp_cache_id_init_addrroute_by_ifindex (NMPCacheId *id, ObjectType obj_type, int ifindex) +{ + nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX); + id->addrroute_by_ifindex.obj_type = obj_type; + id->addrroute_by_ifindex.ifindex = ifindex; + return id; +} + +NMPCacheId * +nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPCacheIdType id_type, gboolean is_v4, int ifindex) +{ + g_return_val_if_fail (NM_IN_SET (id_type, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT), NULL); + nmp_cache_id_init (id, id_type); + id->routes_visible.is_v4 = !!is_v4; + id->routes_visible.ifindex = ifindex; + return id; +} + +/******************************************************************/ + +static gboolean +_nmp_object_init_cache_id (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + const NMPClass *klass = NMP_OBJECT_GET_CLASS (obj); + + if (id_type == NMP_CACHE_ID_TYPE_OBJECT_TYPE) { + *out_id = nmp_cache_id_init_object_type (id, klass->obj_type); + return TRUE; + } + return klass->cmd_obj_init_cache_id (obj, id_type, id, out_id); +} + +static gboolean +_vt_cmd_obj_init_cache_id_link (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + switch (id_type) { + case NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY: + if (_vt_cmd_obj_is_visible_link (obj)) { + *out_id = nmp_cache_id_init_links (id, TRUE); + return TRUE; + } + break; + default: + return FALSE; + } + *out_id = NULL; + return TRUE; +} + +static gboolean +_vt_cmd_obj_init_cache_id_ip4_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + switch (id_type) { + case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX: + if (_vt_cmd_obj_is_visible_ipx_address (obj)) { + *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP4_ADDRESS, obj->object.ifindex); + return TRUE; + } + break; + default: + return FALSE; + } + *out_id = NULL; + return TRUE; +} + +static gboolean +_vt_cmd_obj_init_cache_id_ip6_address (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + switch (id_type) { + case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX: + if (_vt_cmd_obj_is_visible_ipx_address (obj)) { + *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP6_ADDRESS, obj->object.ifindex); + return TRUE; + } + break; + default: + return FALSE; + } + *out_id = NULL; + return TRUE; +} + +static gboolean +_vt_cmd_obj_init_cache_id_ip4_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + switch (id_type) { + case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX: + if (_vt_cmd_obj_is_visible_ipx_route (obj)) { + *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP4_ROUTE, obj->object.ifindex); + return TRUE; + } + break; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL: + if (_vt_cmd_obj_is_visible_ipx_route (obj)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex); + return TRUE; + } + break; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT: + if ( _vt_cmd_obj_is_visible_ipx_route (obj) + && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex); + return TRUE; + } + break; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT: + if ( _vt_cmd_obj_is_visible_ipx_route (obj) + && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, TRUE, obj->object.ifindex); + return TRUE; + } + break; + default: + return FALSE; + } + *out_id = NULL; + return TRUE; +} + +static gboolean +_vt_cmd_obj_init_cache_id_ip6_route (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id) +{ + switch (id_type) { + case NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX: + *out_id = nmp_cache_id_init_addrroute_by_ifindex (id, OBJECT_TYPE_IP6_ROUTE, obj->object.ifindex); + return TRUE; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL: + if (_vt_cmd_obj_is_visible_ipx_route (obj)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex); + return TRUE; + } + break; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT: + if ( _vt_cmd_obj_is_visible_ipx_route (obj) + && !NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex); + return TRUE; + } + break; + case NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT: + if ( _vt_cmd_obj_is_visible_ipx_route (obj) + && NM_PLATFORM_IP_ROUTE_IS_DEFAULT (&obj->ip_route)) { + *out_id = nmp_cache_id_init_routes_visible (id, id_type, FALSE, obj->object.ifindex); + return TRUE; + } + break; + default: + return FALSE; + } + *out_id = NULL; + return TRUE; +} + +/******************************************************************/ + +gboolean +nmp_cache_use_udev_detect () +{ + return access ("/sys", W_OK) == 0; +} + +gboolean +nmp_cache_use_udev_get (const NMPCache *cache) +{ + g_return_val_if_fail (cache, TRUE); + + return cache->use_udev; +} + +gboolean +nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev) +{ + g_return_val_if_fail (cache, FALSE); + + use_udev = !!use_udev; + if (use_udev == cache->use_udev) + return FALSE; + + cache->use_udev = use_udev; + return TRUE; +} + +/******************************************************************/ + +/** + * 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) +{ + const NMPlatformLink *const *links; + gboolean is_lower_up = FALSE; + guint len, i; + + if ( !master + || NMP_OBJECT_GET_TYPE (master) != 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.flags, IFF_LOWER_UP)) + return !!master->link.connected; + + if (potential_slave && NMP_OBJECT_GET_TYPE (potential_slave) != OBJECT_TYPE_LINK) + potential_slave = NULL; + + if ( potential_slave + && nmp_object_is_visible (potential_slave) + && potential_slave->link.ifindex > 0 + && potential_slave->link.master == master->link.ifindex + && potential_slave->link.connected) { + is_lower_up = TRUE; + } else { + links = (const NMPlatformLink *const *) nmp_cache_lookup_multi (cache, nmp_cache_id_init_links (NMP_CACHE_ID_STATIC, FALSE), &len); + for (i = 0; i < len; i++) { + const NMPlatformLink *link = links[i]; + const NMPObject *obj = NMP_OBJECT_UP_CAST ((NMPlatformObject *) link); + + nm_assert (NMP_OBJECT_GET_TYPE (NMP_OBJECT_UP_CAST ((NMPlatformObject *) link)) == OBJECT_TYPE_LINK); + + if ( (!potential_slave || potential_slave->link.ifindex != link->ifindex) + && ignore_slave != obj + && link->ifindex > 0 + && link->master == master->link.ifindex + && nmp_object_is_visible (obj) + && link->connected) { + 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 + * toogling 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; +} + +/******************************************************************/ + +const NMPlatformObject *const * +nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len) +{ + return (const NMPlatformObject *const *) nm_multi_index_lookup (cache->idx_multi, + (const NMMultiIndexId *) cache_id, + out_len); +} + +GArray * +nmp_cache_lookup_multi_to_array (const NMPCache *cache, ObjectType obj_type, const NMPCacheId *cache_id) +{ + const NMPClass *klass = nmp_class_from_type (obj_type); + guint len, i; + const NMPlatformObject *const *objects; + GArray *array; + + g_return_val_if_fail (klass, NULL); + + objects = nmp_cache_lookup_multi (cache, cache_id, &len); + array = g_array_sized_new (FALSE, FALSE, klass->sizeof_public, len); + + for (i = 0; i < len; i++) { + nm_assert (NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[i])) == klass); + g_array_append_vals (array, objects[i], 1); + } + return array; +} + +const NMPObject * +nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj) +{ + g_return_val_if_fail (obj, NULL); + + return g_hash_table_lookup (cache->idx_main, obj); +} + +const NMPObject * +nmp_cache_lookup_link (const NMPCache *cache, int ifindex) +{ + NMPObject obj_needle; + + return nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex)); +} + +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; + const NMPlatformObject *const *list; + guint i, len; + NMPCacheId cache_id; + + 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 { + list = nmp_cache_lookup_multi (cache, nmp_cache_id_init_object_type (&cache_id, OBJECT_TYPE_LINK), &len); + for (i = 0; i < len; i++) { + obj = NMP_OBJECT_UP_CAST (list[i]); + + if (visible_only && !nmp_object_is_visible (obj)) + continue; + if (link_type != NM_LINK_TYPE_NONE && obj->link.type != link_type) + continue; + if (ifname && strcmp (ifname, obj->link.name)) + continue; + if (match_fn && !match_fn (obj, user_data)) + continue; + + return obj; + } + return NULL; + } +} + +GHashTable * +nmp_cache_lookup_all_to_hash (const NMPCache *cache, + NMPCacheId *cache_id, + GHashTable *hash) +{ + NMMultiIndexIdIter iter; + gpointer plobj; + + nm_multi_index_id_iter_init (&iter, cache->idx_multi, (const NMMultiIndexId *) cache_id); + + if (nm_multi_index_id_iter_next (&iter, &plobj)) { + if (!hash) + hash = g_hash_table_new_full (NULL, NULL, (GDestroyNotify) nmp_object_unref, NULL); + + do { + g_hash_table_add (hash, nmp_object_ref (NMP_OBJECT_UP_CAST (plobj))); + } while (nm_multi_index_id_iter_next (&iter, &plobj)); + } + + return hash; +} + +/******************************************************************/ + +static void +_nmp_cache_update_cache (NMPCache *cache, NMPObject *obj, gboolean remove) +{ + NMPCacheIdType id_type; + + for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) { + NMPCacheId cache_id_storage; + const NMPCacheId *cache_id; + + if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id)) + continue; + if (!cache_id) + continue; + + /* We don't put @obj itself into the multi index, but &obj->object. As of now, all + * users expect a pointer to NMPlatformObject, not NMPObject. + * You can use NMP_OBJECT_UP_CAST() to retrieve the original @obj pointer. + * + * If need be, we could determine based on @id_type which pointer we want to store. */ + + if (remove) { + if (!nm_multi_index_remove (cache->idx_multi, &cache_id->base, &obj->object)) + g_assert_not_reached (); + } else { + if (!nm_multi_index_add (cache->idx_multi, &cache_id->base, &obj->object)) + g_assert_not_reached (); + } + } +} + +static void +_nmp_cache_update_add (NMPCache *cache, NMPObject *obj) +{ + nm_assert (!obj->is_cached); + nmp_object_ref (obj); + nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object)); + if (!g_hash_table_add (cache->idx_main, obj)) + g_assert_not_reached (); + obj->is_cached = TRUE; + _nmp_cache_update_cache (cache, obj, FALSE); +} + +static void +_nmp_cache_update_remove (NMPCache *cache, NMPObject *obj) +{ + nm_assert (obj->is_cached); + _nmp_cache_update_cache (cache, obj, TRUE); + obj->is_cached = FALSE; + if (!g_hash_table_remove (cache->idx_main, obj)) + g_assert_not_reached (); + + /* @obj is possibly a dangling pointer at this point. No problem, multi-index doesn't dereference. */ + nm_assert (!nm_multi_index_lookup_first_by_value (cache->idx_multi, &obj->object)); +} + +static void +_nmp_cache_update_update (NMPCache *cache, NMPObject *obj, const NMPObject *new) +{ + NMPCacheIdType id_type; + + nm_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (new)); + nm_assert (obj->is_cached); + nm_assert (!new->is_cached); + + for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) { + NMPCacheId cache_id_storage_obj, cache_id_storage_new; + const NMPCacheId *cache_id_obj, *cache_id_new; + + if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage_obj, &cache_id_obj)) + continue; + if (!_nmp_object_init_cache_id (new, id_type, &cache_id_storage_new, &cache_id_new)) + g_assert_not_reached (); + if (!nm_multi_index_move (cache->idx_multi, (NMMultiIndexId *) cache_id_obj, (NMMultiIndexId *) cache_id_new, &obj->object)) + g_assert_not_reached (); + } + nmp_object_copy (obj, new, FALSE); +} + +NMPCacheOpsType +nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data) +{ + NMPObject *old; + + nm_assert (NMP_OBJECT_IS_VALID (obj)); + + old = g_hash_table_lookup (cache->idx_main, obj); + if (!old) { + if (out_obj) + *out_obj = NULL; + if (out_was_visible) + *out_was_visible = FALSE; + return NMP_CACHE_OPS_UNCHANGED; + } + + if (out_obj) + *out_obj = nmp_object_ref (old); + if (out_was_visible) + *out_was_visible = nmp_object_is_visible (old); + if (equals_by_ptr && old != obj) { + /* We found an identical object, but we only delete it if it's the same pointer as + * @obj. */ + return NMP_CACHE_OPS_UNCHANGED; + } + if (pre_hook) + pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data); + _nmp_cache_update_remove (cache, old); + return NMP_CACHE_OPS_REMOVED; +} + +NMPCacheOpsType +nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj_needle, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data) +{ + if (NMP_OBJECT_GET_TYPE (obj_needle) == OBJECT_TYPE_LINK) { + NMPObject *old; + auto_nmp_obj NMPObject *obj = NULL; + + /* For nmp_cache_remove_netlink() we have an incomplete @obj_needle instance to be + * removed from netlink. Link objects are alive without being in netlink when they + * have a udev-device. All we want to do in this case is clear the netlink.is_in_netlink + * flag. */ + + old = (NMPObject *) nmp_cache_lookup_link (cache, obj_needle->link.ifindex); + if (!old) { + if (out_obj) + *out_obj = NULL; + if (out_was_visible) + *out_was_visible = FALSE; + return NMP_CACHE_OPS_UNCHANGED; + } + + if (out_obj) + *out_obj = nmp_object_ref (old); + if (out_was_visible) + *out_was_visible = nmp_object_is_visible (old); + + if (!old->_link.netlink.is_in_netlink) { + nm_assert (old->_link.udev.device); + return NMP_CACHE_OPS_UNCHANGED; + } + + if (!old->_link.udev.device) { + /* the update would make @old invalid. Remove it. */ + if (pre_hook) + pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data); + _nmp_cache_update_remove (cache, old); + return NMP_CACHE_OPS_REMOVED; + } + + obj = nmp_object_clone (old, FALSE); + obj->_link.netlink.is_in_netlink = FALSE; + + _nmp_object_fixup_link_master_connected (obj, cache); + _nmp_object_fixup_link_udev_fields (obj, cache->use_udev); + + if (pre_hook) + pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data); + _nmp_cache_update_update (cache, old, obj); + return NMP_CACHE_OPS_UPDATED; + } else + return nmp_cache_remove (cache, obj_needle, FALSE, out_obj, out_was_visible, pre_hook, user_data); +} + +/** + * nmp_cache_update_netlink: + * @cache: the platform cache + * @obj: 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. + * @out_obj: (allow-none): (out): return the object instance that is inside + * the cache. If you specify non %NULL, you must always unref the returned + * instance. If the return value indicates that the object was removed, + * the object is no longer in the cache. Even if the return value indicates + * that the object was unchanged, it will still return @out_obj -- if + * such an object is in the cache. + * @out_was_visible: (allow-none): (out): whether the object was visible before + * the update operation. + * @pre_hook: (allow-none): a callback *before* the object gets updated. You cannot + * influence the outcome and must not do anything beyong inspecting the changes. + * @user_data: + * + * Returns: how the cache changed. + **/ +NMPCacheOpsType +nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data) +{ + NMPObject *old; + + nm_assert (NMP_OBJECT_IS_VALID (obj)); + nm_assert (!NMP_OBJECT_IS_STACKINIT (obj)); + nm_assert (!obj->is_cached); + + /* 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) != OBJECT_TYPE_LINK || + ( !obj->_link.udev.device + && !obj->link.driver + && !obj->link.udi)); + + old = g_hash_table_lookup (cache->idx_main, obj); + + if (out_obj) + *out_obj = NULL; + if (out_was_visible) + *out_was_visible = FALSE; + + if (!old) { + if (!nmp_object_is_alive (obj)) + return NMP_CACHE_OPS_UNCHANGED; + + if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) { + _nmp_object_fixup_link_master_connected (obj, cache); + _nmp_object_fixup_link_udev_fields (obj, cache->use_udev); + } + + if (out_obj) + *out_obj = nmp_object_ref (obj); + + if (pre_hook) + pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data); + _nmp_cache_update_add (cache, obj); + return NMP_CACHE_OPS_ADDED; + } else if (old == obj) { + /* updating a cached object inplace is not supported because the object contributes to hash-key + * for NMMultiIndex. Modifying an object that is inside NMMultiIndex means that these + * keys change. + * The problem is, that for a given object NMMultiIndex does not support (efficient) + * reverse lookup to get all the NMPCacheIds to which it belongs. If that would be implemented, + * it would be possible to implement inplace-update. + * + * There is an un-optimized reverse lookup via nm_multi_index_iter_init(), but we don't want + * that because we might have a large number of indexes to search. + * + * We could add efficient reverse lookup by adding a reverse index to NMMultiIndex. But that + * also adds some cost to support an (uncommon?) usage pattern. + * + * Instead we just don't support it. Actually, we expect the user to + * create a new instance with nmp_object_from_nl(). That is what nmp_cache_update_netlink(). + * + * TL;DR: a cached object must never be modified. + */ + g_assert_not_reached (); + } else { + gboolean is_alive = FALSE; + + nm_assert (old->is_cached); + + if (out_obj) + *out_obj = nmp_object_ref (old); + if (out_was_visible) + *out_was_visible = nmp_object_is_visible (old); + + if (NMP_OBJECT_GET_TYPE (obj) == OBJECT_TYPE_LINK) { + if (!obj->_link.netlink.is_in_netlink) { + if (!old->_link.netlink.is_in_netlink) { + nm_assert (old->_link.udev.device); + return NMP_CACHE_OPS_UNCHANGED; + } + if (old->_link.udev.device) { + /* @obj 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. The usecase is when receiving + * a valid @obj instance from netlink with RTM_DELROUTE. + */ + is_alive = TRUE; + } + } else + is_alive = TRUE; + + if (is_alive) { + _nmp_object_fixup_link_master_connected (obj, cache); + + /* Merge the netlink parts with what we have from udev. */ + g_clear_object (&obj->_link.udev.device); + obj->_link.udev.device = old->_link.udev.device ? g_object_ref (old->_link.udev.device) : NULL; + _nmp_object_fixup_link_udev_fields (obj, cache->use_udev); + } + } else + is_alive = nmp_object_is_alive (obj); + + if (!is_alive) { + /* the update would make @old invalid. Remove it. */ + if (pre_hook) + pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data); + _nmp_cache_update_remove (cache, old); + return NMP_CACHE_OPS_REMOVED; + } + + if (nmp_object_equal (old, obj)) + return NMP_CACHE_OPS_UNCHANGED; + + if (pre_hook) + pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data); + _nmp_cache_update_update (cache, old, obj); + return NMP_CACHE_OPS_UPDATED; + } +} + +NMPCacheOpsType +nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data) +{ + NMPObject *old; + auto_nmp_obj NMPObject *obj = NULL; + + old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex); + + if (out_obj) + *out_obj = NULL; + if (out_was_visible) + *out_was_visible = FALSE; + + if (!old) { + if (!udev_device) + return NMP_CACHE_OPS_UNCHANGED; + + obj = nmp_object_new (OBJECT_TYPE_LINK, NULL); + obj->link.ifindex = ifindex; + obj->_link.udev.device = g_object_ref (udev_device); + + _nmp_object_fixup_link_udev_fields (obj, cache->use_udev); + + nm_assert (nmp_object_is_alive (obj)); + + if (out_obj) + *out_obj = nmp_object_ref (obj); + + if (pre_hook) + pre_hook (cache, NULL, obj, NMP_CACHE_OPS_ADDED, user_data); + _nmp_cache_update_add (cache, obj); + return NMP_CACHE_OPS_ADDED; + } else { + nm_assert (old->is_cached); + + if (out_obj) + *out_obj = nmp_object_ref (old); + if (out_was_visible) + *out_was_visible = nmp_object_is_visible (old); + + if (old->_link.udev.device == udev_device) + return NMP_CACHE_OPS_UNCHANGED; + + if (!udev_device && !old->_link.netlink.is_in_netlink) { + /* the update would make @old invalid. Remove it. */ + if (pre_hook) + pre_hook (cache, old, NULL, NMP_CACHE_OPS_REMOVED, user_data); + _nmp_cache_update_remove (cache, old); + return NMP_CACHE_OPS_REMOVED; + } + + obj = nmp_object_clone (old, FALSE); + + g_clear_object (&obj->_link.udev.device); + obj->_link.udev.device = udev_device ? g_object_ref (udev_device) : NULL; + + _nmp_object_fixup_link_udev_fields (obj, cache->use_udev); + + nm_assert (nmp_object_is_alive (obj)); + + if (pre_hook) + pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data); + _nmp_cache_update_update (cache, old, obj); + return NMP_CACHE_OPS_UPDATED; + } +} + +NMPCacheOpsType +nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data) +{ + NMPObject *old; + auto_nmp_obj NMPObject *obj = NULL; + + old = (NMPObject *) nmp_cache_lookup_link (cache, ifindex); + + if (!old) { + if (out_obj) + *out_obj = NULL; + if (out_was_visible) + *out_was_visible = FALSE; + + return NMP_CACHE_OPS_UNCHANGED; + } + + nm_assert (old->is_cached); + + if (out_obj) + *out_obj = nmp_object_ref (old); + if (out_was_visible) + *out_was_visible = nmp_object_is_visible (old); + + if (!nmp_cache_link_connected_needs_toggle (cache, old, NULL, NULL)) + return NMP_CACHE_OPS_UNCHANGED; + + obj = nmp_object_clone (old, FALSE); + obj->link.connected = !old->link.connected; + + nm_assert (nmp_object_is_alive (obj)); + + if (pre_hook) + pre_hook (cache, old, obj, NMP_CACHE_OPS_UPDATED, user_data); + _nmp_cache_update_update (cache, old, obj); + return NMP_CACHE_OPS_UPDATED; +} + +/******************************************************************/ + +NMPCache * +nmp_cache_new () +{ + NMPCache *cache = g_new (NMPCache, 1); + + cache->idx_main = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash, + (GEqualFunc) nmp_object_id_equal, + (GDestroyNotify) nmp_object_unref, + NULL); + cache->idx_multi = nm_multi_index_new ((NMMultiIndexFuncHash) nmp_cache_id_hash, + (NMMultiIndexFuncEqual) nmp_cache_id_equal, + (NMMultiIndexFuncClone) nmp_cache_id_clone, + (NMMultiIndexFuncDestroy) nmp_cache_id_destroy); + cache->use_udev = nmp_cache_use_udev_detect (); + return cache; +} + +void +nmp_cache_free (NMPCache *cache) +{ + GHashTableIter iter; + NMPObject *obj; + + /* No need to cumbersomely remove the objects properly. They are not hooked up + * in a complicated way, we can just unref them together with cache->idx_main. + * + * But we must clear the @is_cached flag. */ + g_hash_table_iter_init (&iter, cache->idx_main); + while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) { + nm_assert (obj->is_cached); + obj->is_cached = FALSE; + } + + nm_multi_index_free (cache->idx_multi); + g_hash_table_unref (cache->idx_main); + + g_free (cache); +} + +/******************************************************************/ + +void +ASSERT_nmp_cache_is_consistent (const NMPCache *cache) +{ +#ifdef NM_MORE_ASSERTS + NMMultiIndexIter iter_multi; + GHashTableIter iter_hash; + guint i, len; + NMPCacheId cache_id_storage; + const NMPCacheId *cache_id, *cache_id2; + const NMPlatformObject *const *objects; + const NMPObject *obj; + + g_assert (cache); + + g_hash_table_iter_init (&iter_hash, cache->idx_main); + while (g_hash_table_iter_next (&iter_hash, (gpointer *) &obj, NULL)) { + NMPCacheIdType id_type; + + g_assert (NMP_OBJECT_IS_VALID (obj)); + g_assert (nmp_object_is_alive (obj)); + + for (id_type = 0; id_type <= NMP_CACHE_ID_TYPE_MAX; id_type++) { + if (!_nmp_object_init_cache_id (obj, id_type, &cache_id_storage, &cache_id)) + continue; + if (!cache_id) + continue; + g_assert (nm_multi_index_contains (cache->idx_multi, &cache_id->base, &obj->object)); + } + } + + nm_multi_index_iter_init (&iter_multi, cache->idx_multi, NULL); + while (nm_multi_index_iter_next (&iter_multi, + (const NMMultiIndexId **) &cache_id, + (void *const**) &objects, + &len)) { + g_assert (len > 0 && objects && objects[len] == NULL); + + for (i = 0; i < len; i++) { + g_assert (objects[i]); + obj = NMP_OBJECT_UP_CAST (objects[i]); + g_assert (NMP_OBJECT_IS_VALID (obj)); + + /* for now, enforce that all objects for a certain index are of the same type. */ + g_assert (NMP_OBJECT_GET_CLASS (obj) == NMP_OBJECT_GET_CLASS (NMP_OBJECT_UP_CAST (objects[0]))); + + if (!_nmp_object_init_cache_id (obj, cache_id->_id_type, &cache_id_storage, &cache_id2)) + g_assert_not_reached (); + g_assert (cache_id2); + g_assert (nmp_cache_id_equal (cache_id, cache_id2)); + g_assert_cmpint (nmp_cache_id_hash (cache_id), ==, nmp_cache_id_hash (cache_id2)); + + g_assert (obj == g_hash_table_lookup (cache->idx_main, obj)); + } + } +#endif +} +/******************************************************************/ + +const NMPClass _nmp_classes[OBJECT_TYPE_MAX] = { + [OBJECT_TYPE_LINK - 1] = { + .obj_type = OBJECT_TYPE_LINK, + .sizeof_data = sizeof (NMPObjectLink), + .sizeof_public = sizeof (NMPlatformLink), + .obj_type_name = "link", + .nl_type = "route/link", + .addr_family = AF_UNSPEC, + .rtm_gettype = RTM_GETLINK, + .signal_type = NM_PLATFORM_SIGNAL_LINK_CHANGED, + .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_link, + .cmd_obj_equal = _vt_cmd_obj_equal_link, + .cmd_obj_copy = _vt_cmd_obj_copy_link, + .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_link, + .cmd_obj_dispose = _vt_cmd_obj_dispose_link, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_link, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_link, + .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_link, + .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_link, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_link, + .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_link, + .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_link, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_link, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_link_to_string, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_link_cmp, + }, + [OBJECT_TYPE_IP4_ADDRESS - 1] = { + .obj_type = OBJECT_TYPE_IP4_ADDRESS, + .sizeof_data = sizeof (NMPObjectIP4Address), + .sizeof_public = sizeof (NMPlatformIP4Address), + .obj_type_name = "ip4-address", + .nl_type = "route/addr", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETADDR, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, + .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip4_address, + .cmd_obj_equal = _vt_cmd_obj_equal_plain, + .cmd_obj_copy = _vt_cmd_obj_copy_plain, + .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip4_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_address, + .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip4_address, + .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip4_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_address, + .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip4_address, + .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip4_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_address, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_address_to_string, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_address_cmp, + }, + [OBJECT_TYPE_IP6_ADDRESS - 1] = { + .obj_type = OBJECT_TYPE_IP6_ADDRESS, + .sizeof_data = sizeof (NMPObjectIP6Address), + .sizeof_public = sizeof (NMPlatformIP6Address), + .obj_type_name = "ip6-address", + .nl_type = "route/addr", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETADDR, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, + .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip6_address, + .cmd_obj_equal = _vt_cmd_obj_equal_plain, + .cmd_obj_copy = _vt_cmd_obj_copy_plain, + .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip6_address, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_address, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_address, + .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip6_address, + .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip6_address, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_address, + .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip6_address, + .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip6_address, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_address, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_address_to_string, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_address_cmp + }, + [OBJECT_TYPE_IP4_ROUTE - 1] = { + .obj_type = OBJECT_TYPE_IP4_ROUTE, + .sizeof_data = sizeof (NMPObjectIP4Route), + .sizeof_public = sizeof (NMPlatformIP4Route), + .obj_type_name = "ip4-route", + .nl_type = "route/route", + .addr_family = AF_INET, + .rtm_gettype = RTM_GETROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, + .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip4_route, + .cmd_obj_equal = _vt_cmd_obj_equal_plain, + .cmd_obj_copy = _vt_cmd_obj_copy_plain, + .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip4_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_route, + .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip4_route, + .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip4_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip4_route, + .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip4_route, + .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip4_route, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip4_route, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip4_route_to_string, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip4_route_cmp, + }, + [OBJECT_TYPE_IP6_ROUTE - 1] = { + .obj_type = OBJECT_TYPE_IP6_ROUTE, + .sizeof_data = sizeof (NMPObjectIP6Route), + .sizeof_public = sizeof (NMPlatformIP6Route), + .obj_type_name = "ip6-route", + .nl_type = "route/route", + .addr_family = AF_INET6, + .rtm_gettype = RTM_GETROUTE, + .signal_type = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, + .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ip6_route, + .cmd_obj_equal = _vt_cmd_obj_equal_plain, + .cmd_obj_copy = _vt_cmd_obj_copy_plain, + .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip6_route, + .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, + .cmd_obj_is_visible = _vt_cmd_obj_is_visible_ipx_route, + .cmd_plobj_init_from_nl = _nmp_vt_cmd_plobj_init_from_nl_ip6_route, + .cmd_plobj_to_nl = _nmp_vt_cmd_plobj_to_nl_ip6_route, + .cmd_plobj_id_copy = _vt_cmd_plobj_id_copy_ip6_route, + .cmd_plobj_id_equal = _vt_cmd_plobj_id_equal_ip6_route, + .cmd_plobj_id_hash = _vt_cmd_plobj_id_hash_ip6_route, + .cmd_plobj_to_string_id = _vt_cmd_plobj_to_string_id_ip6_route, + .cmd_plobj_to_string = (const char *(*) (const NMPlatformObject *obj)) nm_platform_ip6_route_to_string, + .cmd_plobj_cmp = (int (*) (const NMPlatformObject *obj1, const NMPlatformObject *obj2)) nm_platform_ip6_route_cmp, + }, +}; diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h index fff607048e..99f0eaf0c8 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -24,6 +24,12 @@ #include "config.h" #include "nm-platform.h" +#include "nm-multi-index.h" +#include "nm-macros-internal.h" + +#include <netlink/netlink.h> +#include <gudev/gudev.h> + typedef enum { /*< skip >*/ OBJECT_TYPE_UNKNOWN, @@ -36,4 +42,342 @@ typedef enum { /*< skip >*/ OBJECT_TYPE_MAX = __OBJECT_TYPE_LAST - 1, } ObjectType; +typedef enum { /*< skip >*/ + NMP_OBJECT_TO_STRING_ID, + NMP_OBJECT_TO_STRING_PUBLIC, + NMP_OBJECT_TO_STRING_ALL, +} NMPObjectToStringMode; + +typedef enum { /*< skip >*/ + NMP_CACHE_OPS_UNCHANGED = NM_PLATFORM_SIGNAL_NONE, + NMP_CACHE_OPS_UPDATED = NM_PLATFORM_SIGNAL_CHANGED, + NMP_CACHE_OPS_ADDED = NM_PLATFORM_SIGNAL_ADDED, + NMP_CACHE_OPS_REMOVED = NM_PLATFORM_SIGNAL_REMOVED, +} NMPCacheOpsType; + +/* The NMPCacheIdType are the different index types. + * + * An object of a certain object-type, can be candidate to being + * indexed by a certain NMPCacheIdType or not. For example, all + * objects are indexed via an index of type NMP_CACHE_ID_TYPE_OBJECT_TYPE, + * but only route objects are indexed by NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL. + * + * Of one index type, there can be different indexes or not. + * For example, there is only one single instance of + * NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY, because this instance is + * applicable for all link objects. + * But there are different instances of NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX + * type index, which differ in v4/v6, the ifindex, and whether the index + * is for routes or address instances. + * + * But one object, can only be indexed by one particular index of one + * type. For example, a certain address instance is only indexed by + * the index NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX with matching v4/v6 + * and ifindex. + * */ +typedef enum { /*< skip >*/ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + + NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY, + + NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX, + + /* three indeces for the visibile routes. */ + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT, + + __NMP_CACHE_ID_TYPE_MAX, + NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1, +} NMPCacheIdType; + +typedef struct _NMPObject NMPObject; + +typedef struct { + union { + NMMultiIndexId base; + guint8 _id_type; /* NMPCacheIdType as guint8 */ + struct { + /* NMP_CACHE_ID_TYPE_OBJECT_TYPE */ + guint8 _id_type; + guint8 obj_type; /* ObjectType as guint8 */ + } object_type; + struct { + /* NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY */ + guint8 _id_type; + + /* the @_global_id is only defined by it's type and has no arguments. + * It is used by NMP_CACHE_ID_TYPE_LINKS_VISIBLE_ONLY. There is only + * one single occurence of an index of this type. */ + } _global_id; + struct { + /* NMP_CACHE_ID_TYPE_ADDRROUTE_BY_IFINDEX */ + guint8 _id_type; + guint8 obj_type; /* ObjectType as guint8 */ + int ifindex; + } addrroute_by_ifindex; + struct { + /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ALL */ + /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT */ + /* NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT */ + guint8 _id_type; + guint8 is_v4; + int ifindex; + } routes_visible; + }; +} NMPCacheId; + +extern NMPCacheId _nmp_cache_id_static; +#define NMP_CACHE_ID_STATIC (&_nmp_cache_id_static) + +typedef struct { + ObjectType obj_type; + int addr_family; + int rtm_gettype; + int sizeof_data; + int sizeof_public; + const char *obj_type_name; + const char *nl_type; + const char *signal_type; + + /* returns %FALSE, if the obj type would never have an entry for index type @id_type. If @obj has an index, + * initialize @id and set @out_id to it. Otherwise, @out_id is NULL. */ + gboolean (*cmd_obj_init_cache_id) (const NMPObject *obj, NMPCacheIdType id_type, NMPCacheId *id, const NMPCacheId **out_id); + + gboolean (*cmd_obj_equal) (const NMPObject *obj1, const NMPObject *obj2); + void (*cmd_obj_copy) (NMPObject *dst, const NMPObject *src); + void (*cmd_obj_stackinit_id) (NMPObject *obj, const NMPObject *src); + void (*cmd_obj_dispose) (NMPObject *obj); + gboolean (*cmd_obj_is_alive) (const NMPObject *obj); + gboolean (*cmd_obj_is_visible) (const NMPObject *obj); + + /* functions that operate on NMPlatformObject */ + gboolean (*cmd_plobj_init_from_nl) (NMPlatform *platform, NMPlatformObject *obj, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache); + struct nl_object *(*cmd_plobj_to_nl) (NMPlatform *platform, const NMPlatformObject *obj, gboolean id_only); + void (*cmd_plobj_id_copy) (NMPlatformObject *dst, const NMPlatformObject *src); + gboolean (*cmd_plobj_id_equal) (const NMPlatformObject *obj1, const NMPlatformObject *obj2); + guint (*cmd_plobj_id_hash) (const NMPlatformObject *obj); + const char *(*cmd_plobj_to_string_id) (const NMPlatformObject *obj, char *buf, gsize buf_size); + const char *(*cmd_plobj_to_string) (const NMPlatformObject *obj); + int (*cmd_plobj_cmp) (const NMPlatformObject *obj1, const NMPlatformObject *obj2); +} NMPClass; + +extern const NMPClass _nmp_classes[OBJECT_TYPE_MAX]; + +typedef struct { + NMPlatformLink _public; + + struct { + guint8 is_in_netlink; + } netlink; + + struct { + GUdevDevice *device; + } udev; +} NMPObjectLink; + +typedef struct { + NMPlatformIP4Address _public; +} NMPObjectIP4Address; + +typedef struct { + NMPlatformIP4Route _public; +} NMPObjectIP4Route; + +typedef struct { + NMPlatformIP6Address _public; +} NMPObjectIP6Address; + +typedef struct { + NMPlatformIP6Route _public; +} NMPObjectIP6Route; + +struct _NMPObject { + const NMPClass *_class; + int _ref_count; + guint8 is_cached; + union { + NMPlatformObject object; + + NMPlatformLink link; + NMPObjectLink _link; + + NMPlatformIPAddress ip_address; + NMPlatformIPXAddress ipx_address; + NMPlatformIP4Address ip4_address; + NMPlatformIP6Address ip6_address; + NMPObjectIP4Address _ip4_address; + NMPObjectIP6Address _ip6_address; + + NMPlatformIPRoute ip_route; + NMPlatformIPXRoute ipx_route; + NMPlatformIP4Route ip4_route; + NMPlatformIP6Route ip6_route; + NMPObjectIP4Route _ip4_route; + NMPObjectIP6Route _ip6_route; + }; +}; + +static inline gboolean +NMP_CLASS_IS_VALID (const NMPClass *klass) +{ + return klass >= &_nmp_classes[0] + && klass <= &_nmp_classes[G_N_ELEMENTS (_nmp_classes)] + && ((((char *) klass) - ((char *) NULL)) % (&_nmp_classes[1] - &_nmp_classes[0])) == 0; +} + +#define NMP_REF_COUNT_STACKINIT (G_MAXINT) + +static inline NMPObject * +NMP_OBJECT_UP_CAST(const NMPlatformObject *plobj) +{ + NMPObject *obj; + + obj = plobj + ? (NMPObject *) ( &(((char *) plobj)[-((int) G_STRUCT_OFFSET (NMPObject, object))]) ) + : NULL; + nm_assert (!obj || (obj->_ref_count > 0 && NMP_CLASS_IS_VALID (obj->_class))); + return obj; +} +#define NMP_OBJECT_UP_CAST(plobj) (NMP_OBJECT_UP_CAST ((const NMPlatformObject *) (plobj))) + +static inline gboolean +NMP_OBJECT_IS_VALID (const NMPObject *obj) +{ + nm_assert (!obj || ( obj + && obj->_ref_count > 0 + && NMP_CLASS_IS_VALID (obj->_class))); + + /* There isn't really much to check. Either @obj is NULL, or we must + * assume that it points to valid memory. */ + return obj != NULL; +} + +static inline gboolean +NMP_OBJECT_IS_STACKINIT (const NMPObject *obj) +{ + nm_assert (!obj || NMP_OBJECT_IS_VALID (obj)); + + return obj && obj->_ref_count == NMP_REF_COUNT_STACKINIT; +} + +static inline const NMPClass * +NMP_OBJECT_GET_CLASS (const NMPObject *obj) +{ + nm_assert (NMP_OBJECT_IS_VALID (obj)); + + return obj->_class; +} + +static inline ObjectType +NMP_OBJECT_GET_TYPE (const NMPObject *obj) +{ + nm_assert (!obj || NMP_OBJECT_IS_VALID (obj)); + + return obj ? obj->_class->obj_type : OBJECT_TYPE_UNKNOWN; +} + + + +const NMPClass *nmp_class_from_type (ObjectType obj_type); + +NMPObject *nmp_object_ref (NMPObject *object); +void nmp_object_unref (NMPObject *object); +NMPObject *nmp_object_new (ObjectType obj_type, const NMPlatformObject *plob); +NMPObject *nmp_object_new_link (int ifindex); + +const NMPObject *nmp_object_stackinit (NMPObject *obj, ObjectType obj_type, const NMPlatformObject *plobj); +const NMPObject *nmp_object_stackinit_id (NMPObject *obj, const NMPObject *src); +const NMPObject *nmp_object_stackinit_id_link (NMPObject *obj, int ifindex); +const NMPObject *nmp_object_stackinit_id_ip4_address (NMPObject *obj, int ifindex, guint32 address, int plen); +const NMPObject *nmp_object_stackinit_id_ip6_address (NMPObject *obj, int ifindex, const struct in6_addr *address, int plen); +const NMPObject *nmp_object_stackinit_id_ip4_route (NMPObject *obj, int ifindex, guint32 network, int plen, guint32 metric); +const NMPObject *nmp_object_stackinit_id_ip6_route (NMPObject *obj, int ifindex, const struct in6_addr *network, int plen, guint32 metric); + +const char *nmp_object_to_string (const NMPObject *obj, NMPObjectToStringMode to_string_mode, char *buf, gsize buf_size); +int nmp_object_cmp (const NMPObject *obj1, const NMPObject *obj2); +gboolean nmp_object_equal (const NMPObject *obj1, const NMPObject *obj2); +void nmp_object_copy (NMPObject *dst, const NMPObject *src, gboolean id_only); +NMPObject *nmp_object_clone (const NMPObject *obj, gboolean id_only); +gboolean nmp_object_id_equal (const NMPObject *obj1, const NMPObject *obj2); +guint nmp_object_id_hash (const NMPObject *obj); +gboolean nmp_object_is_alive (const NMPObject *obj); +gboolean nmp_object_is_visible (const NMPObject *obj); + +void _nmp_object_fixup_link_udev_fields (NMPObject *obj, gboolean use_udev); + +#define auto_nmp_obj __attribute__((cleanup(_nmp_auto_obj_cleanup))) +static inline void +_nmp_auto_obj_cleanup (NMPObject **pobj) +{ + nmp_object_unref (*pobj); +} + +typedef struct _NMPCache NMPCache; + +typedef void (*NMPCachePreHook) (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data); +typedef gboolean (*NMPObjectMatchFn) (const NMPObject *obj, gpointer user_data); + +gboolean nmp_cache_id_equal (const NMPCacheId *a, const NMPCacheId *b); +guint nmp_cache_id_hash (const NMPCacheId *id); +NMPCacheId *nmp_cache_id_clone (const NMPCacheId *id); +void nmp_cache_id_destroy (NMPCacheId *id); + +NMPCacheId *nmp_cache_id_init (NMPCacheId *id, NMPCacheIdType id_type); +NMPCacheId *nmp_cache_id_init_object_type (NMPCacheId *id, ObjectType obj_type); +NMPCacheId *nmp_cache_id_init_links (NMPCacheId *id, gboolean visible_only); +NMPCacheId *nmp_cache_id_init_addrroute_by_ifindex (NMPCacheId *id, ObjectType obj_type, int ifindex); +NMPCacheId *nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPCacheIdType id_type, gboolean is_v4, int ifindex); + +const NMPlatformObject *const *nmp_cache_lookup_multi (const NMPCache *cache, const NMPCacheId *cache_id, guint *out_len); +GArray *nmp_cache_lookup_multi_to_array (const NMPCache *cache, ObjectType obj_type, const NMPCacheId *cache_id); +const NMPObject *nmp_cache_lookup_obj (const NMPCache *cache, const NMPObject *obj); +const NMPObject *nmp_cache_lookup_link (const NMPCache *cache, int ifindex); + +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); +GHashTable *nmp_cache_lookup_all_to_hash (const NMPCache *cache, + NMPCacheId *cache_id, + GHashTable *hash); + +gboolean nmp_cache_link_connected_needs_toggle (const NMPCache *cache, const NMPObject *master, const NMPObject *potential_slave, const NMPObject *ignore_slave); +const NMPObject *nmp_cache_link_connected_needs_toggle_by_ifindex (const NMPCache *cache, int master_ifindex, const NMPObject *potential_slave, const NMPObject *ignore_slave); + +gboolean nmp_cache_use_udev_detect (void); +gboolean nmp_cache_use_udev_get (const NMPCache *cache); +gboolean nmp_cache_use_udev_set (NMPCache *cache, gboolean use_udev); + +void ASSERT_nmp_cache_is_consistent (const NMPCache *cache); + +NMPCacheOpsType nmp_cache_remove (NMPCache *cache, const NMPObject *obj, gboolean equals_by_ptr, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data); +NMPCacheOpsType nmp_cache_remove_netlink (NMPCache *cache, const NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data); +NMPCacheOpsType nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data); +NMPCacheOpsType nmp_cache_update_link_udev (NMPCache *cache, int ifindex, GUdevDevice *udev_device, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data); +NMPCacheOpsType nmp_cache_update_link_master_connected (NMPCache *cache, int ifindex, NMPObject **out_obj, gboolean *out_was_visible, NMPCachePreHook pre_hook, gpointer user_data); + +NMPCache *nmp_cache_new (void); +void nmp_cache_free (NMPCache *cache); + +NMPObject *nmp_object_from_nl (NMPlatform *platform, const struct nl_object *nlo, gboolean id_only, gboolean complete_from_cache); +struct nl_object *nmp_object_to_nl (NMPlatform *platform, const NMPObject *obj, gboolean id_only); + +/* the following functions are currently implemented inside nm-linux-platform, because + * they depend on utility functions there. */ +ObjectType _nlo_get_object_type (const struct nl_object *nlo); +gboolean _nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache); +gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache); +gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache); +gboolean _nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache); +gboolean _nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache); +struct nl_object *_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only); +struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only); +struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only); +struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only); +struct nl_object *_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only); + #endif /* __NMP_OBJECT_H__ */ diff --git a/src/platform/tests/test-nmp-object.c b/src/platform/tests/test-nmp-object.c index 7c01d9850c..b922599cb3 100644 --- a/src/platform/tests/test-nmp-object.c +++ b/src/platform/tests/test-nmp-object.c @@ -24,6 +24,366 @@ #include "nm-test-utils.h" +struct { + GList *udev_devices; +} global; + +/******************************************************************/ + +static gboolean +_nmp_object_id_equal (const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_id_equal (a, b); + + g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_id_equal (b, a)); + return a_b; +} +#define nmp_object_id_equal _nmp_object_id_equal + +static gboolean +_nmp_object_equal (const NMPObject *a, const NMPObject *b) +{ + gboolean a_b = nmp_object_equal (a, b); + + g_assert (NM_IN_SET (a_b, FALSE, TRUE) && a_b == nmp_object_equal (b, a)); + return a_b; +} +#define nmp_object_equal _nmp_object_equal + +/******************************************************************/ + +static void +_assert_cache_multi_lookup_contains (const NMPCache *cache, const NMPCacheId *cache_id, const NMPObject *obj, gboolean contains) +{ + const NMPlatformObject *const *objects; + guint i, len; + gboolean found; + + g_assert (cache_id); + g_assert (NMP_OBJECT_IS_VALID (obj)); + + g_assert (nmp_cache_lookup_obj (cache, obj) == obj); + + objects = nmp_cache_lookup_multi (cache, cache_id, &len); + + g_assert ((len == 0 && !objects) || (len > 0 && objects && !objects[len])); + + found = FALSE; + for (i = 0; i < len; i++) { + NMPObject *o; + + g_assert (objects[i]); + o = NMP_OBJECT_UP_CAST (objects[i]); + g_assert (NMP_OBJECT_IS_VALID (o)); + + if (obj == o) { + g_assert (!found); + found = TRUE; + } + } + + g_assert (!!contains == found); +} + +/******************************************************************/ + +typedef struct { + NMPCache *cache; + NMPCacheOpsType expected_ops_type; + const NMPObject *obj_clone; + NMPObject *new_clone; + gboolean was_visible; + gboolean called; +} _NMPCacheUpdateData; + +static void +_nmp_cache_update_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data) +{ + _NMPCacheUpdateData *data = user_data; + + g_assert (data); + g_assert (!data->called); + g_assert (data->cache == cache); + + g_assert_cmpint (data->expected_ops_type, ==, ops_type); + + switch (ops_type) { + case NMP_CACHE_OPS_ADDED: + g_assert (!old); + g_assert (NMP_OBJECT_IS_VALID (new)); + g_assert (nmp_object_is_alive (new)); + g_assert (nmp_object_id_equal (data->obj_clone, new)); + g_assert (nmp_object_equal (data->obj_clone, new)); + break; + case NMP_CACHE_OPS_UPDATED: + g_assert (NMP_OBJECT_IS_VALID (old)); + g_assert (NMP_OBJECT_IS_VALID (new)); + g_assert (nmp_object_is_alive (old)); + g_assert (nmp_object_is_alive (new)); + g_assert (nmp_object_id_equal (data->obj_clone, new)); + g_assert (nmp_object_id_equal (data->obj_clone, old)); + g_assert (nmp_object_id_equal (old, new)); + g_assert (nmp_object_equal (data->obj_clone, new)); + g_assert (!nmp_object_equal (data->obj_clone, old)); + g_assert (!nmp_object_equal (old, new)); + break; + case NMP_CACHE_OPS_REMOVED: + g_assert (!new); + g_assert (NMP_OBJECT_IS_VALID (old)); + g_assert (nmp_object_is_alive (old)); + g_assert (nmp_object_id_equal (data->obj_clone, old)); + break; + default: + g_assert_not_reached (); + } + + data->was_visible = old ? nmp_object_is_visible (old) : FALSE; + data->new_clone = new ? nmp_object_clone (new, FALSE) : NULL; + data->called = TRUE; +} + +static void +_nmp_cache_update_netlink (NMPCache *cache, NMPObject *obj, NMPObject **out_obj, gboolean *out_was_visible, NMPCacheOpsType expected_ops_type) +{ + NMPCacheOpsType ops_type; + NMPObject *obj2; + gboolean was_visible; + auto_nmp_obj NMPObject *obj_clone = nmp_object_clone (obj, FALSE); + auto_nmp_obj NMPObject *new_clone = NULL; + const NMPObject *obj_old; + _NMPCacheUpdateData data = { + .cache = cache, + .expected_ops_type = expected_ops_type, + .obj_clone = obj_clone, + }; + + obj_old = nmp_cache_lookup_link (cache, obj->object.ifindex); + if (obj_old && obj_old->_link.udev.device) + obj_clone->_link.udev.device = g_object_ref (obj_old->_link.udev.device); + _nmp_object_fixup_link_udev_fields (obj_clone, nmp_cache_use_udev_get (cache)); + + g_assert (cache); + g_assert (NMP_OBJECT_IS_VALID (obj)); + + ops_type = nmp_cache_update_netlink (cache, obj, &obj2, &was_visible, _nmp_cache_update_hook, &data); + + new_clone = data.new_clone; + + g_assert_cmpint (ops_type, ==, expected_ops_type); + + if (ops_type != NMP_CACHE_OPS_UNCHANGED) { + g_assert (NMP_OBJECT_IS_VALID (obj2)); + g_assert (data.called); + g_assert_cmpint (data.was_visible, ==, was_visible); + + if (ops_type == NMP_CACHE_OPS_REMOVED) + g_assert (!data.new_clone); + else { + g_assert (data.new_clone); + g_assert (nmp_object_equal (obj2, data.new_clone)); + } + } else { + g_assert (!data.called); + g_assert (!obj2 || was_visible == nmp_object_is_visible (obj2)); + } + + g_assert (!obj2 || nmp_object_id_equal (obj, obj2)); + if (ops_type != NMP_CACHE_OPS_REMOVED && obj2) + g_assert (nmp_object_equal (obj, obj2)); + + if (out_obj) + *out_obj = obj2; + else + nmp_object_unref (obj2); + if (out_was_visible) + *out_was_visible = was_visible; +} + +static const NMPlatformLink pl_link_2 = { + .ifindex = 2, + .name = "eth0", + .type = NM_LINK_TYPE_ETHERNET, +}; + +static const NMPlatformLink pl_link_3 = { + .ifindex = 3, + .name = "wlan0", + .type = NM_LINK_TYPE_WIFI, +}; + +static void +test_cache_link () +{ + NMPCache *cache; + NMPObject *obj1, *obj2; + NMPObject objs1; + gboolean was_visible; + NMPCacheId cache_id_storage; + GUdevDevice *udev_device_2 = g_list_nth_data (global.udev_devices, 0); + GUdevDevice *udev_device_3 = g_list_nth_data (global.udev_devices, 0); + NMPCacheOpsType ops_type; + + cache = nmp_cache_new (); + + nmp_cache_use_udev_set (cache, g_rand_int_range (nmtst_get_rand (), 0, 2)); + + /* if we have a link, and don't set is_in_netlink, adding it has no effect. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert (NMP_OBJECT_UP_CAST (&obj1->object) == obj1); + g_assert (!nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (!obj2); + g_assert (!was_visible); + g_assert (!nmp_cache_lookup_obj (cache, obj1)); + g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex))); + nmp_object_unref (obj1); + + /* Only when setting @is_in_netlink the link is added. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + obj1->_link.netlink.is_in_netlink = TRUE; + g_assert (nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_ADDED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (nmp_object_equal (obj1, obj2)); + g_assert (!was_visible); + g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2); + g_assert (nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + /* updating the same link with identical value, has no effect. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + obj1->_link.netlink.is_in_netlink = TRUE; + g_assert (nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UNCHANGED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (obj2 != obj1); + g_assert (nmp_object_equal (obj1, obj2)); + g_assert (was_visible); + g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2); + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + /* remove the link from netlink */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + g_assert (!nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_REMOVED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (obj2 != obj1); + g_assert (was_visible); + g_assert (!nmp_cache_lookup_obj (cache, obj1)); + g_assert (!nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex))); + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + udev_device_2 = NULL; + if (udev_device_2) { + /* now add the link only with aspect UDEV. */ + ops_type = nmp_cache_update_link_udev (cache, pl_link_2.ifindex, udev_device_2, &obj2, &was_visible, NULL, NULL); + ASSERT_nmp_cache_is_consistent (cache); + g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED); + g_assert (!was_visible); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2); + g_assert (!nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + nmp_object_unref (obj2); + } + + /* add it in netlink too. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + obj1->_link.netlink.is_in_netlink = TRUE; + g_assert (nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_ADDED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (nmp_object_equal (obj1, obj2)); + g_assert (!was_visible); + g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2); + g_assert (nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + /* remove again from netlink. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_2); + obj1->_link.netlink.is_in_netlink = FALSE; + g_assert (!nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, udev_device_2 ? NMP_CACHE_OPS_UPDATED : NMP_CACHE_OPS_REMOVED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (obj2 != obj1); + g_assert (was_visible); + if (udev_device_2) { + g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == obj2); + g_assert (!nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + } else { + g_assert (nmp_cache_lookup_obj (cache, obj1) == NULL); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_2.ifindex)) == NULL); + g_assert (nmp_object_is_visible (obj2)); + } + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + /* now another link only with aspect UDEV. */ + if (udev_device_3) { + /* now add the link only with aspect UDEV. */ + ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, udev_device_3, &obj2, &was_visible, NULL, NULL); + g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_ADDED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (NMP_OBJECT_IS_VALID (obj2)); + g_assert (!was_visible); + g_assert (!nmp_object_is_visible (obj2)); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, FALSE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, FALSE); + g_assert_cmpint (obj2->link.initialized, ==, FALSE); + nmp_object_unref (obj2); + + /* add it in netlink too. */ + obj1 = nmp_object_new (OBJECT_TYPE_LINK, (NMPlatformObject *) &pl_link_3); + obj1->_link.netlink.is_in_netlink = TRUE; + g_assert (nmp_object_is_alive (obj1)); + _nmp_cache_update_netlink (cache, obj1, &obj2, &was_visible, NMP_CACHE_OPS_UPDATED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (obj2 != obj1); + g_assert (nmp_object_equal (obj1, obj2)); + g_assert (!was_visible); + g_assert (nmp_cache_lookup_obj (cache, obj1) == obj2); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2); + g_assert (nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint (obj2->link.initialized, ==, TRUE); + nmp_object_unref (obj1); + nmp_object_unref (obj2); + + /* remove UDEV. */ + ops_type = nmp_cache_update_link_udev (cache, pl_link_3.ifindex, NULL, &obj2, &was_visible, NULL, NULL); + g_assert_cmpint (ops_type, ==, NMP_CACHE_OPS_UPDATED); + ASSERT_nmp_cache_is_consistent (cache); + g_assert (was_visible); + g_assert (nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&objs1, pl_link_3.ifindex)) == obj2); + g_assert (nmp_object_is_visible (obj2)); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, TRUE), obj2, TRUE); + _assert_cache_multi_lookup_contains (cache, nmp_cache_id_init_links (&cache_id_storage, FALSE), obj2, TRUE); + g_assert_cmpint (obj2->_link.netlink.is_in_netlink, ==, TRUE); + g_assert_cmpint (obj2->link.initialized, ==, !nmp_cache_use_udev_get (cache)); + nmp_object_unref (obj2); + } + + nmp_cache_free (cache); +} /******************************************************************/ @@ -33,11 +393,34 @@ int main (int argc, char **argv) { int result; + gs_unref_object GUdevClient *udev_client = NULL; nmtst_init_assert_logging (&argc, &argv, "INFO", "DEFAULT"); + udev_client = g_udev_client_new ((const char *[]) { "net", NULL }); + { + gs_unref_object GUdevEnumerator *udev_enumerator = g_udev_enumerator_new (udev_client); + + g_udev_enumerator_add_match_subsystem (udev_enumerator, "net"); + + /* Demand that the device is initialized (udev rules ran, + * device has a stable name now) in case udev is running + * (not in a container). */ + if (access ("/sys", W_OK) == 0) + g_udev_enumerator_add_match_is_initialized (udev_enumerator); + + global.udev_devices = g_udev_enumerator_execute (udev_enumerator); + } + + g_test_add_func ("/nmp-object/cache_link", test_cache_link); + result = g_test_run (); + while (global.udev_devices) { + g_object_unref (global.udev_devices->data); + global.udev_devices = g_list_remove (global.udev_devices, global.udev_devices->data); + } + return result; } |