diff options
author | Thomas Haller <thaller@redhat.com> | 2023-03-21 16:01:24 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2023-03-21 16:01:24 +0100 |
commit | 2dfeec9aea2ba5a1635f550c44a0c67224b21f91 (patch) | |
tree | 1fc45a9b78cad58af324c0bcd5140b260c65604b | |
parent | 575e35d1ca1bb8d17001e8f50acb825e4f3d816d (diff) | |
parent | 1feaf427d2bcbf5b618bbe38a82d76cfe621d203 (diff) |
platform,core: merge branch 'th/platform-rt-prefsrc'
https://bugzilla.redhat.com/show_bug.cgi?id=2046293
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1550
-rw-r--r-- | .clang-format | 1 | ||||
-rw-r--r-- | src/core/devices/nm-device.c | 43 | ||||
-rw-r--r-- | src/core/nm-l3-config-data.c | 29 | ||||
-rw-r--r-- | src/core/nm-l3-config-data.h | 2 | ||||
-rw-r--r-- | src/core/nm-l3cfg.c | 731 | ||||
-rw-r--r-- | src/core/nm-l3cfg.h | 8 | ||||
-rw-r--r-- | src/core/nm-netns.c | 593 | ||||
-rw-r--r-- | src/core/nm-netns.h | 42 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-inet-utils.h | 40 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-prioq.c | 189 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-prioq.h | 4 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.c | 45 | ||||
-rw-r--r-- | src/libnm-glib-aux/nm-shared-utils.h | 6 | ||||
-rw-r--r-- | src/libnm-glib-aux/tests/test-shared-general.c | 55 | ||||
-rw-r--r-- | src/libnm-lldp/nm-lldp-neighbor.c | 1 | ||||
-rw-r--r-- | src/libnm-platform/nm-linux-platform.c | 1 | ||||
-rw-r--r-- | src/libnm-platform/nm-platform.c | 267 | ||||
-rw-r--r-- | src/libnm-platform/nm-platform.h | 22 | ||||
-rw-r--r-- | src/libnm-platform/nmp-base.h | 9 | ||||
-rw-r--r-- | src/libnm-platform/nmp-object.h | 15 | ||||
-rw-r--r-- | src/libnm-platform/nmp-plobj.h | 1 |
21 files changed, 1474 insertions, 630 deletions
diff --git a/.clang-format b/.clang-format index 4099440415..fb100bb1cd 100644 --- a/.clang-format +++ b/.clang-format @@ -120,6 +120,7 @@ ForEachMacros: [ 'nm_manager_for_each_device', 'nm_manager_for_each_device_safe', 'nm_platform_iter_obj_for_each', + 'nm_prioq_for_each', 'nmp_cache_iter_for_each', 'nmp_cache_iter_for_each_link', 'nmp_cache_iter_for_each_reverse', diff --git a/src/core/devices/nm-device.c b/src/core/devices/nm-device.c index 939a9b2995..497108b1c1 100644 --- a/src/core/devices/nm-device.c +++ b/src/core/devices/nm-device.c @@ -3522,7 +3522,7 @@ _dev_ip_state_check(NMDevice *self, int addr_family) &s_is_pending, &s_is_failed); - has_tna = priv->l3cfg && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family); + has_tna = priv->l3cfg && nm_l3cfg_has_failedobj_pending(priv->l3cfg, addr_family); if (has_tna) s_is_pending = TRUE; @@ -3967,9 +3967,7 @@ after_merge_flags: } static gboolean -_dev_l3_register_l3cds_add_config(NMDevice *self, - L3ConfigDataType l3cd_type, - NML3CfgConfigFlags flags) +_dev_l3_register_l3cds_add_config(NMDevice *self, L3ConfigDataType l3cd_type) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); NML3ConfigMergeFlags merge_flags; @@ -3992,7 +3990,7 @@ _dev_l3_register_l3cds_add_config(NMDevice *self, _prop_get_ipvx_dns_priority(self, AF_INET6), acd_defend_type, acd_timeout_msec, - flags, + NM_L3CFG_CONFIG_FLAGS_NONE, merge_flags); } @@ -4000,7 +3998,6 @@ static gboolean _dev_l3_register_l3cds_set_one_full(NMDevice *self, L3ConfigDataType l3cd_type, const NML3ConfigData *l3cd, - NML3CfgConfigFlags flags, NMTernary commit_sync) { NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self); @@ -4024,7 +4021,7 @@ _dev_l3_register_l3cds_set_one_full(NMDevice *self, if (priv->l3cfg) { if (priv->l3cds[l3cd_type].d) { - if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags)) + if (_dev_l3_register_l3cds_add_config(self, l3cd_type)) changed = TRUE; } @@ -4048,11 +4045,7 @@ _dev_l3_register_l3cds_set_one(NMDevice *self, const NML3ConfigData *l3cd, NMTernary commit_sync) { - return _dev_l3_register_l3cds_set_one_full(self, - l3cd_type, - l3cd, - NM_L3CFG_CONFIG_FLAGS_NONE, - commit_sync); + return _dev_l3_register_l3cds_set_one_full(self, l3cd_type, l3cd, commit_sync); } static void @@ -4107,7 +4100,7 @@ _dev_l3_register_l3cds(NMDevice *self, } if (is_external) continue; - if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE)) + if (_dev_l3_register_l3cds_add_config(self, i)) changed = TRUE; } @@ -4261,6 +4254,7 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N _dev_ipshared4_spawn_dnsmasq(self); nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd); } + _dev_ip_state_check_async(self, AF_UNSPEC); _dev_ipmanual_check_ready(self); return; case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT: @@ -4270,10 +4264,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N return; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: return; - case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED: - /* we commit again. This way we try to configure the routes.*/ - _dev_l3_cfg_commit(self, FALSE); - return; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK) @@ -4305,9 +4295,6 @@ _dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, N * synchronously to update the current state and schedule a commit. */ nm_ndisc_dad_failed(priv->ipac6_data.ndisc, conflicts, TRUE); } else if (ready) { - if (nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, AF_INET6)) - _dev_l3_cfg_commit(self, FALSE); - nm_clear_l3cd(&priv->ipac6_data.l3cd); _dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY); _dev_ip_state_check_async(self, AF_INET6); @@ -10314,14 +10301,6 @@ _dev_ipmanual_check_ready(NMDevice *self) _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED); _dev_ip_state_check_async(self, AF_UNSPEC); } else if (ready) { - if (priv->ipmanual_data.state_x[IS_IPv4] != NM_DEVICE_IP_STATE_READY - && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family)) { - /* Addresses with pending ACD/DAD are a possible cause for the - * presence of temporarily-not-available objects. Once all addresses - * are ready, retry to commit those unavailable objects. */ - _dev_l3_cfg_commit(self, FALSE); - } - _dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY); _dev_ip_state_check_async(self, AF_UNSPEC); } @@ -10487,7 +10466,6 @@ _dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_d _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), notify_data->lease_update.l3cd, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, FALSE); if (notify_data->lease_update.accepted) { @@ -10708,7 +10686,6 @@ _dev_ipdhcpx_start(NMDevice *self, int addr_family) _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), previous_lease, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, FALSE); } @@ -11641,11 +11618,7 @@ _dev_ipac6_ndisc_config_changed(NMNDisc *ndisc, _dev_ipac6_grace_period_start(self, 0, TRUE); - _dev_l3_register_l3cds_set_one_full(self, - L3_CONFIG_DATA_TYPE_AC_6, - l3cd, - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE, - FALSE); + _dev_l3_register_l3cds_set_one_full(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE); nm_clear_l3cd(&priv->ipac6_data.l3cd); ready = nm_l3cfg_check_ready(priv->l3cfg, diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 17bb9db87d..4a9dd78bcf 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2604,7 +2604,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, int addr_family, guint32 route_table, guint32 route_metric, - gboolean force_commit, const NML3ConfigData *source) { const int IS_IPv4 = NM_IS_IPv4(addr_family); @@ -2649,7 +2648,6 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, self->ifindex, route_table, route_metric, - force_commit, &r_stack.r4); if (r) nm_l3_config_data_add_route(self, addr_family, NULL, r); @@ -2685,13 +2683,12 @@ nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, } rx.r6 = (NMPlatformIP6Route){ - .ifindex = self->ifindex, - .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, - .table_coerced = nm_platform_route_table_coerce(route_table), - .metric = route_metric, - .network = *a6, - .plen = plen, - .r_force_commit = force_commit, + .ifindex = self->ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .network = *a6, + .plen = plen, }; nm_platform_ip_route_normalize(addr_family, &rx.rx); @@ -3197,7 +3194,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, NMPlatformIPXAddress a; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, - .force_commit = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_a() \ @@ -3230,12 +3226,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready); } - if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT - && (!!hook_result.force_commit) != a_src->a_force_commit) { - _ensure_a(); - a.ax.a_force_commit = (!!hook_result.force_commit); - } - nm_l3_config_data_add_address_full(self, addr_family, a_src == &a.ax ? NULL : obj, @@ -3255,7 +3245,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, NMPlatformIPXRoute r; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, - .force_commit = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_r() \ @@ -3281,12 +3270,6 @@ nm_l3_config_data_merge(NML3ConfigData *self, r.rx.ifindex = self->ifindex; } - if (hook_result.force_commit != NM_OPTION_BOOL_DEFAULT - && (!!hook_result.force_commit) != r_src->r_force_commit) { - _ensure_r(); - r.rx.r_force_commit = (!!hook_result.force_commit); - } - if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) { if (r_src->table_any) { _ensure_r(); diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index bfab04d9ce..80abb00d88 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -137,7 +137,6 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex *mu typedef struct { NMOptionBool ip4acd_not_ready; - NMOptionBool force_commit; } NML3ConfigMergeHookResult; typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd, @@ -164,7 +163,6 @@ void nm_l3_config_data_add_dependent_device_routes(NML3ConfigData *self, int addr_family, guint32 route_table, guint32 route_metric, - gboolean force_commit, const NML3ConfigData *source); /*****************************************************************************/ diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index a49654feb5..4a49c4f8d9 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; } @@ -1246,6 +1305,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 +1340,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 +3078,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 +3472,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 +3669,6 @@ typedef struct { NML3Cfg *self; gconstpointer tag; bool to_commit; - bool force_commit_once; } L3ConfigMergeHookAddObjData; static gboolean @@ -3629,9 +3686,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 +3841,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 +3899,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 +3973,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); + + return G_SOURCE_CONTINUE; +} - now_msec = nm_utils_get_monotonic_timestamp_msec(); +static void +_failedobj_reschedule(NML3Cfg *self, gint64 now_msec) +{ + char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; + ObjStateData *obj_state; - expiry_msec = obj_state->os_temporary_not_available_timestamp_msec - + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - 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; +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 +4069,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); + } } +} - return success; +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); + + 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 +4468,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 +4794,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)); @@ -4689,9 +4905,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 +4922,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 +4946,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 +5006,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 +5369,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 +5380,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 +5420,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 +5459,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)) diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 9d622b4a01..5ee201e7a2 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -55,15 +55,11 @@ typedef enum _nm_packed { * "don't change" behavior. At least once. If the address/route * is still not (no longer) configured on the subsequent * commit, it's not getting added again. - * @NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE: if set, objects in the - * NML3ConfigData are committed to platform even if they were - * removed externally. */ typedef enum _nm_packed { NM_L3CFG_CONFIG_FLAGS_NONE = 0, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1), - NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE = (1LL << 2), } NML3CfgConfigFlags; typedef enum _nm_packed { @@ -132,8 +128,6 @@ typedef enum { * and neither should you call into NML3Cfg again (reentrancy). */ NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED, - NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, - NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, /* emitted before the merged l3cd is committed to platform. @@ -412,7 +406,7 @@ gboolean nm_l3cfg_check_ready(NML3Cfg *self, NML3CfgCheckReadyFlags flags, GArray **conflicts); -gboolean nm_l3cfg_has_temp_not_available_obj(NML3Cfg *self, int addr_family); +gboolean nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family); /*****************************************************************************/ diff --git a/src/core/nm-netns.c b/src/core/nm-netns.c index 88a4d66001..ad156f99ed 100644 --- a/src/core/nm-netns.c +++ b/src/core/nm-netns.c @@ -22,6 +22,44 @@ /*****************************************************************************/ +typedef struct { + gconstpointer tag; + CList watcher_by_tag_lst_head; +} WatcherByTag; + +typedef struct { + NMIPAddrTyped addr; + CList watcher_ip_addr_lst_head; +} WatcherDataIPAddr; + +struct _NMNetnsWatcherHandle { + NMNetnsWatcherType watcher_type; + NMNetnsWatcherData watcher_data; + gconstpointer tag; + NMNetnsWatcherCallback callback; + gpointer callback_user_data; + + /* This is linked to "WatcherByTag.watcher_by_tag_lst_head" in + * "priv->watcher_by_tag_idx". */ + CList watcher_tag_lst; + + /* The registration data, which depends on the "watcher_type". */ + union { + struct { + CList watcher_ip_addr_lst; + } ip_addr; + } reg_data; + + /* nm_netns_watcher_add() will mark the handle as non-dirty, while + * nm_netns_watcher_remove_all() can delete only dirty handles (while + * leaving non-dirty handles alive, but marking them as dirty). + * + * That allows a pattern where you just add the new handles that you want + * now, and then call nm_netns_watcher_remove_all() to remove those that + * should no longer be present. */ + bool watcher_dirty : 1; +}; + NM_GOBJECT_PROPERTIES_DEFINE_BASE(PROP_PLATFORM, ); typedef struct { @@ -33,8 +71,20 @@ typedef struct { GHashTable *shared_ips; GHashTable *ecmp_track_by_obj; GHashTable *ecmp_track_by_ecmpid; - CList l3cfg_signal_pending_lst_head; - GSource *signal_pending_idle_source; + + /* Indexes the watcher handles. */ + GHashTable *watcher_idx; + + /* An index of WatcherByTag. It allows to lookup watcher handles by tag. + * Handles without tag are not indexed. */ + GHashTable *watcher_by_tag_idx; + + /* Index for WatcherDataIPAddr instances. Allows to lookup all subscribers + * by IP address. */ + GHashTable *watcher_ip_data_idx; + + CList l3cfg_signal_pending_lst_head; + GSource *signal_pending_idle_source; } NMNetnsPrivate; struct _NMNetns { @@ -87,6 +137,24 @@ NM_DEFINE_SINGLETON_GETTER(NMNetns, nm_netns_get, NM_TYPE_NETNS); /*****************************************************************************/ +static WatcherDataIPAddr * +_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr); +static void _watcher_handle_notify(NMNetns *self, + NMNetnsWatcherHandle *handle, + const NMNetnsWatcherEventData *event_data); +static const char * +_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size); + +/*****************************************************************************/ + +static gboolean +NM_NETNS_WATCHER_TYPE_VALID(NMNetnsWatcherType watcher_type) +{ + return NM_IN_SET(watcher_type, NM_NETNS_WATCHER_TYPE_IP_ADDR); +} + +/*****************************************************************************/ + typedef struct { const NMPObject *representative_obj; const NMPObject *merged_obj; @@ -437,7 +505,7 @@ _platform_signal_cb(NMPlatform *platform, l3cfg = nm_netns_l3cfg_get(self, ifindex); if (!l3cfg) - return; + goto notify_watcher; l3cfg->internal_netns.signal_pending_obj_type_flags |= nmp_object_type_to_flags(obj_type); @@ -450,6 +518,55 @@ _platform_signal_cb(NMPlatform *platform, } _nm_l3cfg_notify_platform_change(l3cfg, change_type, NMP_OBJECT_UP_CAST(platform_object)); + +notify_watcher: + switch (obj_type) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + { + NMNetnsWatcherHandle *handle; + NMNetnsWatcherHandle *handle_safe; + WatcherDataIPAddr *data; + + data = + _watcher_ip_data_lookup(self, + obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS ? AF_INET : AF_INET6, + ((const NMPlatformIPAddress *) platform_object)->address_ptr); + + if (data) { + const NMNetnsWatcherEventData event_data = { + .ip_addr = + { + .change_type = change_type, + .obj = NMP_OBJECT_UP_CAST(platform_object), + }, + }; + char sbuf[500]; + + c_list_for_each_entry_safe (handle, + handle_safe, + &data->watcher_ip_addr_lst_head, + reg_data.ip_addr.watcher_ip_addr_lst) { + _LOGT("netns-watcher: %s %s", + "notify", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + + /* Note that we dispatch these events directly from the platform event + * and while iterating over "data". + * + * From the callback, it's probably a bad idea to do anything in platform + * that might change anything (emit new signals) or to nm_netns_watcher_remove*() + * any other watcher. + * + * The callee needs to be careful. */ + _watcher_handle_notify(self, handle, &event_data); + } + } + break; + } + default: + break; + } } /*****************************************************************************/ @@ -823,6 +940,460 @@ nm_netns_ip_route_ecmp_commit(NMNetns *self, /*****************************************************************************/ static void +_watcher_data_set(NMNetnsWatcherData *dst, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *src) +{ + nm_assert(dst); + nm_assert(src); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + dst->ip_addr = src->ip_addr; + return; + } + nm_assert_not_reached(); +} + +static void +_watcher_data_hash(NMHashState *h, NMNetnsWatcherType watcher_type, const NMNetnsWatcherData *data) +{ + nm_assert(h); + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + nm_assert(data); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + nm_ip_addr_typed_hash_update(h, &data->ip_addr.addr); + return; + } + nm_assert_not_reached(); +} + +static gboolean +_watcher_data_equal(NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *a, + const NMNetnsWatcherData *b) +{ + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + nm_assert(a); + nm_assert(b); + + switch (watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + return nm_ip_addr_typed_equal(&a->ip_addr.addr, &b->ip_addr.addr); + } + return nm_assert_unreachable_val(FALSE); +} + +static void +_watcher_by_tag_destroy(WatcherByTag *watcher_by_tag) +{ + c_list_unlink_stale(&watcher_by_tag->watcher_by_tag_lst_head); + nm_g_slice_free(watcher_by_tag); +} + +static void +_watcher_handle_init(NMNetnsWatcherHandle *handle, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag) +{ + nm_assert(handle); + nm_assert(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + + *handle = (NMNetnsWatcherHandle){ + .watcher_type = watcher_type, + .tag = tag, + .watcher_tag_lst = C_LIST_INIT(handle->watcher_tag_lst), + }; + _watcher_data_set(&handle->watcher_data, watcher_type, watcher_data); +} + +static guint +_watcher_handle_hash(gconstpointer data) +{ + const NMNetnsWatcherHandle *watcher = data; + NMHashState h; + + nm_assert(watcher); + nm_assert(watcher->tag); + + nm_hash_init(&h, 2696278447u); + nm_hash_update_vals(&h, watcher->tag, watcher->watcher_type); + _watcher_data_hash(&h, watcher->watcher_type, &watcher->watcher_data); + return nm_hash_complete(&h); +} + +static gboolean +_watcher_handle_equal(gconstpointer a, gconstpointer b) +{ + const NMNetnsWatcherHandle *ha = a; + const NMNetnsWatcherHandle *hb = b; + + nm_assert(ha); + nm_assert(hb); + nm_assert(ha->tag); + nm_assert(hb->tag); + + if (ha == hb) + return TRUE; + + return (ha->tag == hb->tag) && (ha->watcher_type == hb->watcher_type) + && _watcher_data_equal(ha->watcher_type, &ha->watcher_data, &hb->watcher_data); +} + +static const char * +_watcher_handle_to_string(const NMNetnsWatcherHandle *handle, char *buf, gsize buf_size) +{ + const char *buf0 = buf; + char sbuf[NM_INET_ADDRSTRLEN]; + + nm_strbuf_append(&buf, + &buf_size, + "h:" NM_HASH_OBFUSCATE_PTR_FMT "[", + NM_HASH_OBFUSCATE_PTR(handle)); + + if (handle->tag) { + nm_strbuf_append(&buf, + &buf_size, + "tag:" NM_HASH_OBFUSCATE_PTR_FMT ",", + NM_HASH_OBFUSCATE_PTR(handle->tag)); + } + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + nm_strbuf_append_str(&buf, &buf_size, "ip-addr:"); + nm_strbuf_append_str(&buf, + &buf_size, + nm_inet_ntop(handle->watcher_data.ip_addr.addr.addr_family, + &handle->watcher_data.ip_addr.addr.addr, + sbuf)); + goto out; + } + nm_assert_not_reached(); + nm_strbuf_append_str(&buf, &buf_size, "unknown"); + +out: + nm_strbuf_append_c(&buf, &buf_size, ']'); + return buf0; +} + +static void +_watcher_handle_notify(NMNetns *self, + NMNetnsWatcherHandle *handle, + const NMNetnsWatcherEventData *event_data) +{ + nm_assert(NM_IS_NETNS(self)); + nm_assert(handle); + nm_assert(handle->callback); + + handle->callback(self, + handle->watcher_type, + &handle->watcher_data, + handle->tag, + event_data, + handle->callback_user_data); +} + +static WatcherDataIPAddr * +_watcher_ip_data_lookup(NMNetns *self, int addr_family, gconstpointer addr) +{ + WatcherDataIPAddr needle; + + needle.addr.addr_family = addr_family; + nm_ip_addr_set(addr_family, &needle.addr.addr, addr); + return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_ip_data_idx, &needle); +} + +static WatcherDataIPAddr * +_watcher_ip_data_lookup_addr(NMNetns *self, const NMIPAddrTyped *addr) +{ + return _watcher_ip_data_lookup(self, addr->addr_family, &addr->addr); +} + +static guint +_watcher_ip_data_hash(gconstpointer _data) +{ + const WatcherDataIPAddr *data = _data; + NMHashState h; + + nm_assert(data); + + nm_hash_init(&h, 3152126191u); + nm_ip_addr_typed_hash_update(&h, &data->addr); + return nm_hash_complete(&h); +} + +static gboolean +_watcher_ip_data_equal(gconstpointer a, gconstpointer b) +{ + const WatcherDataIPAddr *data_a = a; + const WatcherDataIPAddr *data_b = b; + + nm_assert(data_a); + nm_assert(data_b); + + return nm_ip_addr_typed_equal(&data_a->addr, &data_b->addr); +} + +static NMNetnsWatcherHandle * +_watcher_lookup_handle(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag) +{ + NMNetnsWatcherHandle handle_needle; + + nm_assert(NM_IS_NETNS(self)); + nm_assert(tag); + + _watcher_handle_init(&handle_needle, watcher_type, watcher_data, tag); + return g_hash_table_lookup(NM_NETNS_GET_PRIVATE(self)->watcher_idx, &handle_needle); +} + +static void +_watcher_register_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + { + WatcherDataIPAddr *data; + + data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + if (!data) { + data = g_slice_new(WatcherDataIPAddr); + *data = (WatcherDataIPAddr){ + .addr = handle->watcher_data.ip_addr.addr, + .watcher_ip_addr_lst_head = C_LIST_INIT(data->watcher_ip_addr_lst_head), + }; + if (!g_hash_table_add(priv->watcher_ip_data_idx, data)) + nm_assert_not_reached(); + } + + c_list_link_tail(&data->watcher_ip_addr_lst_head, + &handle->reg_data.ip_addr.watcher_ip_addr_lst); + return; + } + } + nm_assert_not_reached(); +} + +static void +_watcher_unregister_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); + + switch (handle->watcher_type) { + case NM_NETNS_WATCHER_TYPE_IP_ADDR: + { + gboolean is_last; + + nm_assert(({ + WatcherDataIPAddr *d; + + d = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + d &&c_list_contains(&d->watcher_ip_addr_lst_head, + &handle->reg_data.ip_addr.watcher_ip_addr_lst); + })); + + is_last = c_list_is_empty_or_single(&handle->reg_data.ip_addr.watcher_ip_addr_lst); + + c_list_unlink(&handle->reg_data.ip_addr.watcher_ip_addr_lst); + + if (is_last) { + WatcherDataIPAddr *data; + + data = _watcher_ip_data_lookup_addr(self, &handle->watcher_data.ip_addr.addr); + nm_assert(data); + nm_assert(c_list_is_empty(&data->watcher_ip_addr_lst_head)); + + if (!g_hash_table_remove(priv->watcher_ip_data_idx, data)) + nm_assert_not_reached(); + + nm_g_slice_free(data); + } + return; + } + } + nm_assert_not_reached(); +} + +void +nm_netns_watcher_add(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + NMNetnsWatcherCallback callback, + gpointer user_data) +{ + NMNetnsPrivate *priv; + NMNetnsWatcherHandle *handle; + gboolean is_new = FALSE; + char sbuf[500]; + + g_return_if_fail(NM_IS_NETNS(self)); + g_return_if_fail(NM_NETNS_WATCHER_TYPE_VALID(watcher_type)); + g_return_if_fail(callback); + g_return_if_fail(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + handle = _watcher_lookup_handle(self, watcher_type, watcher_data, tag); + + if (!handle) { + WatcherByTag *watcher_by_tag; + + if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0)) + g_object_ref(self); + + handle = g_slice_new(NMNetnsWatcherHandle); + _watcher_handle_init(handle, watcher_type, watcher_data, tag); + + if (!g_hash_table_add(priv->watcher_idx, handle)) + nm_assert_not_reached(); + + watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag); + + if (!watcher_by_tag) { + watcher_by_tag = g_slice_new(WatcherByTag); + *watcher_by_tag = (WatcherByTag){ + .tag = tag, + .watcher_by_tag_lst_head = C_LIST_INIT(watcher_by_tag->watcher_by_tag_lst_head), + }; + g_hash_table_add(priv->watcher_by_tag_idx, watcher_by_tag); + } + + c_list_link_tail(&watcher_by_tag->watcher_by_tag_lst_head, &handle->watcher_tag_lst); + + is_new = TRUE; + } else { + /* Handles are deduplicated/shared. Hence it is error prone (and likely + * a bug) to provide different callback/user_data. Such usage is + * rejected here. + * + * This could be made to work, for example by now allowing handles to + * be merged or simply requiring the caller to be careful to not get + * this wrong. But that is currently not implemented nor needed. + */ + nm_assert(!tag + || (handle->callback == callback && handle->callback_user_data == user_data)); + } + + if (_LOGT_ENABLED() + && (is_new || handle->callback != callback || handle->callback_user_data != user_data)) { + _LOGT("netns-watcher: %s %s", + is_new ? "register" : "update", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + } + + handle->callback = callback; + handle->callback_user_data = user_data; + handle->watcher_dirty = FALSE; + + if (is_new) + _watcher_register_handle(self, handle); + + /* We cannot return a handle here, because handles are deduplicated via the priv->watchers_idx dictionary. + * The usage pattern is to use nm_netns_watcher_remove_all(), and not remove them one by one. + * As nm_netns_watcher_add() can return the same handle more than once, the user + * wouldn't know when it's safe to call nm_netns_watcher_remove_handle(). + * + * This could be extended by adding a ref-count to the handles. But that is not + * used currently, so it's not possible to remove watcher by their handle. */ +} + +static void +nm_netns_watcher_remove_handle(NMNetns *self, NMNetnsWatcherHandle *handle) +{ + NMNetnsPrivate *priv; + char sbuf[500]; + + g_return_if_fail(NM_IS_NETNS(self)); + g_return_if_fail(handle); + nm_assert(handle->tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + nm_assert(g_hash_table_lookup(priv->watcher_idx, handle) == handle); + + _LOGT("netns-watcher: %s %s", + "unregister", + _watcher_handle_to_string(handle, sbuf, sizeof(sbuf))); + + _watcher_unregister_handle(self, handle); + + if (!g_hash_table_remove(priv->watcher_idx, handle)) + nm_assert_not_reached(); + + if (c_list_is_empty_or_single(&handle->watcher_tag_lst)) { + if (!g_hash_table_remove(priv->watcher_by_tag_idx, &handle->tag)) + nm_assert_not_reached(); + } + + c_list_unlink_stale(&handle->watcher_tag_lst); + nm_g_slice_free(handle); + + if (G_UNLIKELY(g_hash_table_size(priv->watcher_idx) == 0)) + g_object_unref(self); +} + +void +nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all) +{ + NMNetnsPrivate *priv; + WatcherByTag *watcher_by_tag; + NMNetnsWatcherHandle *handle; + NMNetnsWatcherHandle *handle_safe; + + g_return_if_fail(NM_IS_NETNS(self)); + + /* remove-all only works with handles that have a tag associated. + * Since NMNetns can have multiple users that are unknown to each + * other, it makes no sense to have a remove-all function which + * would remove all of them. */ + g_return_if_fail(tag); + + priv = NM_NETNS_GET_PRIVATE(self); + + watcher_by_tag = g_hash_table_lookup(priv->watcher_by_tag_idx, &tag); + if (!watcher_by_tag) + return; + + c_list_for_each_entry_safe (handle, + handle_safe, + &watcher_by_tag->watcher_by_tag_lst_head, + watcher_tag_lst) { + gboolean is_last; + + if (!all && !handle->watcher_dirty) { + /* Survivors are marked as dirty. This enables a pattern where you + * call nm_netns_watcher_add() on the elements you care about + * (which clears the dirty flag), and then remove all dirty ones + * with nm_netns_watcher_remove_all() (which marks the remaining + * handles as dirty for the next time). */ + handle->watcher_dirty = TRUE; + continue; + } + + is_last = c_list_is_empty_or_single(&watcher_by_tag->watcher_by_tag_lst_head); + nm_netns_watcher_remove_handle(self, handle); + + if (is_last) { + /* Removing the last handle destroys the "watcher_by_tag" and may even + * destroy "self". We must not touch those pointers hereafter. + * + * If you ever *not* return here, make sure to handle that! */ + return; + } + } +} + +/*****************************************************************************/ + +static void set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NMNetns *self = NM_NETNS(object); @@ -850,6 +1421,7 @@ nm_netns_init(NMNetns *self) NMNetnsPrivate *priv = NM_NETNS_GET_PRIVATE(self); priv->_self_signal_user_data = self; + c_list_init(&priv->l3cfg_signal_pending_lst_head); G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(EcmpTrackObj, obj) == 0); @@ -859,6 +1431,14 @@ nm_netns_init(NMNetns *self) _ecmp_routes_by_ecmpid_equal, _ecmp_routes_by_ecmpid_free, NULL); + + priv->watcher_idx = g_hash_table_new(_watcher_handle_hash, _watcher_handle_equal); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(WatcherByTag, tag) == 0); + priv->watcher_by_tag_idx = g_hash_table_new_full(nm_pdirect_hash, + nm_pdirect_equal, + (GDestroyNotify) _watcher_by_tag_destroy, + NULL); + priv->watcher_ip_data_idx = g_hash_table_new(_watcher_ip_data_hash, _watcher_ip_data_equal); } static void @@ -937,10 +1517,17 @@ dispose(GObject *object) nm_assert(nm_g_hash_table_size(priv->l3cfgs) == 0); nm_assert(c_list_is_empty(&priv->l3cfg_signal_pending_lst_head)); nm_assert(!priv->shared_ips); + nm_assert(nm_g_hash_table_size(priv->watcher_idx) == 0); + nm_assert(nm_g_hash_table_size(priv->watcher_by_tag_idx) == 0); + nm_assert(nm_g_hash_table_size(priv->watcher_ip_data_idx) == 0); nm_clear_pointer(&priv->ecmp_track_by_obj, g_hash_table_destroy); nm_clear_pointer(&priv->ecmp_track_by_ecmpid, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_by_tag_idx, g_hash_table_destroy); + nm_clear_pointer(&priv->watcher_ip_data_idx, g_hash_table_destroy); + nm_clear_g_source_inst(&priv->signal_pending_idle_source); if (priv->platform) diff --git a/src/core/nm-netns.h b/src/core/nm-netns.h index 84a78f8363..7725ae79b4 100644 --- a/src/core/nm-netns.h +++ b/src/core/nm-netns.h @@ -59,4 +59,46 @@ void nm_netns_ip_route_ecmp_commit(NMNetns *self, GPtrArray **routes, gboolean is_reapply); +/*****************************************************************************/ + +typedef enum { + NM_NETNS_WATCHER_TYPE_IP_ADDR, +} NMNetnsWatcherType; + +typedef struct { + union { + struct { + NMIPAddrTyped addr; + } ip_addr; + }; +} NMNetnsWatcherData; + +typedef struct { + union { + struct { + const NMPObject *obj; + NMPlatformSignalChangeType change_type; + } ip_addr; + }; +} NMNetnsWatcherEventData; + +typedef struct _NMNetnsWatcherHandle NMNetnsWatcherHandle; + +typedef void (*NMNetnsWatcherCallback)(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + const NMNetnsWatcherEventData *event_data, + gpointer user_data); + +void nm_netns_watcher_add(NMNetns *self, + NMNetnsWatcherType watcher_type, + const NMNetnsWatcherData *watcher_data, + gconstpointer tag, + NMNetnsWatcherCallback callback, + gpointer user_data); + +void +nm_netns_watcher_remove_all(NMNetns *self, gconstpointer tag, gboolean all /* or only dirty */); + #endif /* __NM_NETNS_H__ */ diff --git a/src/libnm-glib-aux/nm-inet-utils.h b/src/libnm-glib-aux/nm-inet-utils.h index 40ba60c653..087a8af179 100644 --- a/src/libnm-glib-aux/nm-inet-utils.h +++ b/src/libnm-glib-aux/nm-inet-utils.h @@ -3,6 +3,8 @@ #ifndef __NM_INET_UTILS_H__ #define __NM_INET_UTILS_H__ +#include "libnm-std-aux/unaligned-fundamental.h" + typedef union _NMIPAddr { guint8 addr_ptr[sizeof(struct in6_addr)]; in_addr_t addr4; @@ -15,6 +17,11 @@ typedef union _NMIPAddr { NMEtherAddr _ether_addr; } NMIPAddr; +typedef struct _NMIPAddrTyped { + NMIPAddr addr; + gint8 addr_family; +} NMIPAddrTyped; + #define NM_IP_ADDR_INIT \ { \ .addr_ptr = { 0 } \ @@ -85,14 +92,15 @@ nm_ip_addr_set(int addr_family, gpointer dst, gconstpointer src) static inline gboolean nm_ip_addr_is_null(int addr_family, gconstpointer addr) { - NMIPAddr a; + struct in6_addr a6; - nm_ip_addr_set(addr_family, &a, addr); + nm_assert(addr); if (NM_IS_IPv4(addr_family)) - return a.addr4 == 0; + return unaligned_read_ne32(addr) == 0; - return IN6_IS_ADDR_UNSPECIFIED(&a.addr6); + memcpy(&a6, addr, sizeof(struct in6_addr)); + return IN6_IS_ADDR_UNSPECIFIED(&a6); } static inline NMIPAddr @@ -138,6 +146,30 @@ nm_ip_addr_from_packed_array(int addr_family, gconstpointer ipaddr_arr, gsize id /*****************************************************************************/ +static inline int +nm_ip_addr_typed_cmp(const NMIPAddrTyped *a, const NMIPAddrTyped *b) +{ + NM_CMP_SELF(a, b); + NM_CMP_FIELD(a, b, addr_family); + NM_CMP_DIRECT_MEMCMP(&a->addr, &b->addr, nm_utils_addr_family_to_size(a->addr_family)); + return 0; +} + +static inline gboolean +nm_ip_addr_typed_equal(const NMIPAddrTyped *a, const NMIPAddrTyped *b) +{ + return nm_ip_addr_typed_cmp(a, b) == 0; +} + +static inline void +nm_ip_addr_typed_hash_update(NMHashState *h, const NMIPAddrTyped *addr) +{ + nm_hash_update_vals(h, addr->addr_family); + nm_hash_update_mem(h, &addr->addr, nm_utils_addr_family_to_size(addr->addr_family)); +} + +/*****************************************************************************/ + static inline guint32 nm_ip4_addr_netmask_to_prefix(in_addr_t subnetmask) { diff --git a/src/libnm-glib-aux/nm-prioq.c b/src/libnm-glib-aux/nm-prioq.c index 3448dcd969..897d65dd51 100644 --- a/src/libnm-glib-aux/nm-prioq.c +++ b/src/libnm-glib-aux/nm-prioq.c @@ -21,10 +21,36 @@ /*****************************************************************************/ -struct _NMPrioqItem { +typedef struct _NMPrioqItem { void *data; unsigned *idx; -}; +} PrioqItem; + +/*****************************************************************************/ + +#define _nm_assert_q(q) \ + G_STMT_START \ + { \ + const NMPrioq *const _q2 = (q); \ + \ + nm_assert(_q2); \ + nm_assert(_q2->_priv.n_items == 0 || _q2->_priv.items); \ + nm_assert(_q2->_priv.compare_func); \ + } \ + G_STMT_END + +#define _nm_assert_item(q, item) \ + G_STMT_START \ + { \ + const NMPrioq *const _q = (q); \ + const PrioqItem *const _item = (item); \ + \ + _nm_assert_q(_q); \ + \ + nm_assert(_item >= _q->_priv.items); \ + nm_assert(_item < &_q->_priv.items[_q->_priv.n_items]); \ + } \ + G_STMT_END /*****************************************************************************/ @@ -72,6 +98,15 @@ nm_prioq_destroy(NMPrioq *q) if (!q || !q->_priv.compare_func) return; + _nm_assert_q(q); + + while (q->_priv.n_items > 0) { + PrioqItem *i = &q->_priv.items[--q->_priv.n_items]; + + if (i->idx) + *i->idx = NM_PRIOQ_IDX_NULL; + } + free(q->_priv.items); q->_priv.compare_func = NULL; } @@ -81,8 +116,7 @@ nm_prioq_destroy(NMPrioq *q) static int compare(NMPrioq *q, unsigned a, unsigned b) { - nm_assert(q); - nm_assert(q->_priv.compare_func); + _nm_assert_q(q); nm_assert(a != b); nm_assert(a < q->_priv.n_items); nm_assert(b < q->_priv.n_items); @@ -99,15 +133,13 @@ compare(NMPrioq *q, unsigned a, unsigned b) static void swap(NMPrioq *q, unsigned j, unsigned k) { - nm_assert(q); + _nm_assert_q(q); nm_assert(j < q->_priv.n_items); nm_assert(k < q->_priv.n_items); - nm_assert(!q->_priv.items[j].idx || *(q->_priv.items[j].idx) == j); nm_assert(!q->_priv.items[k].idx || *(q->_priv.items[k].idx) == k); - NM_SWAP(&q->_priv.items[j].data, &q->_priv.items[k].data); - NM_SWAP(&q->_priv.items[j].idx, &q->_priv.items[k].idx); + NM_SWAP(&q->_priv.items[j], &q->_priv.items[k]); if (q->_priv.items[j].idx) *q->_priv.items[j].idx = j; @@ -119,7 +151,7 @@ swap(NMPrioq *q, unsigned j, unsigned k) static unsigned shuffle_up(NMPrioq *q, unsigned idx) { - nm_assert(q); + _nm_assert_q(q); nm_assert(idx < q->_priv.n_items); while (idx > 0) { @@ -140,7 +172,7 @@ shuffle_up(NMPrioq *q, unsigned idx) static unsigned shuffle_down(NMPrioq *q, unsigned idx) { - nm_assert(q); + _nm_assert_q(q); for (;;) { unsigned j; @@ -184,19 +216,21 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx) { unsigned k; - nm_assert(q); + _nm_assert_q(q); + nm_assert(q->_priv.n_items < G_MAXUINT); - if (q->_priv.n_items >= q->_priv.n_allocated) { + if (G_UNLIKELY(q->_priv.n_items >= q->_priv.n_allocated)) { q->_priv.n_allocated = NM_MAX((q->_priv.n_items + 1u) * 2u, 16u); - q->_priv.items = g_renew(struct _NMPrioqItem, q->_priv.items, q->_priv.n_allocated); + q->_priv.items = g_renew(PrioqItem, q->_priv.items, q->_priv.n_allocated); } k = q->_priv.n_items++; - q->_priv.items[k] = (struct _NMPrioqItem){ + q->_priv.items[k] = (PrioqItem){ .data = data, .idx = idx, }; + if (idx) *idx = k; @@ -204,71 +238,88 @@ nm_prioq_put(NMPrioq *q, void *data, unsigned *idx) } static void -remove_item(NMPrioq *q, struct _NMPrioqItem *i) +remove_item(NMPrioq *q, PrioqItem *i) { - struct _NMPrioqItem *l; - unsigned k; + PrioqItem *l; + unsigned k; - nm_assert(q); - nm_assert(i); - nm_assert(q->_priv.n_items > 0); - nm_assert(i >= q->_priv.items); - nm_assert(i < &q->_priv.items[q->_priv.n_items]); + _nm_assert_item(q, i); + + if (i->idx) + *i->idx = NM_PRIOQ_IDX_NULL; + + q->_priv.n_items--; - l = &q->_priv.items[q->_priv.n_items - 1u]; + l = &q->_priv.items[q->_priv.n_items]; if (i == l) { - /* Last entry, let's just remove it */ - q->_priv.n_items--; + /* Last entry, nothing to do. */ return; } - /* Not last entry, let's replace the last entry with - * this one, and reshuffle */ + /* Not last entry, let's replace this entry with the last one, and + * reshuffle */ + k = i - q->_priv.items; *i = *l; + if (i->idx) *i->idx = k; - q->_priv.n_items--; k = shuffle_down(q, k); shuffle_up(q, k); } -_nm_pure static struct _NMPrioqItem * +static PrioqItem * find_item(NMPrioq *q, void *data, unsigned *idx) { - struct _NMPrioqItem *i; - - nm_assert(q); + PrioqItem *i; - if (q->_priv.n_items <= 0) - return NULL; + _nm_assert_q(q); - if (idx) { - if (*idx == NM_PRIOQ_IDX_NULL || *idx >= q->_priv.n_items) - return NULL; - - i = &q->_priv.items[*idx]; - if (i->data == data) - return i; - } else { + if (G_UNLIKELY(!idx)) { + /* We allow using NMPrioq without "idx". In that case, it does a linear + * search for the data. */ for (i = q->_priv.items; i < &q->_priv.items[q->_priv.n_items]; i++) { if (i->data == data) return i; } + return NULL; } - return NULL; + /* If the user however provides an "idx" pointer, then we assert that it is + * consistent. That is, if data is not in the queue, then we require that + * "*idx" is NM_PRIOQ_IDX_NULL, and otherwise we require that we really + * find "data" at index "*idx". + * + * This means, when the user calls nm_prioq_{remove,update,reshuffle}() + * with an "idx", then they must make sure that the index is consistent. + * Usually this means they are required to initialize the index to + * NM_PRIOQ_IDX_NULL while the data is not in the heap. + * + * This is done to assert more, and requires a stricter usage of the API + * (in the hope to find misuses of the index). */ + + if (*idx >= q->_priv.n_items) { + nm_assert(*idx == NM_PRIOQ_IDX_NULL); + return NULL; + } + + i = &q->_priv.items[*idx]; + + if (i->data != data) + return nm_assert_unreachable_val(NULL); + + return i; } gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx) { - struct _NMPrioqItem *i; + PrioqItem *i; - nm_assert(q); + _nm_assert_q(q); i = find_item(q, data, idx); if (!i) @@ -278,28 +329,60 @@ nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx) return TRUE; } +static void +reshuffle_item(NMPrioq *q, PrioqItem *i) +{ + unsigned k; + + _nm_assert_item(q, i); + + k = i - q->_priv.items; + k = shuffle_down(q, k); + shuffle_up(q, k); +} + gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx) { - struct _NMPrioqItem *i; - unsigned k; + PrioqItem *i; - nm_assert(q); + _nm_assert_q(q); i = find_item(q, data, idx); if (!i) return FALSE; - k = i - q->_priv.items; - k = shuffle_down(q, k); - shuffle_up(q, k); + reshuffle_item(q, i); return TRUE; } +void +nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */) +{ + PrioqItem *i; + + _nm_assert_q(q); + + i = find_item(q, data, idx); + + if (!i) { + if (queued) + nm_prioq_put(q, data, idx); + return; + } + + if (!queued) { + remove_item(q, i); + return; + } + + reshuffle_item(q, i); +} + void * nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) { - nm_assert(q); + _nm_assert_q(q); if (idx >= q->_priv.n_items) return NULL; @@ -312,7 +395,7 @@ nm_prioq_pop(NMPrioq *q) { void *data; - nm_assert(q); + _nm_assert_q(q); if (q->_priv.n_items <= 0) return NULL; diff --git a/src/libnm-glib-aux/nm-prioq.h b/src/libnm-glib-aux/nm-prioq.h index 918c644724..51e5b059fb 100644 --- a/src/libnm-glib-aux/nm-prioq.h +++ b/src/libnm-glib-aux/nm-prioq.h @@ -43,6 +43,8 @@ void nm_prioq_put(NMPrioq *q, void *data, unsigned *idx); gboolean nm_prioq_remove(NMPrioq *q, void *data, unsigned *idx); gboolean nm_prioq_reshuffle(NMPrioq *q, void *data, unsigned *idx); +void nm_prioq_update(NMPrioq *q, void *data, unsigned *idx, bool queued /* or else remove */); + void *nm_prioq_peek_by_index(NMPrioq *q, unsigned idx) _nm_pure; static inline void * @@ -53,7 +55,7 @@ nm_prioq_peek(NMPrioq *q) void *nm_prioq_pop(NMPrioq *q); -#define NM_PRIOQ_FOREACH_ITEM(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index(q, _i)); _i++) +#define nm_prioq_for_each(q, p) for (unsigned _i = 0; (p = nm_prioq_peek_by_index((q), _i)); _i++) _nm_pure static inline unsigned nm_prioq_size(NMPrioq *q) diff --git a/src/libnm-glib-aux/nm-shared-utils.c b/src/libnm-glib-aux/nm-shared-utils.c index c71d3dc190..8b255101b3 100644 --- a/src/libnm-glib-aux/nm-shared-utils.c +++ b/src/libnm-glib-aux/nm-shared-utils.c @@ -20,6 +20,7 @@ #include "c-list/src/c-list.h" #include "nm-errno.h" +#include "nm-time-utils.h" #include "nm-str-buf.h" #include "nm-time-utils.h" @@ -4840,6 +4841,50 @@ nm_g_child_watch_source_new(GPid pid, return source; } +gboolean +nm_g_timeout_reschedule(GSource **src, + gint64 *p_expiry_msec, + gint64 expiry_msec, + GSourceFunc func, + gpointer user_data) +{ + gint64 now_msec; + gint64 timeout_msec; + + /* (Re-)Schedules a timeout at "expiry_msec" (in + * nm_utils_get_monotonic_timestamp_msec() scale). + * + * If a source is already scheduled in "*src" and "*p_expiry_msec" is + * identical to "expiry_msec", then we assume the timer is already ticking, + * and nothing is rescheduled. + * + * Otherwise, "*src" gets cancelled (if any), a new timer is scheduled + * (assigned to "*src") and the new expiry is written to "*p_expiry_msec". + */ + + nm_assert(src); + nm_assert(p_expiry_msec); + + if (*src) { + if (*p_expiry_msec == expiry_msec) { + /* already scheduled with same expiry. */ + return FALSE; + } + nm_clear_g_source_inst(src); + } + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + + if (expiry_msec <= now_msec) + timeout_msec = 0; + else + timeout_msec = NM_MIN(expiry_msec - now_msec, (gint64) G_MAXUINT); + + *p_expiry_msec = expiry_msec; + *src = nm_g_timeout_add_source(timeout_msec, func, user_data); + return TRUE; +} + /*****************************************************************************/ #define _CTX_LOG(fmt, ...) \ diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 05d96cbd7e..d9f1d7b008 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -1543,6 +1543,12 @@ nm_g_timeout_add_source(guint timeout_msec, GSourceFunc func, gpointer user_data NULL); } +gboolean nm_g_timeout_reschedule(GSource **src, + gint64 *p_expiry_msec, + gint64 expiry_msec, + GSourceFunc func, + gpointer user_data); + static inline GSource * nm_g_timeout_add_seconds_source(guint timeout_sec, GSourceFunc func, gpointer user_data) { diff --git a/src/libnm-glib-aux/tests/test-shared-general.c b/src/libnm-glib-aux/tests/test-shared-general.c index 3eaca5474a..563c370243 100644 --- a/src/libnm-glib-aux/tests/test-shared-general.c +++ b/src/libnm-glib-aux/tests/test-shared-general.c @@ -2348,7 +2348,7 @@ test_garray(void) static int _prioq_cmp(gconstpointer a, gconstpointer b) { - NM_CMP_DIRECT(GPOINTER_TO_UINT(a), GPOINTER_TO_UINT(b)); + NM_CMP_DIRECT(*((const guint32 *) a), *((const guint32 *) b)); return 0; } @@ -2362,11 +2362,12 @@ static void test_nm_prioq(void) { nm_auto_prioq NMPrioq q = NM_PRIOQ_ZERO; - gpointer data[200]; - gpointer data_pop[200]; + guint32 data[200]; + const guint32 *data_pop[200]; guint data_idx[G_N_ELEMENTS(data)]; guint i; guint n; + guint m; gpointer p; if (nmtst_get_rand_one_case_in(10)) @@ -2379,37 +2380,67 @@ test_nm_prioq(void) g_assert(nm_prioq_size(&q) == 0); - if (nmtst_get_rand_one_case_in(10)) + if (nmtst_get_rand_one_case_in(100)) return; for (i = 0; i < G_N_ELEMENTS(data); i++) { - data[i] = GUINT_TO_POINTER((nmtst_get_rand_uint32() % G_N_ELEMENTS(data)) + 1u); + data[i] = nmtst_get_rand_uint32() % G_N_ELEMENTS(data); data_idx[i] = NM_PRIOQ_IDX_NULL; } - nm_prioq_put(&q, data[0], NULL); + nm_prioq_put(&q, &data[0], NULL); g_assert(nm_prioq_size(&q) == 1); p = nm_prioq_pop(&q); - g_assert(p == data[0]); + g_assert(p == &data[0]); g_assert(nm_prioq_size(&q) == 0); g_assert(!nm_prioq_pop(&q)); n = nmtst_get_rand_uint32() % G_N_ELEMENTS(data); for (i = 0; i < n; i++) - nm_prioq_put(&q, data[i], &data_idx[i]); + nm_prioq_put(&q, &data[i], &data_idx[i]); - g_assert_cmpint(nm_prioq_size(&q), ==, n); + m = n; + for (i = 0; i < n; i++) { + if (!nmtst_get_rand_bool()) + continue; - if (nmtst_get_rand_one_case_in(10)) + data[i] = nmtst_get_rand_uint32() % G_N_ELEMENTS(data); + switch (nmtst_get_rand_uint32() % 4) { + case 0: + nm_prioq_reshuffle(&q, &data[i], &data_idx[i]); + break; + case 1: + nm_prioq_remove(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL); + m--; + break; + case 2: + nm_prioq_update(&q, &data[i], &data_idx[i], TRUE); + break; + case 3: + nm_prioq_update(&q, &data[i], nmtst_get_rand_bool() ? &data_idx[i] : NULL, FALSE); + m--; + break; + } + } + + g_assert_cmpint(nm_prioq_size(&q), ==, m); + + if (nmtst_get_rand_one_case_in(50)) return; - for (i = 0; i < n; i++) { + for (i = 0; i < m; i++) { data_pop[i] = nm_prioq_pop(&q); g_assert(data_pop[i]); - if (i > 0) + g_assert_cmpint(*data_pop[i], >=, 0); + g_assert_cmpint(*data_pop[i], <, G_N_ELEMENTS(data)); + g_assert(data_pop[i] >= &data[0]); + g_assert(data_pop[i] < &data[n]); + if (i > 0) { g_assert(_prioq_cmp(data_pop[i - 1], data_pop[i]) <= 0); + g_assert_cmpint(*data_pop[i - 1], <=, *data_pop[i]); + } } g_assert(!nm_prioq_pop(&q)); diff --git a/src/libnm-lldp/nm-lldp-neighbor.c b/src/libnm-lldp/nm-lldp-neighbor.c index f1e2d42eb0..a2a9695e85 100644 --- a/src/libnm-lldp/nm-lldp-neighbor.c +++ b/src/libnm-lldp/nm-lldp-neighbor.c @@ -735,6 +735,7 @@ nm_lldp_neighbor_new(size_t raw_size) n->raw_size = raw_size; n->ref_count = 1; + n->prioq_idx = NM_PRIOQ_IDX_NULL; return n; } diff --git a/src/libnm-platform/nm-linux-platform.c b/src/libnm-platform/nm-linux-platform.c index c75fb3bf81..b6c2902d20 100644 --- a/src/libnm-platform/nm-linux-platform.c +++ b/src/libnm-platform/nm-linux-platform.c @@ -8149,6 +8149,7 @@ do_add_addrroute(NMPlatform *platform, do_request_one_type_by_needle_object(platform, obj_id); } + NM_SET_OUT(out_extack_msg, g_steal_pointer(&extack_msg)); return wait_for_nl_response_to_nmerr(seq_result); } diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index eddbaba376..deaf72acab 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -4593,53 +4593,6 @@ nm_platform_ip_address_flush(NMPlatform *self, int addr_family, int ifindex) /*****************************************************************************/ -static gboolean -_route_is_temporary_not_available(NMPlatform *self, - int err, - const NMPObject *obj, - const char **out_err_reason) -{ - const NMPlatformIPXRoute *rx; - const NMPlatformIP6Address *a; - - nm_assert(NM_IS_PLATFORM(self)); - nm_assert(NMP_OBJECT_IS_VALID(obj)); - - if (err != -EINVAL) - return FALSE; - - rx = NMP_OBJECT_CAST_IPX_ROUTE(obj); - - if (rx->rx.rt_source != NM_IP_CONFIG_SOURCE_USER) { - /* we only allow this workaround for routes added manually by the user. */ - return FALSE; - } - - if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ROUTE) { - return FALSE; - } else { - const NMPlatformIP6Route *r = &rx->r6; - - /* trying to add an IPv6 route with pref-src fails, if the address is - * still tentative (rh#1452684). We need to hack around that. - * - * Detect it, by guessing whether that's the case. */ - - if (IN6_IS_ADDR_UNSPECIFIED(&r->pref_src)) - return FALSE; - - a = nm_platform_ip6_address_get(self, r->ifindex, &r->pref_src); - if (!a) - return FALSE; - if (!NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TENTATIVE) - || NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_DADFAILED)) - return FALSE; - - *out_err_reason = "tentative IPv6 src address not ready"; - return TRUE; - } -} - static guint _ipv6_temporary_addr_prefixes_keep_hash(gconstpointer ptr) { @@ -4968,8 +4921,8 @@ nm_platform_ip_route_get_prune_list(NMPlatform *self, * at the end of the operation. Note that if @routes contains * the same route, then it will not be deleted. @routes overrules * @routes_prune list. - * @out_temporary_not_available: (allow-none) (out): routes that could - * currently not be synced. The caller shall keep them and try later again. + * @out_routes_failed: (allow-none) (out): routes that could + * not be synced/added. * * Returns: %TRUE on success. */ @@ -4979,7 +4932,7 @@ nm_platform_ip_route_sync(NMPlatform *self, int ifindex, GPtrArray *routes, GPtrArray *routes_prune, - GPtrArray **out_temporary_not_available) + GPtrArray **out_routes_failed) { const int IS_IPv4 = NM_IS_IPv4(addr_family); const NMPlatformVTableRoute *vt; @@ -4999,9 +4952,7 @@ nm_platform_ip_route_sync(NMPlatform *self, for (i_type = 0; routes && i_type < 2; i_type++) { for (i = 0; i < routes->len; i++) { - gs_free char *extack_msg = NULL; - gboolean gateway_route_added = FALSE; - int r2; + gs_free char *extack_msg = NULL; int r; conf_o = routes->pdata[i]; @@ -5058,147 +5009,59 @@ nm_platform_ip_route_sync(NMPlatform *self, } } -sync_route_add: r = nm_platform_ip_route_add(self, NMP_NLM_FLAG_APPEND | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, conf_o, &extack_msg); - if (r < 0) { - const char *err_reason = NULL; - - if (r == -EEXIST) { - /* Don't fail for EEXIST. It's not clear that the existing route - * is identical to the one that we were about to add. However, - * above we should have deleted conflicting (non-identical) routes. */ - if (_LOGD_ENABLED()) { - plat_entry = - nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); - if (!plat_entry) { - _LOG3D("route-sync: adding route %s failed with EEXIST, however we " - "cannot find such a route", - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1))); - } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), - NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj), - NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) - != 0) { - _LOG3D("route-sync: adding route %s failed due to existing " - "(different!) route %s", - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1)), - nmp_object_to_string(plat_entry->obj, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf2, - sizeof(sbuf2))); - } - } - } else if (NMP_OBJECT_CAST_IP_ROUTE(conf_o)->rt_source < NM_IP_CONFIG_SOURCE_USER) { - _LOG3D("route-sync: ignore failure to add IPv%c route: %s: %s", - vt->is_ip4 ? '4' : '6', - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1)), - nm_strerror(r)); - } else if (out_temporary_not_available - && _route_is_temporary_not_available(self, r, conf_o, &err_reason)) { - _LOG3D("route-sync: ignore temporary failure to add route (%s, %s): %s", - nm_strerror(r), - err_reason, - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1))); - if (!*out_temporary_not_available) - *out_temporary_not_available = - g_ptr_array_new_full(0, (GDestroyNotify) nmp_object_unref); - g_ptr_array_add(*out_temporary_not_available, - (gpointer) nmp_object_ref(conf_o)); - } else if (!gateway_route_added - && ((r == -ENETUNREACH && vt->is_ip4 - && !!NMP_OBJECT_CAST_IP4_ROUTE(conf_o)->gateway) - || (r == -EHOSTUNREACH && !vt->is_ip4 - && !IN6_IS_ADDR_UNSPECIFIED( - &NMP_OBJECT_CAST_IP6_ROUTE(conf_o)->gateway)))) { - NMPObject oo; - - if (vt->is_ip4) { - const NMPlatformIP4Route *rt = NMP_OBJECT_CAST_IP4_ROUTE(conf_o); - - nmp_object_stackinit( - &oo, - NMP_OBJECT_TYPE_IP4_ROUTE, - &((NMPlatformIP4Route){ - .ifindex = rt->ifindex, - .network = rt->gateway, - .plen = 32, - .metric = nm_platform_ip4_route_get_effective_metric(rt), - .rt_source = rt->rt_source, - .table_coerced = nm_platform_ip_route_get_effective_table( - NM_PLATFORM_IP_ROUTE_CAST(rt)), - })); - } else { - const NMPlatformIP6Route *rt = NMP_OBJECT_CAST_IP6_ROUTE(conf_o); - - nmp_object_stackinit( - &oo, - NMP_OBJECT_TYPE_IP6_ROUTE, - &((NMPlatformIP6Route){ - .ifindex = rt->ifindex, - .network = rt->gateway, - .plen = 128, - .metric = nm_platform_ip6_route_get_effective_metric(rt), - .rt_source = rt->rt_source, - .table_coerced = nm_platform_ip_route_get_effective_table( - NM_PLATFORM_IP_ROUTE_CAST(rt)), - })); - } - - _LOG3D("route-sync: failure to add IPv%c route: %s: %s; try adding direct " - "route to gateway %s", - vt->is_ip4 ? '4' : '6', - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1)), - nm_strerror(r), - nmp_object_to_string(&oo, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf2, - sizeof(sbuf2))); - - r2 = nm_platform_ip_route_add(self, - NMP_NLM_FLAG_APPEND - | NMP_NLM_FLAG_SUPPRESS_NETLINK_FAILURE, - &oo, - NULL); - - if (r2 < 0) { - _LOG3D("route-sync: failure to add gateway IPv%c route: %s: %s", - vt->is_ip4 ? '4' : '6', + if (r == 0) { + /* success */ + } else if (r == -EEXIST) { + /* Don't fail for EEXIST. It's not clear that the existing route + * is identical to the one that we were about to add. However, + * above we should have deleted conflicting (non-identical) routes. */ + if (_LOGD_ENABLED()) { + plat_entry = + nm_platform_lookup_entry(self, NMP_CACHE_ID_TYPE_OBJECT_TYPE, conf_o); + if (!plat_entry) { + _LOG3D("route-sync: adding route %s failed with EEXIST, however we " + "cannot find such a route", + nmp_object_to_string(conf_o, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + } else if (vt->route_cmp(NMP_OBJECT_CAST_IPX_ROUTE(conf_o), + NMP_OBJECT_CAST_IPX_ROUTE(plat_entry->obj), + NM_PLATFORM_IP_ROUTE_CMP_TYPE_SEMANTICALLY) + != 0) { + _LOG3D("route-sync: adding route %s failed due to existing " + "(different!) route %s", nmp_object_to_string(conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1)), - nm_strerror(r2)); + nmp_object_to_string(plat_entry->obj, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf2, + sizeof(sbuf2))); } + } + } else { + _LOG3D( + "route-sync: failure to add IPv%c route: %s: %s%s%s%s", + vt->is_ip4 ? '4' : '6', + nmp_object_to_string(conf_o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf1, sizeof(sbuf1)), + nm_strerror(r), + NM_PRINT_FMT_QUOTED(extack_msg, " (", extack_msg, ")", "")); - gateway_route_added = TRUE; - goto sync_route_add; - } else { - _LOG3W("route-sync: failure to add IPv%c route: %s: %s", - vt->is_ip4 ? '4' : '6', - nmp_object_to_string(conf_o, - NMP_OBJECT_TO_STRING_PUBLIC, - sbuf1, - sizeof(sbuf1)), - nm_strerror(r)); - success = FALSE; + success = FALSE; + + if (out_routes_failed) { + if (!*out_routes_failed) { + *out_routes_failed = + g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + } + g_ptr_array_add(*out_routes_failed, (gpointer) nmp_object_ref(conf_o)); } } } @@ -5346,7 +5209,7 @@ _ip_route_add(NMPlatform *self, NMPNlmFlags flags, NMPObject *obj_stack, char ** char sbuf[NM_UTILS_TO_STRING_BUFFER_SIZE]; int ifindex; - _CHECK_SELF(self, klass, FALSE); + _CHECK_SELF(self, klass, -NME_BUG); /* The caller already ensures that this is a stack allocated copy, that * - stays alive for the duration of the call. @@ -7037,7 +6900,6 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, "%s" /* rto_min */ "%s" /* quickack */ "%s" /* mtu */ - "%s" /* r_force_commit */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -7104,8 +6966,7 @@ nm_platform_ip4_route_to_string_full(const NMPlatformIP4Route *route, " mtu %s%" G_GUINT32_FORMAT, route->lock_mtu ? "lock " : "", route->mtu) - : "", - route->r_force_commit ? " force-commit" : ""); + : ""); if ((n_nexthops == 1 && route->ifindex > 0) || n_nexthops == 0) { /* A plain single hop route. Nothing extra to remark. */ @@ -7225,7 +7086,6 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz "%s" /* quickack */ "%s" /* mtu */ "%s" /* pref */ - "%s" /* r_force_commit */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -7292,8 +7152,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz str_pref, " pref %s", nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2))) - : "", - route->r_force_commit ? " force-commit" : ""); + : ""); return buf; } @@ -8680,8 +8539,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, obj->lock_initcwnd, obj->lock_initrwnd, obj->lock_mtu, - obj->lock_mss, - obj->r_force_commit)); + obj->lock_mss)); break; } } @@ -8824,8 +8682,6 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, NM_CMP_FIELD(a, b, initrwnd); NM_CMP_FIELD(a, b, mtu); NM_CMP_FIELD(a, b, rto_min); - if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) - NM_CMP_FIELD_UNSAFE(a, b, r_force_commit); break; } return 0; @@ -8925,8 +8781,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, obj->lock_initcwnd, obj->lock_initrwnd, obj->lock_mtu, - obj->lock_mss, - obj->r_force_commit), + obj->lock_mss), obj->window, obj->cwnd, obj->initcwnd, @@ -9016,8 +8871,6 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref)); else NM_CMP_FIELD(a, b, rt_pref); - if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) - NM_CMP_FIELD_UNSAFE(a, b, r_force_commit); break; } return 0; @@ -9339,7 +9192,6 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr, int ifindex, guint32 route_table, guint32 route_metric, - gboolean force_commit, NMPlatformIP4Route *dst) { in_addr_t network_4; @@ -9369,15 +9221,14 @@ nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr, } *dst = (NMPlatformIP4Route){ - .ifindex = ifindex, - .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, - .network = network_4, - .plen = addr->plen, - .pref_src = addr->address, - .table_coerced = nm_platform_route_table_coerce(route_table), - .metric = route_metric, - .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK), - .r_force_commit = force_commit, + .ifindex = ifindex, + .rt_source = NM_IP_CONFIG_SOURCE_KERNEL, + .network = network_4, + .plen = addr->plen, + .pref_src = addr->address, + .table_coerced = nm_platform_route_table_coerce(route_table), + .metric = route_metric, + .scope_inv = nm_platform_route_scope_inv(NM_RT_SCOPE_LINK), }; nm_platform_ip_route_normalize(AF_INET, (NMPlatformIPRoute *) dst); diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 08a7716e39..86e01d63de 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -243,13 +243,6 @@ typedef enum { guint _nm_platform_signal_id_get(NMPlatformSignalIdType signal_type); -typedef enum { - NM_PLATFORM_SIGNAL_NONE, - NM_PLATFORM_SIGNAL_ADDED, - NM_PLATFORM_SIGNAL_CHANGED, - NM_PLATFORM_SIGNAL_REMOVED, -} NMPlatformSignalChangeType; - /* Default value for adding an IPv4 route. This is also what iproute2 does. * Note that contrary to IPv6, you can add routes with metric 0 and it is even * the default. @@ -2306,6 +2299,19 @@ nm_platform_ip_route_get_gateway(int addr_family, const NMPlatformIPRoute *route return &((NMPlatformIP6Route *) route)->gateway; } +static inline gconstpointer +nm_platform_ip_route_get_pref_src(int addr_family, const NMPlatformIPRoute *route) +{ + nm_assert_addr_family(addr_family); + + if (!route) + return NULL; + + if (NM_IS_IPv4(addr_family)) + return &((NMPlatformIP4Route *) route)->pref_src; + return &((NMPlatformIP6Route *) route)->pref_src; +} + int nm_platform_ip_route_add(NMPlatform *self, NMPNlmFlags flags, const NMPObject *route, @@ -2326,7 +2332,7 @@ gboolean nm_platform_ip_route_sync(NMPlatform *self, int ifindex, GPtrArray *routes, GPtrArray *routes_prune, - GPtrArray **out_temporary_not_available); + GPtrArray **out_routes_failed); gboolean nm_platform_ip_route_flush(NMPlatform *self, int addr_family, int ifindex); diff --git a/src/libnm-platform/nmp-base.h b/src/libnm-platform/nmp-base.h index 80d254b261..4802b03553 100644 --- a/src/libnm-platform/nmp-base.h +++ b/src/libnm-platform/nmp-base.h @@ -28,6 +28,15 @@ /*****************************************************************************/ typedef enum { + NM_PLATFORM_SIGNAL_NONE, + NM_PLATFORM_SIGNAL_ADDED, + NM_PLATFORM_SIGNAL_CHANGED, + NM_PLATFORM_SIGNAL_REMOVED, +} NMPlatformSignalChangeType; + +/*****************************************************************************/ + +typedef enum { NM_PLATFORM_LINK_DUPLEX_UNKNOWN, NM_PLATFORM_LINK_DUPLEX_HALF, NM_PLATFORM_LINK_DUPLEX_FULL, diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h index 0d5f84b310..408f0318cb 100644 --- a/src/libnm-platform/nmp-object.h +++ b/src/libnm-platform/nmp-object.h @@ -1183,21 +1183,6 @@ nm_platform_lookup_object_by_addr_family(NMPlatform *platform, /*****************************************************************************/ -static inline gboolean -nmp_object_get_force_commit(const NMPObject *obj) -{ - switch (NMP_OBJECT_GET_TYPE(obj)) { - case NMP_OBJECT_TYPE_IP4_ADDRESS: - case NMP_OBJECT_TYPE_IP6_ADDRESS: - return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_force_commit; - case NMP_OBJECT_TYPE_IP4_ROUTE: - case NMP_OBJECT_TYPE_IP6_ROUTE: - return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_force_commit; - default: - return nm_assert_unreachable_val(FALSE); - } -} - static inline const char * nmp_object_link_get_ifname(const NMPObject *obj) { diff --git a/src/libnm-platform/nmp-plobj.h b/src/libnm-platform/nmp-plobj.h index e149150d29..833167f678 100644 --- a/src/libnm-platform/nmp-plobj.h +++ b/src/libnm-platform/nmp-plobj.h @@ -255,7 +255,6 @@ NMPlatformIP4Route *nm_platform_ip4_address_generate_device_route(const NMPlatfo int ifindex, guint32 route_table, guint32 route_metric, - gboolean force_commit, NMPlatformIP4Route *dst); typedef enum { |