diff options
| author | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
| commit | 05e4a733f2141995181a551854d5df929f084adf (patch) | |
| tree | 83bb937740a6667525ba0df046748ecaa829c269 /src/core/nm-l3cfg.c | |
| parent | 14b0f3a9dc9ea90d60a3b057350fd4d637dc021a (diff) | |
New upstream version 1.44.0 upstream/1.44.0
Diffstat (limited to 'src/core/nm-l3cfg.c')
| -rw-r--r-- | src/core/nm-l3cfg.c | 757 |
1 files changed, 498 insertions, 259 deletions
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index a49654fe..3c2d3ec8 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -11,6 +11,7 @@ #include <linux/if_ether.h> #include <linux/rtnetlink.h> +#include "libnm-glib-aux/nm-prioq.h" #include "libnm-glib-aux/nm-time-utils.h" #include "libnm-platform/nm-platform.h" #include "libnm-platform/nmp-object.h" @@ -123,25 +124,34 @@ typedef struct { CList os_lst; - /* If we have a timeout pending, we link the instance to - * self->priv.p->obj_state_temporary_not_available_lst_head. */ - CList os_temporary_not_available_lst; - /* If a NMPObject is no longer to be configured (but was configured * during a previous commit), then we need to remember it so that the * next commit can delete the address/route in kernel. It becomes a zombie. */ CList os_zombie_lst; - /* We might want to configure "obj" in platform, but it's currently not possible. - * For example, certain IPv6 routes can only be added after the IPv6 address - * becomes non-tentative (*sigh*). In such a case, we need to remember that, and - * retry later. If this timestamp is set to a non-zero value, then it means - * we tried to configure the obj (at that timestamp) and failed, but we are - * waiting to retry. + /* Used by _handle_routes_failed() mechanism. If "os_plobj" is set, then + * this is meaningless but should be set to zero. + * + * If set to a non-zero value, this means adding the object failed. Until + * "os_failedobj_expiry_msec" we are still waiting whether we would be able to + * configure the object. Afterwards, we consider the element failed. * - * See also self->priv.p->obj_state_temporary_not_available_lst_head - * and self->priv.p->obj_state_temporary_not_available_timeout_source. */ - gint64 os_temporary_not_available_timestamp_msec; + * Depending on "os_failedobj_prioq_idx", we are currently waiting whether the + * condition can resolve itself or becomes a failure. */ + gint64 os_failedobj_expiry_msec; + + /* The index into the "priv->failedobj_prioq" queue for objects that are failed. + * - this field is meaningless in case "os_plobj" is set (but it should be + * set to NM_PRIOQ_IDX_NULL). + * - otherwise, if "os_failedobj_expiry_msec" is 0, no error was detected so + * far. The index should be set to NM_PRIOQ_IDX_NULL. + * - otherwise, if the index is NM_PRIOQ_IDX_NULL it means that the object + * is not tracked by the queue, no grace timer is pending, and the object + * is considered failed. + * - otherwise, the index is used for tracking the element in the queue. + * It means, we are currently waiting to decide whether this will be a + * failure or not. */ + guint os_failedobj_prioq_idx; /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but * now no longer), it needs to be deleted from platform. This ratelimits the time @@ -206,7 +216,6 @@ typedef struct { guint32 acd_timeout_msec_confdata; NML3AcdDefendType acd_defend_type_confdata : 3; bool dirty_confdata : 1; - gboolean force_commit_once : 1; } L3ConfigData; struct _NML3CfgBlockHandle { @@ -241,7 +250,6 @@ typedef struct _NML3CfgPrivate { CList obj_state_lst_head; CList obj_state_zombie_lst_head; - CList obj_state_temporary_not_available_lst_head; GHashTable *acd_ipv4_addresses_on_link; @@ -288,12 +296,22 @@ typedef struct _NML3CfgPrivate { guint64 pseudo_timestamp_counter; - GSource *obj_state_temporary_not_available_timeout_source; + NMPrioq failedobj_prioq; + GSource *failedobj_timeout_source; + gint64 failedobj_timeout_expiry_msec; NML3CfgCommitType commit_on_idle_type; gint8 commit_reentrant_count; + union { + struct { + gint8 commit_reentrant_count_ip_address_sync_6; + gint8 commit_reentrant_count_ip_address_sync_4; + }; + gint8 commit_reentrant_count_ip_address_sync_x[2]; + }; + /* The value that was set before we touched the sysctl (this only is * meaningful if "ip6_privacy_set" is true. At the end, we want to restore * this value. */ @@ -340,6 +358,9 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) #define _MPTCP_TAG(self, IS_IPv4) ((gconstpointer) (&(((const char *) (self))[2 + (!(IS_IPv4))]))) +#define _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family) \ + ((gconstpointer) & (((char *) self)[1 + NM_IS_IPv4(addr_family)])) + /*****************************************************************************/ #define _NMLOG_DOMAIN LOGD_CORE @@ -410,8 +431,6 @@ static NM_UTILS_ENUM2STR_DEFINE( NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, "pre-commit"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"), - NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, - "routes-temporary-not-available-expired"), NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), ); static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string, @@ -754,51 +773,51 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp /*****************************************************************************/ -#define nm_assert_obj_state(self, obj_state) \ - G_STMT_START \ - { \ - if (NM_MORE_ASSERTS > 0) { \ - const NML3Cfg *_self = (self); \ - const ObjStateData *_obj_state = (obj_state); \ - \ - nm_assert(_obj_state); \ - nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \ - NMP_OBJECT_TYPE_IP4_ADDRESS, \ - NMP_OBJECT_TYPE_IP6_ADDRESS, \ - NMP_OBJECT_TYPE_IP4_ROUTE, \ - NMP_OBJECT_TYPE_IP6_ROUTE)); \ - nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ - nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ - == c_list_is_empty(&_obj_state->os_temporary_not_available_lst)); \ - if (_self) { \ - if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \ - nm_assert(_self->priv.p->combined_l3cd_commited); \ - \ - if (NM_MORE_ASSERTS > 5) { \ - nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \ - &_obj_state->os_lst)); \ - nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ - || c_list_contains( \ - &_self->priv.p->obj_state_temporary_not_available_lst_head, \ - &_obj_state->os_temporary_not_available_lst)); \ - nm_assert(_obj_state->os_plobj \ - == nm_platform_lookup_obj(_self->priv.platform, \ - NMP_CACHE_ID_TYPE_OBJECT_TYPE, \ - _obj_state->obj)); \ - nm_assert( \ - c_list_is_empty(&obj_state->os_zombie_lst) \ - ? (_obj_state->obj \ - == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \ - _self->priv.p->combined_l3cd_commited, \ - _obj_state->obj))) \ - : (!nm_l3_config_data_lookup_obj( \ - _self->priv.p->combined_l3cd_commited, \ - _obj_state->obj))); \ - } \ - } \ - } \ - } \ - } \ +#define nm_assert_obj_state(self, obj_state) \ + G_STMT_START \ + { \ + if (NM_MORE_ASSERTS > 0) { \ + const NML3Cfg *_self = (self); \ + const ObjStateData *_obj_state = (obj_state); \ + \ + nm_assert(_obj_state); \ + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \ + NMP_OBJECT_TYPE_IP4_ADDRESS, \ + NMP_OBJECT_TYPE_IP6_ADDRESS, \ + NMP_OBJECT_TYPE_IP4_ROUTE, \ + NMP_OBJECT_TYPE_IP6_ROUTE)); \ + nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ + nm_assert(_obj_state->os_failedobj_expiry_msec != 0 \ + || _obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || !_obj_state->os_plobj); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 \ + || c_list_is_empty(&_obj_state->os_zombie_lst)); \ + nm_assert(_obj_state->os_failedobj_expiry_msec == 0 || _obj_state->obj); \ + if (_self) { \ + if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \ + nm_assert(_self->priv.p->combined_l3cd_commited); \ + \ + if (NM_MORE_ASSERTS > 5) { \ + nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \ + &_obj_state->os_lst)); \ + nm_assert(_obj_state->os_plobj \ + == nm_platform_lookup_obj(_self->priv.platform, \ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, \ + _obj_state->obj)); \ + nm_assert( \ + c_list_is_empty(&obj_state->os_zombie_lst) \ + ? (_obj_state->obj \ + == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))) \ + : (!nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))); \ + } \ + } \ + } \ + } \ + } \ G_STMT_END static ObjStateData * @@ -808,13 +827,14 @@ _obj_state_data_new(const NMPObject *obj, const NMPObject *plobj) obj_state = g_slice_new(ObjStateData); *obj_state = (ObjStateData){ - .obj = nmp_object_ref(obj), - .os_plobj = nmp_object_ref(plobj), - .os_was_in_platform = !!plobj, - .os_nm_configured = FALSE, - .os_dirty = FALSE, - .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst), - .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst), + .obj = nmp_object_ref(obj), + .os_plobj = nmp_object_ref(plobj), + .os_was_in_platform = !!plobj, + .os_nm_configured = FALSE, + .os_dirty = FALSE, + .os_failedobj_expiry_msec = 0, + .os_failedobj_prioq_idx = NM_PRIOQ_IDX_NULL, + .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst), }; return obj_state; } @@ -824,9 +844,10 @@ _obj_state_data_free(gpointer data) { ObjStateData *obj_state = data; + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); + c_list_unlink_stale(&obj_state->os_lst); c_list_unlink_stale(&obj_state->os_zombie_lst); - c_list_unlink_stale(&obj_state->os_temporary_not_available_lst); nmp_object_unref(obj_state->obj); nmp_object_unref(obj_state->os_plobj); nm_g_slice_free(obj_state); @@ -864,15 +885,17 @@ _obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_si } else if (obj_state->os_was_in_platform) nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform"); - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { + if (obj_state->os_failedobj_expiry_msec > 0) { nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - nm_strbuf_append( - &buf, - &buf_size, - ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d", - (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000, - (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000)); - } + nm_strbuf_append(&buf, + &buf_size, + ", %s-since=%" G_GINT64_FORMAT ".%03d", + (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) ? "failed" + : "failed-wait", + (obj_state->os_failedobj_expiry_msec - now_msec) / 1000, + (int) ((obj_state->os_failedobj_expiry_msec - now_msec) % 1000)); + } else + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); return buf0; } @@ -934,6 +957,7 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) { /* this is a zombie. We can forget about it.*/ + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); nm_clear_nmp_object(&obj_state->os_plobj); c_list_unlink(&obj_state->os_zombie_lst); _LOGD("obj-state: zombie gone (untrack): %s", @@ -949,8 +973,23 @@ _obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboole if (in_platform) { nmp_object_ref_set(&obj_state->os_plobj, obj); obj_state->os_was_in_platform = TRUE; - _LOGD("obj-state: appeared in platform: %s", - _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + if (obj_state->os_failedobj_expiry_msec != 0) { + obj_state->os_failedobj_expiry_msec = 0; + if (obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL) { + _LOGT("obj-state: failed-obj: object now configured after failed earlier: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } else { + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); + _LOGT("obj-state: failed-obj: object now configured after waiting: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } + } else { + _LOGD("obj-state: appeared in platform: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); goto out; } @@ -1039,6 +1078,7 @@ _obj_states_update_all(NML3Cfg *self) continue; if (obj_state->os_plobj && obj_state->os_nm_configured) { + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head, &obj_state->os_zombie_lst); obj_state->os_zombie_count = ZOMBIE_COUNT_START; @@ -1049,6 +1089,9 @@ _obj_states_update_all(NML3Cfg *self) _LOGD("obj-state: untrack: %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); g_hash_table_iter_remove(&h_iter); } } @@ -1086,16 +1129,32 @@ _obj_states_sync_filter(NML3Cfg *self, const NMPObject *obj, NML3CfgCommitType c return TRUE; } - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { - /* we currently try to configure this address (but failed earlier). - * Definitely retry. */ - return TRUE; - } - - if (!obj_state->os_plobj && commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY - && !nmp_object_get_force_commit(obj)) - return FALSE; - + /* One goal would be that we don't forcefully re-add routes which were + * externally removed (e.g. by the user via `ip route del`). + * + * However, + * + * - some routes get automatically deleted by kernel (for example, + * when we have an IPv4 route with RTA_PREFSRC set and the referenced + * IPv4 address gets removed). The absence of such a route does not + * mean that the user doesn't want the route there. It means, kernel + * removed it because of some consistency check, but we want it back. + * - a route with a non-zero gateway requires that the gateway is + * directly reachable via an onlink route. The rules for this are + * complex, but kernel will reject adding a route which has such a + * gateway. If the user manually removed the needed onlink route, the + * gateway route cannot be added in kernel ("Nexthop has invalid + * gateway"). To handle that is a nightmare, so we always ensure that + * the onlink route is there. + * - a route with RTA_PREFSRC requires that such an address is + * configured otherwise kernel rejects adding the route with "Invalid + * prefsrc address"/"Invalid source address". Removing an address can + * thus prevent adding the route, which is a problem for us. + * + * So the goal is not tenable and causes problems. NetworkManager will + * try hard to re-add routes and address that it thinks should be + * present. If you externally remove them, then you are starting a + * fight where NetworkManager tries to re-add them on every commit. */ return TRUE; } @@ -1129,6 +1188,7 @@ static void _commit_collect_routes(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type, + gboolean any_addrs, GPtrArray **routes, GPtrArray **routes_nodev) { @@ -1154,6 +1214,24 @@ _commit_collect_routes(NML3Cfg *self, else { nm_assert(NMP_OBJECT_CAST_IP_ROUTE(obj)->ifindex == self->priv.ifindex); + if (!any_addrs) { + /* This is a unicast route (or a similar route, which has an + * ifindex). + * + * However, during this commit we don't plan to configure any + * IP addresses. With `ipvx.method=manual` that should not be + * possible. More likely, this is because the profile has + * `ipvx.method=auto` and static routes. + * + * Don't configure any such routes before we also have at least + * one IP address. + * + * This code applies to IPv4 and IPv6, however for IPv6 we + * early on configure a link local address, so in practice the + * branch is not taken for IPv6. */ + continue; + } + if (IS_IPv4 && NMP_OBJECT_CAST_IP4_ROUTE(obj)->weight > 0) { /* This route needs to be registered as ECMP route. */ nm_netns_ip_route_ecmp_register(self->priv.netns, self, obj); @@ -1246,6 +1324,7 @@ _obj_state_zombie_lst_get_prune_lists(NML3Cfg *self, if (--obj_state->os_zombie_count == 0) { _LOGD("obj-state: prune zombie (untrack): %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); continue; } @@ -1280,6 +1359,7 @@ _obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family) if (--obj_state->os_zombie_count == 0) { _LOGD("obj-state: zombie pruned during reapply (untrack): %s", _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_assert(obj_state->os_failedobj_prioq_idx == NM_PRIOQ_IDX_NULL); g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); continue; } @@ -3017,16 +3097,14 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr) /*****************************************************************************/ gboolean -nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family) +nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family) { ObjStateData *obj_state; nm_assert(NM_IS_L3CFG(self)); nm_assert_addr_family(addr_family); - c_list_for_each_entry (obj_state, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { + nm_prioq_for_each (&self->priv.p->failedobj_prioq, obj_state) { if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) == addr_family) return TRUE; } @@ -3413,8 +3491,7 @@ nm_l3cfg_add_config(NML3Cfg *self, .acd_timeout_msec_confdata = acd_timeout_msec, .priority_confdata = priority, .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter, - .force_commit_once = NM_FLAGS_HAS(config_flags, NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE), - .dirty_confdata = FALSE, + .dirty_confdata = FALSE, }; changed = TRUE; } else { @@ -3611,7 +3688,6 @@ typedef struct { NML3Cfg *self; gconstpointer tag; bool to_commit; - bool force_commit_once; } L3ConfigMergeHookAddObjData; static gboolean @@ -3629,9 +3705,6 @@ _l3_hook_add_obj_cb(const NML3ConfigData *l3cd, nm_assert(obj); nm_assert(hook_result); nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); - nm_assert(hook_result->force_commit == NM_OPTION_BOOL_DEFAULT); - - hook_result->force_commit = hook_data->force_commit_once; switch (NMP_OBJECT_GET_TYPE(obj)) { case NMP_OBJECT_TYPE_IP4_ADDRESS: @@ -3787,8 +3860,7 @@ _l3cfg_update_combined_config(NML3Cfg *self, if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD)) continue; - hook_data.tag = l3cd_data->tag_confdata; - hook_data.force_commit_once = l3cd_data->force_commit_once; + hook_data.tag = l3cd_data->tag_confdata; nm_l3_config_data_merge(l3cd, l3cd_data->l3cd, @@ -3846,7 +3918,6 @@ _l3cfg_update_combined_config(NML3Cfg *self, IS_IPv4 ? AF_INET : AF_INET6, l3cd_data->default_route_table_x[IS_IPv4], l3cd_data->default_route_metric_x[IS_IPv4], - l3cd_data->force_commit_once, l3cd_data->l3cd); } } @@ -3921,79 +3992,91 @@ out: /*****************************************************************************/ static gboolean -_routes_temporary_not_available_timeout(gpointer user_data) +_failedobj_timeout_cb(gpointer user_data) { - NML3Cfg *self = NM_L3CFG(user_data); - ObjStateData *obj_state; - gint64 now_msec; - gint64 expiry_msec; + NML3Cfg *self = NM_L3CFG(user_data); - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); + _LOGT("obj-state: failed-obj: handle timeout"); - obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, - ObjStateData, - os_temporary_not_available_lst); + nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source); - if (!obj_state) - return G_SOURCE_CONTINUE; + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); - now_msec = nm_utils_get_monotonic_timestamp_msec(); + return G_SOURCE_CONTINUE; +} - expiry_msec = obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; +static void +_failedobj_reschedule(NML3Cfg *self, gint64 now_msec) +{ + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + ObjStateData *obj_state; - if (now_msec < expiry_msec) { - /* the timeout is not yet reached. Restart the timer... */ - self->priv.p->obj_state_temporary_not_available_timeout_source = - nm_g_timeout_add_source(expiry_msec - now_msec, - _routes_temporary_not_available_timeout, - self); - return G_SOURCE_CONTINUE; + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + +again: + obj_state = nm_prioq_peek(&self->priv.p->failedobj_prioq); + + if (obj_state && obj_state->os_failedobj_expiry_msec <= now_msec) { + /* The object is already expired... */ + + /* we shouldn't have a "os_plobj", because if we had, we should have + * removed "obj_state" from the queue. */ + nm_assert(!obj_state->os_plobj); + + /* we need to have an "obj", otherwise the "obj_state" instance + * shouldn't exist (as it also has not "os_plobj"). */ + nm_assert(obj_state->obj); + + /* It seems that nm_platform_ip_route_sync() signaled success and did + * not report the route as missing. Regardless, it is still not + * configured and the timeout expired. */ + nm_prioq_remove(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx); + _LOGW( + "missing IPv%c route: %s", + nm_utils_addr_family_to_char(NMP_OBJECT_GET_TYPE(obj_state->obj)), + nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + goto again; } - /* One (or several) routes expired. We emit a signal, but we don't schedule it again. - * We expect the callers to commit again, which will one last time try to configure - * the route. If that again fails, we detect the timeout, log a warning and don't - * track the object as not temporary-not-available anymore. */ - _nm_l3cfg_emit_signal_notify_simple( - self, - NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED); - return G_SOURCE_CONTINUE; + if (!obj_state) { + if (nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source)) + _LOGT("obj-state: failed-obj: cancel timeout"); + return; + } + + if (nm_g_timeout_reschedule(&self->priv.p->failedobj_timeout_source, + &self->priv.p->failedobj_timeout_expiry_msec, + obj_state->os_failedobj_expiry_msec, + _failedobj_timeout_cb, + self)) { + _LOGT( + "obj-state: failed-obj: schedule timeout in %" G_GINT64_FORMAT " msec", + NM_MAX((gint64) 0, + obj_state->os_failedobj_expiry_msec - nm_utils_get_monotonic_timestamp_msec())); + } } -static gboolean -_routes_temporary_not_available_update(NML3Cfg *self, - int addr_family, - GPtrArray *routes_temporary_not_available_arr) +static void +_failedobj_handle_routes(NML3Cfg *self, int addr_family, GPtrArray *routes_failed) { - ObjStateData *obj_state; - ObjStateData *obj_state_safe; - gint64 now_msec; - gboolean prune_all = FALSE; - gboolean success = TRUE; - guint i; - const NMPClass *klass; - - klass = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); - now_msec = nm_utils_get_monotonic_timestamp_msec(); - - if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) { - prune_all = TRUE; - goto out_prune; - } + const gint64 now_msec = nm_utils_get_monotonic_timestamp_msec(); + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + ObjStateData *obj_state; + guint i; - c_list_for_each_entry (obj_state, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { - if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { - nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0); - obj_state->os_tna_dirty = TRUE; - } - } + if (!routes_failed) + return; - for (i = 0; i < routes_temporary_not_available_arr->len; i++) { - const NMPObject *o = routes_temporary_not_available_arr->pdata[i]; - char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + for (i = 0; i < routes_failed->len; i++) { + const NMPObject *o = routes_failed->pdata[i]; + const NMPlatformIPXRoute *rt = NMP_OBJECT_CAST_IPX_ROUTE(o); + gboolean just_started_to_fail = FALSE; + gboolean just_failed = FALSE; + gboolean arm_timer = FALSE; + int grace_timeout_msec; + gint64 grace_expiry_mesc; nm_assert(NMP_OBJECT_GET_TYPE(o) == NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); @@ -4005,70 +4088,83 @@ _routes_temporary_not_available_update(NML3Cfg *self, continue; } - if (obj_state->os_temporary_not_available_timestamp_msec > 0) { - nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0 - && obj_state->os_temporary_not_available_timestamp_msec <= now_msec); - - if (!obj_state->os_tna_dirty) { - /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates. - * It should not. */ - nm_assert_not_reached(); - continue; - } - - if (now_msec > obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { - /* Timeout. Could not add this address. - * - * For now, keep it obj_state->os_tna_dirty and prune it below. */ - _LOGW("failure to add IPv%c route: %s", - nm_utils_addr_family_to_char(addr_family), - nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); - success = FALSE; - continue; - } - - obj_state->os_tna_dirty = FALSE; + if (obj_state->os_plobj) { + /* This object is apparently present in platform. Not sure what this failure report + * is about. Probably some harmless glitch. Ignore. */ continue; } - _LOGT("(temporarily) unable to add IPv%c route: %s", - nm_utils_addr_family_to_char(addr_family), - nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + /* This route failed, but why? That determines the grace time that we + * give before considering it bad. */ + if (!nm_ip_addr_is_null(addr_family, + nm_platform_ip_route_get_pref_src(addr_family, &rt->rx))) { + /* This route has a pref_src. A common cause for being unable to + * configure such routes, is that the referenced IP address is not + * configured/ready (yet). Give a longer timeout to this case. */ + grace_timeout_msec = 10000; + } else { + /* Other route don't have any grace time. There is no retry/wait, + * they are a failure right away. */ + grace_timeout_msec = 0; + } - obj_state->os_tna_dirty = FALSE; - obj_state->os_temporary_not_available_timestamp_msec = now_msec; - c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head, - &obj_state->os_temporary_not_available_lst); - } + grace_expiry_mesc = now_msec + grace_timeout_msec; -out_prune: - c_list_for_each_entry_safe (obj_state, - obj_state_safe, - &self->priv.p->obj_state_temporary_not_available_lst_head, - os_temporary_not_available_lst) { - if (prune_all || obj_state->os_tna_dirty) { - if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { - obj_state->os_temporary_not_available_timestamp_msec = 0; - c_list_unlink(&obj_state->os_temporary_not_available_lst); + if (obj_state->os_failedobj_expiry_msec == 0) { + /* This is a new failure that we didn't see before... */ + obj_state->os_failedobj_expiry_msec = grace_expiry_mesc; + if (grace_timeout_msec == 0) + just_failed = TRUE; + else { + arm_timer = TRUE; + just_started_to_fail = TRUE; } + } else { + if (obj_state->os_failedobj_expiry_msec > grace_expiry_mesc) { + /* Shorten the grace timeout. We anyway rearm below... */ + obj_state->os_failedobj_expiry_msec = grace_expiry_mesc; + } + if (obj_state->os_failedobj_expiry_msec <= now_msec) { + /* The grace period is (already) expired. */ + if (obj_state->os_failedobj_prioq_idx != NM_PRIOQ_IDX_NULL) { + /* We are still tracking the element. It just is about to become failed. */ + just_failed = TRUE; + } + } else + arm_timer = TRUE; } - } - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - - obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, - ObjStateData, - os_temporary_not_available_lst); - if (obj_state) { - self->priv.p->obj_state_temporary_not_available_timeout_source = - nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec), - _routes_temporary_not_available_timeout, - self); + nm_prioq_update(&self->priv.p->failedobj_prioq, + obj_state, + &obj_state->os_failedobj_prioq_idx, + arm_timer); + + if (just_failed) { + _LOGW("unable to configure IPv%c route: %s", + nm_utils_addr_family_to_char(addr_family), + nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); + } else if (just_started_to_fail) { + _LOGT("obj-state: failed-obj: unable to configure %s. Wait for %d msec", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf)), + grace_timeout_msec); + } } +} + +static int +_failedobj_prioq_cmp(gconstpointer a, gconstpointer b) +{ + const ObjStateData *object_state_a = a; + const ObjStateData *object_state_b = b; + + nm_assert(object_state_a); + nm_assert(object_state_a->os_failedobj_expiry_msec > 0); + nm_assert(object_state_b); + nm_assert(object_state_b->os_failedobj_expiry_msec > 0); - return success; + NM_CMP_SELF(object_state_a, object_state_b); + NM_CMP_FIELD(object_state_a, object_state_b, os_failedobj_expiry_msec); + return 0; } /*****************************************************************************/ @@ -4391,6 +4487,147 @@ _rp_filter_update(NML3Cfg *self, gboolean reapply) /*****************************************************************************/ +static void +_routes_watch_ip_addrs_cb(NMNetns *netns, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + const NMNetnsWatcherEventData *event_data, + gpointer user_data) +{ + const int IS_IPv4 = NM_IS_IPv4(watcher_data->ip_addr.addr.addr_family); + NML3Cfg *self = user_data; + char sbuf[NM_INET_ADDRSTRLEN]; + + if (NMP_OBJECT_CAST_IP_ADDRESS(event_data->ip_addr.obj)->ifindex == self->priv.ifindex) { + if (self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4] > 0) { + /* We are currently commiting IP addresses on this very interface. + * We can ignore the event. Also, because we will sync the routes + * immediately after already. So even if somebody externally added + * the address just this very moment, we would still do the commit + * at the right time to ensure our routes are there. */ + return; + } + } + + if (event_data->ip_addr.change_type == NM_PLATFORM_SIGNAL_REMOVED) + return; + + _LOGT("watched ip-address %s changed. Schedule an idle commit", + nm_inet_ntop(watcher_data->ip_addr.addr.addr_family, + &watcher_data->ip_addr.addr.addr, + sbuf)); + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); +} + +static void +_routes_watch_ip_addrs(NML3Cfg *self, int addr_family, GPtrArray *addresses, GPtrArray *routes) +{ + gconstpointer const TAG = _NETNS_WATCHER_IP_ADDR_TAG(self, addr_family); + NMNetnsWatcherData watcher_data = { + .ip_addr = + { + .addr = + { + .addr_family = addr_family, + }, + }, + }; + guint i; + guint j; + + /* IP routes that have a pref_src, can only be configured in kernel if that + * address exists (and is non-tentative, in case of IPv6). That address + * might be on another interface. So we actually watch all other + * interfaces. + * + * Note that while we track failure to configure routes via "failedobj" + * mechanism, we eagerly register watchers, even if the route is already + * successfully configured or if the route is to be configure the first + * time. Maybe that could be improved, but + * - watchers should be cheap unless they notify the event. + * - upon change we do an async commit, which is maybe not entirely cheap + * but cheap enough. More importantly, committing is something that + * *always* should be permissible -- because NML3Cfg has multiple, + * independent users, that don't know about each other and which + * independently are allowed to issue a commit when they think something + * relevant changed. If there are really too many, unnecessary commits, + * then the cause needs to be understood and addressed explicitly. */ + + if (!routes) + goto out; + + for (i = 0; i < routes->len; i++) { + const NMPlatformIPRoute *rt = NMP_OBJECT_CAST_IP_ROUTE(routes->pdata[i]); + gconstpointer pref_src; + + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(routes->pdata[i]) == addr_family); + + pref_src = nm_platform_ip_route_get_pref_src(addr_family, rt); + + if (nm_ip_addr_is_null(addr_family, pref_src)) + continue; + + if (NM_IS_IPv4(addr_family)) { + if (addresses) { + /* This nested loop makes the whole operation O(n*m). We still + * do it that way, because it's probably faster to just iterate + * over the few addresses instead of building a lookup index to + * get it in O(n+m). */ + for (j = 0; j < addresses->len; j++) { + const NMPlatformIPAddress *a = NMP_OBJECT_CAST_IP_ADDRESS(addresses->pdata[j]); + + nm_assert(NMP_OBJECT_GET_ADDR_FAMILY(addresses->pdata[j]) == addr_family); + + if (nm_ip_addr_equal(addr_family, pref_src, a->address_ptr)) { + /* We optimize for the case where the required address + * is about be configured in the same commit. That is a + * common case, because our DHCP routes have prefsrc + * set, and we commonly have the respective IP address + * ready. Otherwise, the very common DHCP case would + * also require the overhead of registering a watcher + * (every time). + */ + goto next; + } + } + } + } else { + /* For IPv6, the prefsrc address must also be non-tentative (or + * IFA_F_OPTIMISTIC). So by only looking at the addresses we are + * about to configure, it's not clear whether we will be able to + * configure the route too. + * + * Maybe we could check current platform, whether the address + * exists there as non-tentative, but that seems fragile. + * + * Maybe we should only register watchers, after we encountered a + * failure to configure a route, but that seems complicated (and + * has the potential to be wrong). + * + * The overhead for always watching the IPv6 address should be + * acceptably small. So just do that. + */ + } + + nm_assert(watcher_data.ip_addr.addr.addr_family == addr_family); + nm_ip_addr_set(addr_family, &watcher_data.ip_addr.addr.addr, pref_src); + + nm_netns_watcher_add(self->priv.netns, + NM_NETNS_WATCHER_TYPE_IP_ADDR, + &watcher_data, + TAG, + _routes_watch_ip_addrs_cb, + self); +next: + (void) 0; + } + +out: + nm_netns_watcher_remove_all(self->priv.netns, TAG, FALSE); +} +/*****************************************************************************/ + static gboolean _global_tracker_mptcp_untrack(NML3Cfg *self, int addr_family) { @@ -4576,24 +4813,22 @@ _l3_commit_mptcp(NML3Cfg *self, NML3CfgCommitType commit_type) _rp_filter_update(self, reapply); } -static gboolean +static void _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type, gboolean changed_combined_l3cd, const NML3ConfigData *l3cd_old) { - const int IS_IPv4 = NM_IS_IPv4(addr_family); - gs_unref_ptrarray GPtrArray *addresses = NULL; - gs_unref_ptrarray GPtrArray *routes = NULL; - gs_unref_ptrarray GPtrArray *routes_nodev = NULL; - gs_unref_ptrarray GPtrArray *addresses_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_prune = NULL; - gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + gs_unref_ptrarray GPtrArray *addresses = NULL; + gs_unref_ptrarray GPtrArray *routes = NULL; + gs_unref_ptrarray GPtrArray *routes_nodev = NULL; + gs_unref_ptrarray GPtrArray *addresses_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_prune = NULL; + gs_unref_ptrarray GPtrArray *routes_failed = NULL; NMIPRouteTableSyncMode route_table_sync; - gboolean final_failure_for_temporary_not_available = FALSE; char sbuf_commit_type[50]; - gboolean success = TRUE; guint i; nm_assert(NM_IS_L3CFG(self)); @@ -4609,7 +4844,12 @@ _l3_commit_one(NML3Cfg *self, addresses = _commit_collect_addresses(self, addr_family, commit_type); - _commit_collect_routes(self, addr_family, commit_type, &routes, &routes_nodev); + _commit_collect_routes(self, + addr_family, + commit_type, + nm_g_ptr_array_len(addresses) > 0, + &routes, + &routes_nodev); route_table_sync = self->priv.p->combined_l3cd_commited @@ -4689,9 +4929,14 @@ _l3_commit_one(NML3Cfg *self, } } } + + _routes_watch_ip_addrs(self, addr_family, addresses, routes); + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */ /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */ + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]++; + nm_platform_ip_address_sync(self->priv.platform, addr_family, self->priv.ifindex, @@ -4701,26 +4946,18 @@ _l3_commit_one(NML3Cfg *self, ? NMP_IP_ADDRESS_SYNC_FLAGS_NONE : NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE); - _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); - - if (!nm_platform_ip_route_sync(self->priv.platform, - addr_family, - self->priv.ifindex, - routes, - routes_prune, - &routes_temporary_not_available_arr)) - success = FALSE; + self->priv.p->commit_reentrant_count_ip_address_sync_x[IS_IPv4]--; - final_failure_for_temporary_not_available = FALSE; - if (!_routes_temporary_not_available_update(self, - addr_family, - routes_temporary_not_available_arr)) - final_failure_for_temporary_not_available = TRUE; + _nodev_routes_sync(self, addr_family, commit_type, routes_nodev); - /* FIXME(l3cfg) */ - (void) final_failure_for_temporary_not_available; + nm_platform_ip_route_sync(self->priv.platform, + addr_family, + self->priv.ifindex, + routes, + routes_prune, + &routes_failed); - return success; + _failedobj_handle_routes(self, addr_family, routes_failed); } static void @@ -4733,7 +4970,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) gboolean is_sticky_update = FALSE; char sbuf_ct[30]; gboolean changed_combined_l3cd; - guint i; g_return_if_fail(NM_IS_L3CFG(self)); nm_assert(NM_IN_SET(commit_type, @@ -4794,19 +5030,12 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old); _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old); + _failedobj_reschedule(self, 0); + _l3_commit_mptcp(self, commit_type); _l3_acd_data_process_changes(self); - if (self->priv.p->l3_config_datas) { - for (i = 0; i < self->priv.p->l3_config_datas->len; i++) { - L3ConfigData *l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, i); - - if (l3_config_data->force_commit_once) - l3_config_data->force_commit_once = FALSE; - } - } - nm_assert(self->priv.p->commit_reentrant_count == 1); self->priv.p->commit_reentrant_count--; @@ -5164,7 +5393,6 @@ nm_l3cfg_init(NML3Cfg *self) c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); c_list_init(&self->priv.p->obj_state_lst_head); - c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head); c_list_init(&self->priv.p->obj_state_zombie_lst_head); c_list_init(&self->priv.p->blocked_lst_head_4); c_list_init(&self->priv.p->blocked_lst_head_6); @@ -5176,6 +5404,8 @@ nm_l3cfg_init(NML3Cfg *self) nmp_object_indirect_id_equal, _obj_state_data_free, NULL); + + nm_prioq_init(&self->priv.p->failedobj_prioq, _failedobj_prioq_cmp); } static void @@ -5214,6 +5444,18 @@ finalize(GObject *object) NML3Cfg *self = NM_L3CFG(object); gboolean changed; + if (self->priv.netns) { + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET), + TRUE); + nm_netns_watcher_remove_all(self->priv.netns, + _NETNS_WATCHER_IP_ADDR_TAG(self, AF_INET6), + TRUE); + } + + nm_prioq_destroy(&self->priv.p->failedobj_prioq); + nm_clear_g_source_inst(&self->priv.p->failedobj_timeout_source); + nm_assert(c_list_is_empty(&self->internal_netns.signal_pending_lst)); nm_assert(c_list_is_empty(&self->internal_netns.ecmp_track_ifindex_lst_head)); @@ -5241,11 +5483,8 @@ finalize(GObject *object) nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source); - nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy); nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head)); - nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head)); nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head)); if (_nodev_routes_untrack(self, AF_INET)) |