diff --git a/src/platform/nm-linux-platform.c b/src/platform/nm-linux-platform.c index e746475ec..f4a4ba86d 100644 --- a/src/platform/nm-linux-platform.c +++ b/src/platform/nm-linux-platform.c @@ -3169,7 +3169,7 @@ cache_prune_candidates_prune (NMPlatform *platform) static void cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMPCacheOpsType ops_type, gpointer user_data) { - NMPlatform *platform = NM_PLATFORM (user_data); + NMPlatform *platform = user_data; NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); const NMPClass *klass; char str_buf[sizeof (_nm_utils_to_string_buffer)]; @@ -3181,6 +3181,7 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP nm_assert (ops_type != NMP_CACHE_OPS_REMOVED || (new == NULL && NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old))); nm_assert (ops_type != NMP_CACHE_OPS_UPDATED || (NMP_OBJECT_IS_VALID (old) && nmp_object_is_alive (old) && NMP_OBJECT_IS_VALID (new) && nmp_object_is_alive (new))); nm_assert (new == NULL || old == NULL || nmp_object_id_equal (new, old)); + nm_assert (!old || !new || NMP_OBJECT_GET_CLASS (old) == NMP_OBJECT_GET_CLASS (new)); klass = old ? NMP_OBJECT_GET_CLASS (old) : NMP_OBJECT_GET_CLASS (new); @@ -3387,11 +3388,49 @@ cache_pre_hook (NMPCache *cache, const NMPObject *old, const NMPObject *new, NMP NULL); } } + break; default: break; } } +static void +cache_post (NMPlatform *platform, + struct nlmsghdr *msghdr, + NMPCacheOpsType cache_op, + NMPObject *obj, + NMPObject *obj_cache) +{ + NMLinuxPlatformPrivate *priv = NM_LINUX_PLATFORM_GET_PRIVATE (platform); + + nm_assert (NMP_OBJECT_IS_VALID (obj)); + nm_assert (!obj_cache || nmp_object_id_equal (obj, obj_cache)); + + if (msghdr->nlmsg_type == RTM_NEWROUTE) { + DelayedActionType action_type; + + action_type = NMP_OBJECT_GET_TYPE (obj) == NMP_OBJECT_TYPE_IP4_ROUTE + ? DELAYED_ACTION_TYPE_REFRESH_ALL_IP4_ROUTES + : DELAYED_ACTION_TYPE_REFRESH_ALL_IP6_ROUTES; + if ( !delayed_action_refresh_all_in_progress (platform, action_type) + && nmp_cache_find_other_route_for_same_destination (priv->cache, obj)) { + /* via `iproute route change` the user can update an existing route which effectively + * means that a new object (with a different ID) comes into existance, replacing the + * old on. In other words, as the ID of the object changes, we really see a new + * object with the old one deleted. + * However, kernel decides not to send a RTM_DELROUTE event for that. + * + * To hack around that, check if the update leaves us with multiple routes for the + * same network/plen,metric part. In that case, we cannot do better then requesting + * all routes anew, which sucks. + * + * One mitigation to avoid a dump is only to request a new dump, if we are not in + * the middle of an ongoing dump (delayed_action_refresh_all_in_progress). */ + delayed_action_schedule (platform, action_type, NULL); + } + } +} + /******************************************************************/ static int @@ -3528,8 +3567,6 @@ event_seq_check_refresh_all (NMPlatform *platform, guint32 seq_number) DelayedActionWaitForNlResponseData *data; guint i; - (void) delayed_action_refresh_all_in_progress; - if (NM_IN_SET (seq_number, 0, priv->nlh_seq_last_seen)) return; @@ -3635,6 +3672,9 @@ event_valid_msg (NMPlatform *platform, struct nl_msg *msg, gboolean handle_event case RTM_NEWADDR: case RTM_NEWROUTE: cache_op = nmp_cache_update_netlink (priv->cache, obj, &obj_cache, &was_visible, cache_pre_hook, platform); + + cache_post (platform, msghdr, cache_op, obj, obj_cache); + do_emit_signal (platform, obj_cache, cache_op, was_visible); break; diff --git a/src/platform/nmp-object.c b/src/platform/nmp-object.c index 07ea9fe1d..eb7e1ca40 100644 --- a/src/platform/nmp-object.c +++ b/src/platform/nmp-object.c @@ -965,6 +965,8 @@ _NM_UTILS_LOOKUP_DEFINE (static, _nmp_cache_id_size_by_type, NMPCacheIdType, gui NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT, _STRUCT_SIZE (NMPCacheId, object_type_by_ifindex)), NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT, _STRUCT_SIZE (NMPCacheId, object_type_by_ifindex)), NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_LINK_BY_IFNAME, _STRUCT_SIZE (NMPCacheId, link_by_ifname)), + NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4, _STRUCT_SIZE (NMPCacheId, routes_by_destination_ip4)), + NM_UTILS_LOOKUP_ITEM (NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6, _STRUCT_SIZE (NMPCacheId, routes_by_destination_ip6)), NM_UTILS_LOOKUP_ITEM_IGNORE (NMP_CACHE_ID_TYPE_NONE), NM_UTILS_LOOKUP_ITEM_IGNORE (__NMP_CACHE_ID_TYPE_MAX), ); @@ -1110,6 +1112,33 @@ nmp_cache_id_init_link_by_ifname (NMPCacheId *id, return id; } +NMPCacheId * +nmp_cache_id_init_routes_by_destination_ip4 (NMPCacheId *id, + guint32 network, + guint8 plen, + guint32 metric) +{ + _nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4); + id->routes_by_destination_ip4.plen = plen; + memcpy (&id->routes_by_destination_ip4._misaligned_metric, &metric, sizeof (guint32)); + memcpy (&id->routes_by_destination_ip4._misaligned_network, &network, sizeof (guint32)); + return id; +} + +NMPCacheId * +nmp_cache_id_init_routes_by_destination_ip6 (NMPCacheId *id, + const struct in6_addr *network, + guint8 plen, + guint32 metric) +{ + _nmp_cache_id_init (id, NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6); + id->routes_by_destination_ip4.plen = plen; + memcpy (&id->routes_by_destination_ip6._misaligned_metric, &metric, sizeof (guint32)); + if (network) + memcpy (&id->routes_by_destination_ip6._misaligned_network, network, sizeof (struct in6_addr)); + return id; +} + /******************************************************************/ static gboolean @@ -1182,7 +1211,7 @@ _vt_cmd_obj_init_cache_id_ipx_address (const NMPObject *obj, NMPCacheIdType id_t return TRUE; } -static const guint8 _supported_cache_ids_ipx_route[] = { +static const guint8 _supported_cache_ids_ip4_route[] = { NMP_CACHE_ID_TYPE_OBJECT_TYPE, NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY, NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX, @@ -1190,6 +1219,19 @@ static const guint8 _supported_cache_ids_ipx_route[] = { NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4, + 0, +}; + +static const guint8 _supported_cache_ids_ip6_route[] = { + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_CACHE_ID_TYPE_OBJECT_TYPE_VISIBLE_ONLY, + NMP_CACHE_ID_TYPE_ADDRROUTE_VISIBLE_BY_IFINDEX, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_NO_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_ONLY_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT, + NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6, 0, }; @@ -1236,6 +1278,18 @@ _vt_cmd_obj_init_cache_id_ipx_route (const NMPObject *obj, NMPCacheIdType id_typ return TRUE; } break; + case NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4: + if (NMP_OBJECT_GET_CLASS (obj)->obj_type == NMP_OBJECT_TYPE_IP4_ROUTE) { + *out_id = nmp_cache_id_init_routes_by_destination_ip4 (id, obj->ip4_route.network, obj->ip_route.plen, obj->ip_route.metric); + return TRUE; + } + return FALSE; + case NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6: + if (NMP_OBJECT_GET_CLASS (obj)->obj_type == NMP_OBJECT_TYPE_IP6_ROUTE) { + *out_id = nmp_cache_id_init_routes_by_destination_ip6 (id, &obj->ip6_route.network, obj->ip_route.plen, obj->ip_route.metric); + return TRUE; + } + return FALSE; default: return FALSE; } @@ -1404,6 +1458,51 @@ nmp_cache_lookup_link (const NMPCache *cache, int ifindex) return nmp_cache_lookup_obj (cache, nmp_object_stackinit_id_link (&obj_needle, ifindex)); } +/** + * nmp_cache_find_other_route_for_same_destination: + * @cache: + * @route: + * + * Look into the cache whether there is a route to the same destination, + * in terms of network/plen,metric. + * + * Returns: (transfer none): the first found route object from the cache + * that has the same (network/plen,metric) values as @route, but has different + * ID. Or %NULL, if no such route exists. + */ +const NMPObject * +nmp_cache_find_other_route_for_same_destination (const NMPCache *cache, const NMPObject *route) +{ + NMPCacheId cache_id; + const NMPlatformObject *const *list; + + nm_assert (cache); + + switch (NMP_OBJECT_GET_TYPE (route)) { + case NMP_OBJECT_TYPE_IP4_ROUTE: + nmp_cache_id_init_routes_by_destination_ip4 (&cache_id, route->ip4_route.network, route->ip_route.plen, route->ip_route.metric); + break; + case NMP_OBJECT_TYPE_IP6_ROUTE: + nmp_cache_id_init_routes_by_destination_ip6 (&cache_id, &route->ip6_route.network, route->ip_route.plen, route->ip_route.metric); + break; + default: + g_return_val_if_reached (NULL); + } + + list = nmp_cache_lookup_multi (cache, &cache_id, NULL); + if (list) { + for (; *list; list++) { + const NMPObject *candidate = NMP_OBJECT_UP_CAST (*list); + + nm_assert (NMP_OBJECT_GET_CLASS (route) == NMP_OBJECT_GET_CLASS (candidate)); + + if (!nmp_object_id_equal (route, candidate)) + return candidate; + } + } + return NULL; +} + const NMPObject * nmp_cache_lookup_link_full (const NMPCache *cache, int ifindex, @@ -2066,7 +2165,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { .rtm_gettype = RTM_GETROUTE, .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP4_ROUTE, .signal_type = NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, - .supported_cache_ids = _supported_cache_ids_ipx_route, + .supported_cache_ids = _supported_cache_ids_ip4_route, .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ipx_route, .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip4_route, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, @@ -2086,7 +2185,7 @@ const NMPClass _nmp_classes[NMP_OBJECT_TYPE_MAX] = { .rtm_gettype = RTM_GETROUTE, .signal_type_id = NM_PLATFORM_SIGNAL_ID_IP6_ROUTE, .signal_type = NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, - .supported_cache_ids = _supported_cache_ids_ipx_route, + .supported_cache_ids = _supported_cache_ids_ip6_route, .cmd_obj_init_cache_id = _vt_cmd_obj_init_cache_id_ipx_route, .cmd_obj_stackinit_id = _vt_cmd_obj_stackinit_id_ip6_route, .cmd_obj_is_alive = _vt_cmd_obj_is_alive_ipx_route, diff --git a/src/platform/nmp-object.h b/src/platform/nmp-object.h index 72d627081..c5241037d 100644 --- a/src/platform/nmp-object.h +++ b/src/platform/nmp-object.h @@ -79,6 +79,17 @@ typedef enum { /*< skip >*/ NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_NO_DEFAULT, NMP_CACHE_ID_TYPE_ROUTES_VISIBLE_BY_IFINDEX_ONLY_DEFAULT, + /* Consider all the destination fields of a route, that is, the ID without the ifindex + * and gateway (meaning: network/plen,metric). + * The reason for this is that `ip route change` can replace an existing route + * and modify it's ifindex/gateway. Effectively, that means it deletes an existing + * route and adds a different one (as the ID of the route changes). However, it only + * sends one RTM_NEWADDR notification without notifying about the deletion. We detect + * that by having this index to contain overlapping routes which require special + * cache-resync. */ + NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4, + NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6, + __NMP_CACHE_ID_TYPE_MAX, NMP_CACHE_ID_TYPE_MAX = __NMP_CACHE_ID_TYPE_MAX - 1, } NMPCacheIdType; @@ -110,6 +121,20 @@ typedef struct { guint8 _id_type; char ifname_short[IFNAMSIZ - 1]; /* don't include the trailing NUL so the struct fits in 4 bytes. */ } link_by_ifname; + struct _nm_packed { + /* NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP6 */ + guint8 _id_type; + guint8 plen; + guint32 _misaligned_metric; + guint32 _misaligned_network; + } routes_by_destination_ip4; + struct _nm_packed { + /* NMP_CACHE_ID_TYPE_ROUTES_BY_DESTINATION_IP4 */ + guint8 _id_type; + guint8 plen; + guint32 _misaligned_metric; + struct in6_addr _misaligned_network; + } routes_by_destination_ip6; }; } NMPCacheId; @@ -384,12 +409,16 @@ NMPCacheId *nmp_cache_id_init_object_type (NMPCacheId *id, NMPObjectType obj_typ NMPCacheId *nmp_cache_id_init_addrroute_visible_by_ifindex (NMPCacheId *id, NMPObjectType obj_type, int ifindex); NMPCacheId *nmp_cache_id_init_routes_visible (NMPCacheId *id, NMPObjectType obj_type, gboolean with_default, gboolean with_non_default, int ifindex); NMPCacheId *nmp_cache_id_init_link_by_ifname (NMPCacheId *id, const char *ifname); +NMPCacheId *nmp_cache_id_init_routes_by_destination_ip4 (NMPCacheId *id, guint32 network, guint8 plen, guint32 metric); +NMPCacheId *nmp_cache_id_init_routes_by_destination_ip6 (NMPCacheId *id, const struct in6_addr *network, guint8 plen, guint32 metric); 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, NMPObjectType 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_find_other_route_for_same_destination (const NMPCache *cache, const NMPObject *route); + const NMPObject *nmp_cache_lookup_link_full (const NMPCache *cache, int ifindex, const char *ifname,