platform: ensure cache consistency for routes by workaround missing kernel event
Consider: unshare -n ip link add d0 type dummy ip link add d1 type dummy ip link set d0 up ip link set d1 up ip addr add 192.168.100.5/24 dev d0 ip addr add 192.168.101.5/24 dev d1 ip route add 192.168.200.0/24 via 192.168.100.1 ip monitor & ip route change 192.168.200.0/24 via 192.168.101.1 #prints 192.168.200.0/24 via 192.168.101.1 dev d1 ip route show #192.168.100.0/24 dev d0 proto kernel scope link src 192.168.100.5 #192.168.101.0/24 dev d1 proto kernel scope link src 192.168.101.5 #192.168.200.0/24 via 192.168.101.1 dev d1 Note that `ip route change` replaced the exising route. "Replaced" in this case means: the previous route on device "d0" got removed and a new route on "d1" was added. However, kernel only sent one RTM_NEWROUTE event, no RTM_DELROUTE that notifies about this change. We need to workaround that by re-synching the routes when we receive a RTM_NEWROUTE notification.
This commit is contained in:
@@ -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;
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user