From b8f9d7b5dd11070ecb51b1aa5f4a4f6ad15fc0b5 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 28 Sep 2020 18:07:51 +0200 Subject: [PATCH] l3cfg: rework ACD handling in NML3Cfg to support handling conflicts Heavily rework NML3Cfg's ACD handling. - the (user facing) API changed, so that we can ask the current ACD state of an address with nm_l3cfg_get_acd_addr_info(). So, the acd-event signal is only to notify when the state changes, it does not carry information that you couldn't fetch anytime. - add clearer ACD states (NML3AcdAddrState). The current (ACD) state of an address is important and becomes part of the information that we expose. - add new ACD state "USED", when ACD fails. This blocks the address from being used. Usually the caller would either remove the (used) address or force reconfigure it (by setting acd_timeout_msec to zero). - add new ACD state "CONFLICT". Previously conflicts were not handled. Now the API allows to specify the defend policy. A conflicted address also gets blocked from being used. - add new ACD state "EXTERNAL_REMOVED". This happens when we have an address we wanted to configure, but then the address is no longer on the interface. For example because the user removed it from the interface. This also leaves the device indefinitely blocked, and is important to stop announcing the address. - add a new ACD state "READY". This indicates that the address is ready to be configured, but not yet actually configured on the device. This is the step before "DEFENDING". --- src/nm-l3cfg.c | 2171 +++++++++++++++++++++++----------------- src/nm-l3cfg.h | 58 +- src/tests/test-l3cfg.c | 104 +- 3 files changed, 1381 insertions(+), 952 deletions(-) diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c index a176d354a..b68920699 100644 --- a/src/nm-l3cfg.c +++ b/src/nm-l3cfg.c @@ -19,10 +19,11 @@ #define ACD_ENSURE_RATELIMIT_MSEC ((guint32) 4000u) #define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32)(1000u + ACD_ENSURE_RATELIMIT_MSEC)) #define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC ((guint32) 1000u) -#define ACD_WAIT_PROBING_RESTART_TIME_MSEC ((guint32) 8000u) #define ACD_MAX_TIMEOUT_MSEC ((guint32) 30000u) #define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u) -#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 20000u) +#define ACD_WAIT_TIME_CONFLICT_RESTART_MSEC ((guint32) 120000u) +#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 30000u) +#define ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC ((guint32) 30000u) static gboolean ACD_ADDR_SKIP(in_addr_t addr) @@ -39,12 +40,15 @@ ACD_ADDR_SKIP(in_addr_t addr) ACD_TRACK_PTR2((acd_track)->l3cd, (acd_track)->obj, (acd_track)->tag) typedef enum { - ACD_STATE_CHANGE_MODE_INIT, - ACD_STATE_CHANGE_MODE_POST_COMMIT, + ACD_STATE_CHANGE_MODE_NACD_CONFLICT = N_ACD_EVENT_CONFLICT, + ACD_STATE_CHANGE_MODE_NACD_DEFENDED = N_ACD_EVENT_DEFENDED, + ACD_STATE_CHANGE_MODE_NACD_DOWN = N_ACD_EVENT_DOWN, + ACD_STATE_CHANGE_MODE_NACD_READY = N_ACD_EVENT_READY, + ACD_STATE_CHANGE_MODE_NACD_USED = N_ACD_EVENT_USED, - ACD_STATE_CHANGE_MODE_NACD_READY, - ACD_STATE_CHANGE_MODE_NACD_USED, - ACD_STATE_CHANGE_MODE_NACD_DOWN, + ACD_STATE_CHANGE_MODE_INIT = _N_ACD_EVENT_N, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT, ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, @@ -54,56 +58,66 @@ typedef enum { } AcdStateChangeMode; typedef struct { - CList acd_track_lst; - const NMPObject * obj; - const NML3ConfigData *l3cd; - gconstpointer tag; - guint32 acd_timeout_msec; - bool acd_dirty : 1; - bool acd_failed_notified : 1; + union { + NML3AcdAddrTrackInfo track_info; + struct { + const NMPObject * obj; + const NML3ConfigData *l3cd; + gconstpointer tag; + + guint32 acd_timeout_msec_track; + NML3AcdDefendType acd_defend_type_track; + bool acd_dirty_track : 1; + bool acd_failed_notified_track : 1; + }; + }; } AcdTrackData; -typedef enum _nm_packed { - ACD_STATE_INIT, - ACD_STATE_PROBING, - ACD_STATE_PROBE_DONE, - ACD_STATE_ANNOUNCING, -} AcdState; +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, track_info) == 0); +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, obj) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, obj)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, l3cd) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, l3cd)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, tag) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, tag)); +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, acd_timeout_msec_track) + >= G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, _padding)); +G_STATIC_ASSERT(sizeof(AcdTrackData) == sizeof(NML3AcdAddrTrackInfo)); + +#define ACD_TRACK_DATA(arg) NM_CONSTCAST(AcdTrackData, arg, NML3AcdAddrTrackInfo) + +G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3AcdAddrInfo, addr) == 0); typedef struct { - in_addr_t addr; - - /* This is only relevant while in state ACD_STATE_PROBING. It's the - * duration for how long we probe, and @probing_timestamp_msec is the - * timestamp when we start probing. */ - guint32 probing_timeout_msec; + NML3AcdAddrInfo info; CList acd_lst; - CList acd_notify_complete_lst; - CList acd_track_lst_head; - - NML3Cfg *self; + CList acd_event_notify_lst; NAcdProbe *nacd_probe; - GSource *acd_timeout_source; - gint64 acd_timeout_expiry_msec; + GSource *acd_data_timeout_source; /* see probing_timeout_msec. */ gint64 probing_timestamp_msec; - /* the ACD state for this address. */ - AcdState acd_state; + gint64 last_defendconflict_timestamp_msec; - /* The probe result. This is only relevant if @acd_state is ACD_STATE_PROBE_DONE. - * In state ACD_STATE_ANNOUNCING the @probe_result must be TRUE. */ - bool probe_result : 1; + guint n_track_infos_alloc; - bool announcing_failed_is_retrying : 1; + /* This is only relevant while in state NM_L3_ACD_ADDR_STATE_PROBING. It's the + * duration for how long we probe, and @probing_timestamp_msec is the + * timestamp when we start probing. */ + guint32 probing_timeout_msec; - bool initializing : 1; + NMEtherAddr last_conflict_addr; + + NML3AcdDefendType acd_defend_type_desired : 3; + NML3AcdDefendType acd_defend_type_current : 3; + bool acd_defend_type_is_active : 1; + + bool track_infos_changed : 1; } AcdData; +G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0); + struct _NML3CfgCommitTypeHandle { CList commit_type_lst; NML3CfgCommitType commit_type; @@ -133,11 +147,12 @@ typedef struct { }; guint32 default_route_penalty_x[2]; }; - gconstpointer tag; - guint64 pseudo_timestamp; - int priority; - guint32 acd_timeout_msec; - bool dirty : 1; + gconstpointer tag_confdata; + guint64 pseudo_timestamp_confdata; + int priority_confdata; + guint32 acd_timeout_msec_confdata; + NML3AcdDefendType acd_defend_type_confdata : 3; + bool dirty_confdata : 1; } L3ConfigData; /*****************************************************************************/ @@ -170,11 +185,14 @@ typedef struct _NML3CfgPrivate { GHashTable *acd_lst_hash; CList acd_lst_head; - CList acd_notify_complete_lst_head; + CList acd_event_notify_lst_head; NAcd * nacd; GSource *nacd_source; + GSource *nacd_event_down_source; + gint64 nacd_event_down_ratelimited_until_msec; + /* This is for rate-limiting the creation of nacd instance. */ GSource *nacd_instance_ensure_retry; @@ -198,16 +216,35 @@ typedef struct _NML3CfgPrivate { guint externally_removed_objs_cnt_routes_x[2]; }; + union { + struct { + GPtrArray *last_addresses_6; + GPtrArray *last_addresses_4; + }; + GPtrArray *last_addresses_x[2]; + }; + + union { + struct { + GPtrArray *last_routes_6; + GPtrArray *last_routes_4; + }; + GPtrArray *last_routes_x[2]; + }; + guint routes_temporary_not_available_id; + gint8 commit_reentrant_count; + bool commit_type_update_sticky : 1; bool acd_is_pending : 1; - bool acd_is_announcing : 1; bool nacd_acd_not_supported : 1; bool acd_ipv4_addresses_on_link_has : 1; + bool changed_configs_configs : 1; + bool changed_configs_acd_state : 1; } NML3CfgPrivate; struct _NML3CfgClass { @@ -234,15 +271,16 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) } \ G_STMT_END -#define _LOGT_acd(acd_data, ...) \ - G_STMT_START \ - { \ - char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \ - \ - _LOGT("acd[%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ - _nm_utils_inet4_ntop((acd_data)->addr, _sbuf_acd) \ - _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ - } \ +#define _LOGT_acd(acd_data, ...) \ + G_STMT_START \ + { \ + char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \ + \ + _LOGT("acd[%s, %s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + _nm_utils_inet4_ntop((acd_data)->info.addr, _sbuf_acd), \ + _l3_acd_addr_state_to_string((acd_data)->info.state) \ + _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ G_STMT_END /*****************************************************************************/ @@ -251,7 +289,7 @@ static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type); -static void _l3_acd_data_notify_acd_completed_all(NML3Cfg *self); +static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self); static gboolean _acd_has_valid_link(const NMPObject *obj, const guint8 ** out_addr_bin, @@ -260,12 +298,11 @@ static gboolean _acd_has_valid_link(const NMPObject *obj, static void _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_data_notify); -static void _l3_acd_data_prune(NML3Cfg *self, gboolean all); - static void _l3_acd_data_state_change(NML3Cfg * self, AcdData * acd_data, AcdStateChangeMode mode, - NAcdEvent * event); + const NMEtherAddr *sender, + gint64 * p_now_msec); static AcdData *_l3_acd_data_find(NML3Cfg *self, in_addr_t addr); @@ -282,7 +319,7 @@ static NM_UTILS_ENUM2STR_DEFINE(_l3_cfg_commit_type_to_string, static NM_UTILS_ENUM2STR_DEFINE( _l3_config_notify_type_to_string, NML3ConfigNotifyType, - NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, "acd-complete"), + NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"), @@ -290,6 +327,36 @@ static NM_UTILS_ENUM2STR_DEFINE( "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, + NML3AcdDefendType, + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ALWAYS, "always"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NEVER, "never"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NONE, "none"), + NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), ); + +static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd, + NML3AcdDefendType, + int, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ALWAYS, + N_ACD_DEFEND_ALWAYS), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ONCE, N_ACD_DEFEND_ONCE), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_NEVER, N_ACD_DEFEND_NEVER), + NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), ); + +static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string, + NML3AcdAddrState, + const char *, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_CONFLICT, "conflict"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_READY, "ready"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_DEFENDING, "defending"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_INIT, "init"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_PROBING, "probing"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, + "external-removed"), + NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_USED, "used"), ); + /*****************************************************************************/ static const char * @@ -308,12 +375,12 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, nm_utils_strbuf_seek_end(&s, &l); switch (notify_data->notify_type) { - case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED: + case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: nm_utils_strbuf_append(&s, &l, - ", addr=%s, probe-result=%d", - _nm_utils_inet4_ntop(notify_data->acd_completed.addr, sbuf_addr), - (int) notify_data->acd_completed.probe_result); + ", addr=%s, state=%s", + _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr), + _l3_acd_addr_state_to_string(notify_data->acd_event.info.state)); break; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE: nm_utils_strbuf_append( @@ -365,8 +432,9 @@ _nm_l3cfg_emit_signal_notify_simple(NML3Cfg *self, NML3ConfigNotifyType notify_t static void _l3_changed_configs_set_dirty(NML3Cfg *self) { - _LOGT("configuration changed"); - self->priv.changed_configs = TRUE; + _LOGT("IP configuration changed (mark dirty)"); + self->priv.p->changed_configs_configs = TRUE; + self->priv.p->changed_configs_acd_state = TRUE; } /*****************************************************************************/ @@ -386,7 +454,11 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self, else self->priv.p->acd_ipv4_addresses_on_link_has = FALSE; if (acd_data) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, NULL); + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + NULL, + NULL); return; } @@ -396,8 +468,13 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self, * cached addresses, and fetch them new the next time we need the information. */ nm_clear_pointer(&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref); self->priv.p->acd_ipv4_addresses_on_link_has = FALSE; - if (acd_data) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, NULL); + if (acd_data) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, + NULL, + NULL); + } } static gboolean @@ -666,9 +743,16 @@ _load_link(NML3Cfg *self, gboolean initial) _l3_acd_nacd_instance_reset(self, NM_TERNARY_FALSE, TRUE); } else if (nacd_link_now_up) { if (!c_list_is_empty(&self->priv.p->acd_lst_head)) { + gint64 now_msec = 0; + _LOGT("acd: link up requires are re-initialize of ACD probes"); - c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_LINK_NOW_UP, NULL); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_LINK_NOW_UP, + NULL, + &now_msec); + } } } } @@ -689,7 +773,7 @@ _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags) }; _nm_l3cfg_emit_signal_notify(self, ¬ify_data); - _l3_acd_data_notify_acd_completed_all(self); + _nm_l3cfg_emit_signal_notify_acd_event_all(self); if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ROUTE))) _property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE); @@ -895,68 +979,59 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self) static gboolean _acd_track_data_is_not_dirty(const AcdTrackData *acd_track) { - return acd_track && !acd_track->acd_dirty; + return acd_track && !acd_track->acd_dirty_track; } static void -_acd_track_data_free(AcdTrackData *acd_track) +_acd_track_data_clear(AcdTrackData *acd_track) { - c_list_unlink_stale(&acd_track->acd_track_lst); nm_l3_config_data_unref(acd_track->l3cd); nmp_object_unref(acd_track->obj); - nm_g_slice_free(acd_track); } static void _acd_data_free(AcdData *acd_data) { - nm_assert(c_list_is_empty(&acd_data->acd_track_lst_head)); + nm_assert(acd_data->info.n_track_infos == 0u); n_acd_probe_free(acd_data->nacd_probe); - nm_clear_g_source_inst(&acd_data->acd_timeout_source); + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); c_list_unlink_stale(&acd_data->acd_lst); + c_list_unlink_stale(&acd_data->acd_event_notify_lst); + g_free((AcdTrackData *) acd_data->info.track_infos); nm_g_slice_free(acd_data); } -static gboolean -_acd_data_probe_result_is_good(const AcdData *acd_data) -{ - nm_assert(acd_data); - - if (acd_data->acd_state < ACD_STATE_PROBE_DONE) { - /* we are currently probing. Wait. */ - return FALSE; - } - - /* Probing is already completed. Use the probe result. */ - return acd_data->probe_result; -} - static guint -_acd_data_collect_tracks_data(const AcdData *acd_data, - NMTernary dirty_selector, - NMTernary acd_failed_notified_selector, - guint32 * out_best_acd_timeout_msec) +_acd_data_collect_tracks_data(const AcdData * acd_data, + NMTernary dirty_selector, + guint32 * out_best_acd_timeout_msec, + NML3AcdDefendType *out_best_acd_defend_type) { - guint32 best_acd_timeout_msec = G_MAXUINT32; - AcdTrackData *acd_track; - guint n = 0; + NML3AcdDefendType best_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_NONE; + guint32 best_acd_timeout_msec = G_MAXUINT32; + guint n = 0; + guint i; + + for (i = 0; i < acd_data->info.n_track_infos; i++) { + const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]); - c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) { if (dirty_selector != NM_TERNARY_DEFAULT) { - if ((!!dirty_selector) != (!!acd_track->acd_dirty)) - continue; - } - if (acd_failed_notified_selector != NM_TERNARY_DEFAULT) { - if ((!!acd_failed_notified_selector) != (!!acd_track->acd_failed_notified)) + if ((!!dirty_selector) != (!!acd_track->acd_dirty_track)) continue; } n++; - if (best_acd_timeout_msec > acd_track->acd_timeout_msec) - best_acd_timeout_msec = acd_track->acd_timeout_msec; + if (best_acd_timeout_msec > acd_track->acd_timeout_msec_track) + best_acd_timeout_msec = acd_track->acd_timeout_msec_track; + if (best_acd_defend_type < acd_track->acd_defend_type_track) + best_acd_defend_type = acd_track->acd_defend_type_track; } + nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE); + nm_assert(best_acd_defend_type <= NM_L3_ACD_DEFEND_TYPE_ALWAYS); + NM_SET_OUT(out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u); + NM_SET_OUT(out_best_acd_defend_type, best_acd_defend_type); return n; } @@ -966,11 +1041,13 @@ _acd_data_find_track(const AcdData * acd_data, const NMPObject * obj, gconstpointer tag) { - AcdTrackData *acd_track; + guint i; + + for (i = 0; i < acd_data->info.n_track_infos; i++) { + const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]); - c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) { if (acd_track->obj == obj && acd_track->l3cd == l3cd && acd_track->tag == tag) - return acd_track; + return (AcdTrackData *) acd_track; } return NULL; @@ -995,7 +1072,7 @@ _acd_has_valid_link(const NMPObject *obj, link = NMP_OBJECT_CAST_LINK(obj); addr_bin = nmp_link_address_get(&link->l_address, &addr_len); - if (!addr_bin || addr_len != ACD_SUPPORTED_ETH_ALEN) { + if (addr_len != ACD_SUPPORTED_ETH_ALEN) { NM_SET_OUT(out_acd_not_supported, TRUE); return FALSE; } @@ -1005,6 +1082,21 @@ _acd_has_valid_link(const NMPObject *obj, return TRUE; } +static gboolean +_l3_acd_nacd_event_down_timeout_cb(gpointer user_data) +{ + NML3Cfg *self = user_data; + AcdData *acd_data; + gint64 now_msec = 0; + + _LOGT("acd: message possibly dropped due to device down (handle events)"); + nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL, &now_msec); + _nm_l3cfg_emit_signal_notify_acd_event_all(self); + return G_SOURCE_REMOVE; +} + static gboolean _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) { @@ -1022,8 +1114,10 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) } while (TRUE) { - AcdData * acd_data; - NAcdEvent *event; + NMEtherAddr sender_addr_data; + const NMEtherAddr *sender_addr; + AcdData * acd_data; + NAcdEvent * event; r = n_acd_pop_event(self->priv.p->nacd, &event); if (r) { @@ -1035,77 +1129,83 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) goto out; } -#define _acd_event_payload used - G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload) - == G_STRUCT_OFFSET(NAcdEvent, defended)); - G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload) - == G_STRUCT_OFFSET(NAcdEvent, conflict)); - G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload) - == G_STRUCT_OFFSET(NAcdEvent, used)); - nm_assert(&event->_acd_event_payload == &event->defended); - nm_assert(&event->_acd_event_payload == &event->conflict); - nm_assert(&event->_acd_event_payload == &event->used); - switch (event->event) { case N_ACD_EVENT_READY: - n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data); - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, event); + n_acd_probe_get_userdata(event->ready.probe, (void **) &acd_data); + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, NULL, NULL); break; case N_ACD_EVENT_USED: - n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data); - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_USED, event); - break; case N_ACD_EVENT_DEFENDED: case N_ACD_EVENT_CONFLICT: { - gs_free char *sender_str = NULL; - const char * addr_str = NULL; - char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; +#define _acd_event_payload_with_sender(event) \ + ({ \ + NAcdEvent *_event = (event); \ + \ + nm_assert(event); \ + nm_assert(NM_IN_SET(event->event, \ + N_ACD_EVENT_USED, \ + N_ACD_EVENT_DEFENDED, \ + N_ACD_EVENT_CONFLICT)); \ + nm_assert(&_event->used == &_event->defended); \ + nm_assert(&_event->used == &_event->conflict); \ + &_event->used; \ + }) - /* since we announce with N_ACD_DEFEND_ALWAYS, we don't actually expect any - * conflict reported and don't handle it. It would be complicated to de-configure - * the address. */ - nm_assert(event->event == N_ACD_EVENT_DEFENDED); + n_acd_probe_get_userdata(_acd_event_payload_with_sender(event)->probe, + (void **) &acd_data); - n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data); - _LOGT_acd(acd_data, - "address %s %s from %s", - (addr_str = _nm_utils_inet4_ntop(acd_data->addr, sbuf_addr)), - event->event == N_ACD_EVENT_DEFENDED ? "defended" : "conflict detected", - (sender_str = nm_utils_bin2hexstr_full(event->_acd_event_payload.sender, - event->_acd_event_payload.n_sender, - ':', - FALSE, - NULL))); - if (event->event == N_ACD_EVENT_CONFLICT) { - _LOGW("IPv4 address collision detection sees conflict on interface %i%s%s%s for " - "address %s from host %s", - self->priv.ifindex, - NM_PRINT_FMT_QUOTED(self->priv.plobj, - " (", - NMP_OBJECT_CAST_LINK(self->priv.plobj)->name, - ")", - ""), - addr_str ?: _nm_utils_inet4_ntop(acd_data->addr, sbuf_addr), - sender_str - ?: (sender_str = - nm_utils_bin2hexstr_full(event->_acd_event_payload.sender, - event->_acd_event_payload.n_sender, - ':', - FALSE, - NULL))); + if (_acd_event_payload_with_sender(event)->n_sender == ETH_ALEN) { + G_STATIC_ASSERT_EXPR(_nm_alignof(NMEtherAddr) == 1); + nm_assert(_acd_event_payload_with_sender(event)->sender); + memcpy(&sender_addr_data, _acd_event_payload_with_sender(event)->sender, ETH_ALEN); + sender_addr = &sender_addr_data; + } else { + nm_assert_not_reached(); + sender_addr = &nm_ether_addr_zero; } + + _l3_acd_data_state_change(self, + acd_data, + (AcdStateChangeMode) event->event, + sender_addr, + NULL); break; } case N_ACD_EVENT_DOWN: - _LOGT("acd: message possibly dropped due to device down."); - c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL); + if (!self->priv.p->nacd_event_down_source) { + gint64 now_msec; + guint32 timeout_msec; + + now_msec = nm_utils_get_monotonic_timestamp_msec(); + if (self->priv.p->nacd_event_down_ratelimited_until_msec > 0 + && now_msec < self->priv.p->nacd_event_down_ratelimited_until_msec) + timeout_msec = self->priv.p->nacd_event_down_ratelimited_until_msec - now_msec; + else { + timeout_msec = 0; + self->priv.p->nacd_event_down_ratelimited_until_msec = now_msec + 2000; + } + _LOGT("acd: message possibly dropped due to device down (schedule handling event " + "in %u msec)", + timeout_msec); + self->priv.p->nacd_event_down_source = + nm_g_timeout_source_new(timeout_msec, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_event_down_timeout_cb, + self, + NULL); + g_source_attach(self->priv.p->nacd_event_down_source, NULL); + } break; default: - _LOGT("acd: unexpected event %u. Ignore", event->event); + _LOGE("acd: unexpected event %u. Ignore", event->event); + nm_assert_not_reached(); break; } + + /* We are on an idle handler, and the n-acd events are expected to be independent. So, after + * each event emit all queued AcdEvent signals. */ + _nm_l3cfg_emit_signal_notify_acd_event_all(self); } nm_assert_not_reached(); @@ -1117,8 +1217,6 @@ out: _l3_acd_nacd_instance_reset(self, NM_TERNARY_TRUE, TRUE); } - _l3_acd_data_notify_acd_completed_all(self); - return G_SOURCE_CONTINUE; } @@ -1169,9 +1267,15 @@ _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_d if (acd_data_notify) { AcdData *acd_data; + gint64 now_msec = 0; - c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_INSTANCE_RESET, NULL); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_INSTANCE_RESET, + NULL, + &now_msec); + } } } @@ -1277,30 +1381,37 @@ _l3_acd_nacd_instance_create_probe(NML3Cfg * self, } static void -_l3_acd_data_free_trackers(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */) +_l3_acd_data_prune_one(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */) { - AcdTrackData *acd_track; - AcdTrackData *acd_track_safe; + AcdTrackData *acd_tracks; + guint i; + guint j; + + acd_tracks = (AcdTrackData *) acd_data->info.track_infos; + j = 0; + for (i = 0; i < acd_data->info.n_track_infos; i++) { + AcdTrackData *acd_track = &acd_tracks[i]; - c_list_for_each_entry_safe (acd_track, - acd_track_safe, - &acd_data->acd_track_lst_head, - acd_track_lst) { /* If not "all" is requested, we only delete the dirty ones * (and mark the survivors as dirty right away). */ - if (!all && !acd_track->acd_dirty) { - acd_track->acd_dirty = TRUE; + if (!all && !acd_track->acd_dirty_track) { + acd_track->acd_dirty_track = TRUE; + if (j != i) + acd_tracks[j] = *acd_track; + j++; continue; } _LOGT_acd(acd_data, "untrack " ACD_TRACK_FMT "", ACD_TRACK_PTR(acd_track)); - _acd_track_data_free(acd_track); + _acd_track_data_clear(acd_track); } - if (!c_list_is_empty(&acd_data->acd_track_lst_head)) + acd_data->info.n_track_infos = j; + if (j > 0) return; + _LOGT_acd(acd_data, "removed"); if (!g_hash_table_remove(self->priv.p->acd_lst_hash, acd_data)) nm_assert_not_reached(); _acd_data_free(acd_data); @@ -1313,7 +1424,7 @@ _l3_acd_data_prune(NML3Cfg *self, gboolean all /* or only dirty */) AcdData *acd_data; c_list_for_each_entry_safe (acd_data, acd_data_safe, &self->priv.p->acd_lst_head, acd_lst) - _l3_acd_data_free_trackers(self, acd_data, all); + _l3_acd_data_prune_one(self, acd_data, all); } static AcdData * @@ -1322,17 +1433,33 @@ _l3_acd_data_find(NML3Cfg *self, in_addr_t addr) return nm_g_hash_table_lookup(self->priv.p->acd_lst_hash, &addr); } +static gboolean +_l3_acd_data_defendconflict_warning_ratelimited(AcdData *acd_data, gint64 *p_now_msec) +{ + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + if (acd_data->last_defendconflict_timestamp_msec == 0 + || acd_data->last_defendconflict_timestamp_msec + > *p_now_msec - ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC) { + acd_data->last_defendconflict_timestamp_msec = *p_now_msec; + return FALSE; + } + return TRUE; +} + static void _l3_acd_data_add(NML3Cfg * self, const NML3ConfigData *l3cd, const NMPObject * obj, gconstpointer tag, + NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec) { in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; AcdTrackData *acd_track; AcdData * acd_data; const char * track_mode; + char sbuf100[100]; if (ACD_ADDR_SKIP(addr)) return; @@ -1347,64 +1474,91 @@ _l3_acd_data_add(NML3Cfg * self, if (!acd_data) { if (G_UNLIKELY(!self->priv.p->acd_lst_hash)) { - G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, addr) == 0); + G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, info.addr) == 0); self->priv.p->acd_lst_hash = g_hash_table_new(nm_puint32_hash, nm_puint32_equals); } acd_data = g_slice_new(AcdData); *acd_data = (AcdData){ - .self = self, - .addr = addr, - .acd_track_lst_head = C_LIST_INIT(acd_data->acd_track_lst_head), - .acd_notify_complete_lst = C_LIST_INIT(acd_data->acd_notify_complete_lst), - .acd_state = ACD_STATE_INIT, - .probing_timestamp_msec = 0, - .probe_result = FALSE, - .initializing = TRUE, + .info = + { + .l3cfg = self, + .addr = addr, + .state = NM_L3_ACD_ADDR_STATE_INIT, + .n_track_infos = 0, + .track_infos = NULL, + }, + .n_track_infos_alloc = 0, + .acd_event_notify_lst = C_LIST_INIT(acd_data->acd_event_notify_lst), + .probing_timestamp_msec = 0, + .acd_defend_type_desired = NM_L3_ACD_DEFEND_TYPE_NONE, + .acd_defend_type_current = NM_L3_ACD_DEFEND_TYPE_NONE, + .acd_defend_type_is_active = FALSE, }; c_list_link_tail(&self->priv.p->acd_lst_head, &acd_data->acd_lst); if (!g_hash_table_add(self->priv.p->acd_lst_hash, acd_data)) nm_assert_not_reached(); acd_track = NULL; - } else { + } else acd_track = _acd_data_find_track(acd_data, l3cd, obj, tag); - } if (!acd_track) { - acd_track = g_slice_new(AcdTrackData); + if (acd_data->info.n_track_infos >= acd_data->n_track_infos_alloc) { + acd_data->n_track_infos_alloc = NM_MAX(2u, acd_data->n_track_infos_alloc * 2u); + acd_data->info.track_infos = + g_realloc((gpointer) acd_data->info.track_infos, + acd_data->n_track_infos_alloc * sizeof(acd_data->info.track_infos[0])); + } + acd_track = (AcdTrackData *) &acd_data->info.track_infos[acd_data->info.n_track_infos++]; *acd_track = (AcdTrackData){ - .l3cd = nm_l3_config_data_ref(l3cd), - .obj = nmp_object_ref(obj), - .tag = tag, - .acd_dirty = FALSE, - .acd_timeout_msec = acd_timeout_msec, + .l3cd = nm_l3_config_data_ref(l3cd), + .obj = nmp_object_ref(obj), + .tag = tag, + .acd_dirty_track = FALSE, + .acd_defend_type_track = acd_defend_type, + .acd_timeout_msec_track = acd_timeout_msec, }; - c_list_link_tail(&acd_data->acd_track_lst_head, &acd_track->acd_track_lst); track_mode = "new"; } else { - nm_assert(acd_track->acd_dirty); - acd_track->acd_dirty = FALSE; - if (acd_track->acd_timeout_msec != acd_timeout_msec) { - acd_track->acd_timeout_msec = acd_timeout_msec; - track_mode = "update"; + nm_assert(acd_track->acd_dirty_track); + acd_track->acd_dirty_track = FALSE; + if (acd_track->acd_timeout_msec_track != acd_timeout_msec + || acd_track->acd_defend_type_track != acd_defend_type) { + acd_track->acd_defend_type_track = acd_defend_type; + acd_track->acd_timeout_msec_track = acd_timeout_msec; + track_mode = "update"; } else - track_mode = NULL; + return; } - if (track_mode) { - _LOGT_acd(acd_data, - "track " ACD_TRACK_FMT " with timeout %u msec (%s)", - ACD_TRACK_PTR(acd_track), - acd_timeout_msec, - track_mode); - } + acd_data->track_infos_changed = TRUE; + _LOGT_acd( + acd_data, + "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)", + ACD_TRACK_PTR(acd_track), + acd_timeout_msec, + _l3_acd_defend_type_to_string(acd_track->acd_defend_type_track, sbuf100, sizeof(sbuf100)), + track_mode); } static void -_l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint infos_len) +_l3_acd_data_add_all(NML3Cfg * self, + const L3ConfigData *const *infos, + guint infos_len, + gboolean reapply) { AcdData *acd_data; guint i_info; + gint64 now_msec = 0; + guint i; + +#if NM_MORE_ASSERTS > 5 + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + nm_assert(acd_data->info.n_track_infos > 0u); + for (i = 0; i < acd_data->info.n_track_infos; i++) + nm_assert(((const AcdTrackData *) &acd_data->info.track_infos[i])->acd_dirty_track); + } +#endif /* First we add/track all the relevant addresses for ACD. */ for (i_info = 0; i_info < infos_len; i_info++) { @@ -1412,63 +1566,54 @@ _l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint info NMDedupMultiIter iter; const NMPObject * obj; - nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) - _l3_acd_data_add(self, info->l3cd, obj, info->tag, info->acd_timeout_msec); + nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) { + _l3_acd_data_add(self, + info->l3cd, + obj, + info->tag_confdata, + info->acd_defend_type_confdata, + info->acd_timeout_msec_confdata); + } } /* Then we do a pre-flight check, whether some of the acd_data entries can already * move forward to automatically pass ACD. That is the case if acd_timeout_msec * is zero (to disable ACD) or if the address is already configured on the * interface. */ - c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_INIT, NULL); + c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { + _l3_acd_data_state_change(self, + acd_data, + reapply ? ACD_STATE_CHANGE_MODE_INIT_REAPPLY + : ACD_STATE_CHANGE_MODE_INIT, + NULL, + &now_msec); + } } static gboolean _l3_acd_data_timeout_cb(gpointer user_data) { AcdData *acd_data = user_data; - NML3Cfg *self = acd_data->self; + NML3Cfg *self = acd_data->info.l3cfg; nm_assert(NM_IS_L3CFG(self)); - nm_clear_g_source_inst(&acd_data->acd_timeout_source); - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL); + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL, NULL); return G_SOURCE_REMOVE; } static void -_l3_acd_data_timeout_schedule(AcdData *acd_data, - gint64 now_msec, - gint64 expiry_msec, - gboolean msec_granularity) +_l3_acd_data_timeout_schedule(AcdData *acd_data, gint64 timeout_msec) { - nm_assert(expiry_msec > 0); - nm_assert(now_msec > 0); - - if (acd_data->acd_timeout_source && acd_data->acd_timeout_expiry_msec == expiry_msec) - return; - - nm_clear_g_source_inst(&acd_data->acd_timeout_source); - - acd_data->acd_timeout_expiry_msec = expiry_msec; - - if (msec_granularity) { - acd_data->acd_timeout_source = nm_g_timeout_source_new(NM_MAX(0, expiry_msec - now_msec), - G_PRIORITY_DEFAULT, - _l3_acd_data_timeout_cb, - acd_data, - NULL); - } else { - acd_data->acd_timeout_source = - nm_g_timeout_source_new_seconds((NM_MAX(0, expiry_msec - now_msec) + 999) / 1000, - G_PRIORITY_DEFAULT, - _l3_acd_data_timeout_cb, - acd_data, - NULL); - } - - g_source_attach(acd_data->acd_timeout_source, NULL); + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + acd_data->acd_data_timeout_source = + nm_g_timeout_source_new(NM_CLAMP((gint64) 0, timeout_msec, (gint64) G_MAXUINT), + G_PRIORITY_DEFAULT, + _l3_acd_data_timeout_cb, + acd_data, + NULL); + g_source_attach(acd_data->acd_data_timeout_source, NULL); } static void @@ -1479,7 +1624,7 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec nm_assert(acd_data); nm_assert(now_msec > 0); - nm_assert(acd_data->acd_state == ACD_STATE_PROBING); + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING); nm_assert(!acd_data->nacd_probe); nm_assert(acd_data->probing_timeout_msec > 0); nm_assert(acd_data->probing_timestamp_msec > 0); @@ -1488,145 +1633,161 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec timeout_msec = NM_MAX(0, expiry_msec - now_msec); - if (timeout_msec > 1000) { - /* we poll at least once per second to re-check the state. */ - timeout_msec = 1000; + if (timeout_msec > 1500) { + /* we poll at least every 1.5 seconds to re-check the state. */ + timeout_msec = 1500; } - _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec + timeout_msec, TRUE); + _l3_acd_data_timeout_schedule(acd_data, timeout_msec); } static void -_l3_acd_data_timeout_schedule_probing_full_restart(AcdData *acd_data, gint64 now_msec) +_nm_l3cfg_emit_signal_notify_acd_event(NML3Cfg *self, AcdData *acd_data) { + gs_free NML3AcdAddrTrackInfo *track_infos_clone = NULL; + NML3ConfigNotifyData notify_data; + NML3AcdAddrInfo * info; + guint i; + nm_assert(acd_data); - nm_assert(now_msec > 0); - nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE); - nm_assert(!acd_data->probe_result); + nm_assert(acd_data->info.state > NM_L3_ACD_ADDR_STATE_INIT); + nm_assert(acd_data->info.n_track_infos > 0); - _l3_acd_data_timeout_schedule(acd_data, - now_msec, - now_msec + ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC, - FALSE); -} - -static void -_l3_acd_data_timeout_schedule_announce_restart(AcdData *acd_data, gint64 now_msec) -{ - nm_assert(acd_data); - nm_assert(now_msec > 0); - nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE); - nm_assert(acd_data->probe_result); - - _l3_acd_data_timeout_schedule(acd_data, - now_msec, - now_msec + ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC, - FALSE); -} - -static void -_l3_acd_data_notify_acd_completed(NML3Cfg *self, AcdData *acd_data, gboolean force_all) -{ - gs_free NML3ConfigNotifyPayloadAcdFailedSource *sources_free = NULL; - NML3ConfigNotifyPayloadAcdFailedSource * sources = NULL; - NML3ConfigNotifyData notify_data; - AcdTrackData * acd_track; - guint i, n; - NMTernary acd_failed_notified_selector; - - nm_assert(NM_IS_L3CFG(self)); - nm_assert(acd_data); - nm_assert(_acd_data_collect_tracks_data(acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) == 0); - - acd_failed_notified_selector = force_all ? NM_TERNARY_DEFAULT : FALSE; - - n = _acd_data_collect_tracks_data(acd_data, - NM_TERNARY_DEFAULT, - acd_failed_notified_selector, - NULL); - - if (n == 0) - return; - - if (!force_all) { - _LOGT_acd(acd_data, "state: acd probe failed earlier. Emit notification for new trackers"); - } - - if (n * sizeof(sources[0]) > 300) { - sources_free = g_new(NML3ConfigNotifyPayloadAcdFailedSource, n); - sources = sources_free; - } else - sources = g_newa(NML3ConfigNotifyPayloadAcdFailedSource, n); - - i = 0; - c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) { - if (!force_all && acd_track->acd_failed_notified) { - /* already notified before. Skip. */ - continue; - } - nm_assert(i < n); - acd_track->acd_failed_notified = TRUE; - sources[i++] = (NML3ConfigNotifyPayloadAcdFailedSource){ - .obj = nmp_object_ref(acd_track->obj), - .l3cd = nm_l3_config_data_ref(acd_track->l3cd), - .tag = acd_track->tag, - }; - } - nm_assert(i == n); - - notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED; - notify_data.acd_completed = (typeof(notify_data.acd_completed)){ - .addr = acd_data->addr, - .probe_result = acd_data->probe_result, - .sources_len = n, - .sources = sources, + notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT; + notify_data.acd_event = (typeof(notify_data.acd_event)){ + .info = acd_data->info, }; + + /* we need to clone the track-data, because the callee is allowed to add/remove + * configs. This means, the event data is stale. If you need the current + * value, look it up with nm_l3cfg_get_acd_addr_info(). */ + info = ¬ify_data.acd_event.info; + info->track_infos = nm_memdup_maybe_a(300, + info->track_infos, + info->n_track_infos * sizeof(info->track_infos[0]), + &track_infos_clone); + + for (i = 0; i < info->n_track_infos; i++) { + NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i]; + + nmp_object_ref(ti->obj); + nm_l3_config_data_ref(ti->l3cd); + } + _nm_l3cfg_emit_signal_notify(self, ¬ify_data); - for (i = 0; i < n; i++) { - nmp_object_unref(sources[i].obj); - nm_l3_config_data_unref(sources[i].l3cd); + for (i = 0; i < info->n_track_infos; i++) { + NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i]; + + nmp_object_unref(ti->obj); + nm_l3_config_data_unref(ti->l3cd); } } static void -_l3_acd_data_notify_acd_completed_queue(NML3Cfg *self, AcdData *acd_data) +_nm_l3cfg_emit_signal_notify_acd_event_queue(NML3Cfg *self, AcdData *acd_data) { - if (!c_list_is_empty(&acd_data->acd_notify_complete_lst)) { - nm_assert(c_list_contains(&self->priv.p->acd_notify_complete_lst_head, - &acd_data->acd_notify_complete_lst)); + if (!c_list_is_empty(&acd_data->acd_event_notify_lst)) { + nm_assert(c_list_contains(&self->priv.p->acd_event_notify_lst_head, + &acd_data->acd_event_notify_lst)); return; } - c_list_link_tail(&self->priv.p->acd_notify_complete_lst_head, - &acd_data->acd_notify_complete_lst); + c_list_link_tail(&self->priv.p->acd_event_notify_lst_head, &acd_data->acd_event_notify_lst); } static void -_l3_acd_data_notify_acd_completed_all(NML3Cfg *self) +_nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self) { gs_unref_object NML3Cfg *self_keep_alive = NULL; AcdData * acd_data; - while ((acd_data = c_list_first_entry(&self->priv.p->acd_notify_complete_lst_head, + while ((acd_data = c_list_first_entry(&self->priv.p->acd_event_notify_lst_head, AcdData, - acd_notify_complete_lst))) { + acd_event_notify_lst))) { if (!self_keep_alive) self_keep_alive = g_object_ref(self); - c_list_unlink(&acd_data->acd_notify_complete_lst); - _l3_acd_data_notify_acd_completed(self, acd_data, TRUE); + c_list_unlink(&acd_data->acd_event_notify_lst); + _nm_l3cfg_emit_signal_notify_acd_event(self, acd_data); } } +_nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg * self, + AcdData * acd_data, + NML3AcdAddrState state, + gboolean allow_commit, + const char * format, + ...) +{ + NML3AcdAddrState old_state; + gboolean changed; + + if (acd_data->info.state == state) + return; + + /* in every state we only have one timer possibly running. Resetting + * the states makes the previous timeout obsolete. */ + nm_clear_g_source_inst(&acd_data->acd_data_timeout_source); + + old_state = acd_data->info.state; + acd_data->info.state = state; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + + if (state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) + changed = FALSE; + else if (NM_IN_SET(old_state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING) + != NM_IN_SET(state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)) + changed = TRUE; + else + changed = FALSE; + + if (format) { + gs_free char *msg = NULL; + va_list args; + + va_start(args, format); + msg = g_strdup_vprintf(format, args); + va_end(args); + + _LOGT_acd(acd_data, "set state to %s (%s)", _l3_acd_addr_state_to_string(state), msg); + } else + _LOGT_acd(acd_data, "set state to %s", _l3_acd_addr_state_to_string(state)); + + if (changed && allow_commit) { + /* The availability of an address just changed (and we are instructed to + * trigger a new commit). Do it. */ + _l3_changed_configs_set_dirty(self); + nm_l3cfg_commit_on_idle_schedule(self); + } +} + +static void +_l3_acd_data_state_set(NML3Cfg * self, + AcdData * acd_data, + NML3AcdAddrState state, + gboolean allow_commit) +{ + _l3_acd_data_state_set_full(self, acd_data, state, allow_commit, NULL); +} + static void _l3_acd_data_state_change(NML3Cfg * self, AcdData * acd_data, AcdStateChangeMode state_change_mode, - NAcdEvent * event) + const NMEtherAddr *sender_addr, + gint64 * p_now_msec) + { - guint32 acd_timeout_msec; - gint64 now_msec = 0; - const char *log_reason; - gboolean was_probing; + guint32 acd_timeout_msec; + NML3AcdDefendType acd_defend_type; + gint64 now_msec; + const char * log_reason; + char sbuf256[256]; + char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN]; + + if (!p_now_msec) { + now_msec = 0; + p_now_msec = &now_msec; + } /* Keeping track of ACD inevitably requires keeping (and mutating) state. Then a multitude of * things can happen, and depending on the state, we need to do something. @@ -1646,415 +1807,533 @@ _l3_acd_data_state_change(NML3Cfg * self, nm_assert(NM_IS_L3CFG(self)); nm_assert(acd_data); - nm_assert(!c_list_is_empty(&acd_data->acd_track_lst_head)); + nm_assert(acd_data->info.n_track_infos); + nm_assert(NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_CONFLICT, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING, + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, + NM_L3_ACD_ADDR_STATE_USED)); + nm_assert(!acd_data->track_infos_changed + || NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED)); + nm_assert((!!sender_addr) + == NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_NACD_USED, + ACD_STATE_CHANGE_MODE_NACD_CONFLICT, + ACD_STATE_CHANGE_MODE_NACD_DEFENDED)); - was_probing = acd_data->acd_state < ACD_STATE_PROBE_DONE; + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) { + /* once remove, the state can only change by external added or during + * the POST-COMMIT check. */ + if (!NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED)) + return; + } switch (state_change_mode) { case ACD_STATE_CHANGE_MODE_INIT: - { - AcdTrackData *acd_track; - gboolean any_no_timeout; + case ACD_STATE_CHANGE_MODE_INIT_REAPPLY: - /* we are called from _l3_acd_data_add_all(), and we do a fast check whether - * newly tracked entries already passed ACD so that we can use the address - * right away. */ + /* We are called right before commit. We check whether we have a acd_data + * in INIT or PROBING state. In that case, maybe the new configuration + * disables ACD, or we have the address already configured (which also let's + * us skip/cancel the probing). The point is that if the address would be ready + * already, we want to commit it right away. */ - if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->addr)) { - /* the address is already configured on the link. It is an automatic pass. */ - if (_acd_data_collect_tracks_data(acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) <= 0) { - /* The entry has no non-dirty trackers, that means, it's no longer referenced - * and will be removed during the next _l3_acd_data_prune(). We can ignore - * this entry. */ + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_USED: + goto handle_init; + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + if (state_change_mode != ACD_STATE_CHANGE_MODE_INIT_REAPPLY) return; - } - log_reason = "address initially already configured"; - goto handle_probing_acd_good; + goto handle_init; } + nm_assert_not_reached(); + return; - /* we are called at the end of _l3_acd_data_add_all(). We updated the list of a - * all tracked IP addresses before we actually collect the addresses that are - * ready. We don't do regular handling of ACD states at this point, however, - * we check whether ACD for new elements is disabled entirely, so we can signal - * the address are ready right away (without going through another hop). */ - - if (acd_data->acd_state != ACD_STATE_INIT) { - /* this element is not new and we don't perform the quick-check. */ +handle_init: + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_FALSE, + &acd_timeout_msec, + &acd_defend_type) + <= 0u) { + /* the acd_data has no active trackers. It will soon be pruned. */ return; } - any_no_timeout = FALSE; - c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) { - /* There should be no dirty trackers, because the element is in init-state. */ - nm_assert(!acd_track->acd_dirty); - if (acd_track->acd_timeout_msec <= 0) { - /* ACD for this element is disabled. We can process is right away. */ - any_no_timeout = TRUE; - break; + if (acd_timeout_msec == 0u) + log_reason = "acd disabled by configuration"; + else if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) + log_reason = "address already configured"; + else { + if (state_change_mode == ACD_STATE_CHANGE_MODE_INIT_REAPPLY) { + /* during a reapply, we forget all the state and start from scratch. */ + _LOGT_acd(acd_data, "reset state for reapply"); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_INIT, FALSE); } - } - if (!any_no_timeout) { - /* there are elements that request the address, but they all specify - * an ACD timeout. We cannot progress the state. */ return; } - /* ACD is disabled, we can artificially moving the state further to - * ACD_STATE_PROBE_DONE and configure the address right away. This avoids - * that we go through another hop. - */ - log_reason = "ACD disabled by configuration from the start"; - goto handle_probing_acd_good; - } + _LOGT_acd(acd_data, + "%s probing (%s, during pre-check)", + acd_data->info.state == NM_L3_ACD_ADDR_STATE_INIT ? "skip" : "cancel", + log_reason); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->acd_defend_type_desired = acd_defend_type; + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_READY, FALSE); + return; case ACD_STATE_CHANGE_MODE_POST_COMMIT: - acd_data->initializing = FALSE; - goto handle_post_commit; + + if (acd_data->track_infos_changed) { + acd_data->track_infos_changed = FALSE; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + } + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + log_reason = "address already configured"; + goto handle_probing_done; + } + + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) + return; + + /* we just did a commit of the IP configuration and now visit all ACD states + * and kick off the necessary actions... */ + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_TRUE, + &acd_timeout_msec, + &acd_defend_type) + <= 0) + nm_assert_not_reached(); + + acd_data->acd_defend_type_desired = acd_defend_type; + + if (acd_timeout_msec <= 0) { + log_reason = "acd disabled by configuration"; + goto handle_probing_done; + } + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert(!acd_data->nacd_probe); + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data); + log_reason = "initial post-commit"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_PROBING: + { + gint64 old_expiry_msec; + gint64 new_expiry_msec; + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + new_expiry_msec = (*p_now_msec) + acd_timeout_msec; + old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec; + + if (!acd_data->nacd_probe) { + /* we are currently waiting for restarting a probe. At this point, at most we have + * to adjust the timeout/timestamp and let the regular timeouts handle this. */ + + if (new_expiry_msec >= old_expiry_msec) { + /* the running timeout expires before the new timeout. We don't update the timestamp/timeout, + * because we don't want to prolong the overall probing time. */ + return; + } + /* update the timers after out timeout got reduced. Also, reschedule the timeout + * so that it expires immediately. */ + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + _l3_acd_data_timeout_schedule(acd_data, 0); + return; + } + + if (new_expiry_msec >= old_expiry_msec) { + /* we already have ACD running with a timeout that expires before the requested one. There + * is nothing to do at this time. */ + return; + } + + /* the timeout got reduced. We try to restart the probe. */ + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + log_reason = "post-commit timeout update"; + goto handle_start_probing; + } + + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + /* we are done for now. We however scheduled a timeout to restart. This + * will be handled with the ACD_STATE_CHANGE_MODE_TIMEOUT event. */ + return; + + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + nm_assert_not_reached(); + return; case ACD_STATE_CHANGE_MODE_TIMEOUT: - { - if (acd_data->acd_state == ACD_STATE_PROBING && !acd_data->nacd_probe) { - const char *failure_reason; - gboolean acd_not_supported; - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + + case NM_L3_ACD_ADDR_STATE_PROBING: + if (acd_data->nacd_probe) { + /* we are already probing. There is nothing to do for this timeout. */ + return; + } + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC - >= now_msec) { + >= (*p_now_msec)) { + /* hm. We failed to create a new probe too long. Something is really wrong + * internally, but let's ignore the issue and assume the address is good. What + * else would we do? Assume the address is USED? */ _LOGT_acd(acd_data, - "state: probe-good (waiting for creating probe timed out. Assume good)"); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; + "probe-good (waiting for creating probe timed out. Assume good)"); + goto handle_start_defending; } - /* try create a new probe. The timeout is always as originally requested. */ - acd_data->nacd_probe = - _l3_acd_nacd_instance_create_probe(self, - acd_data->addr, - acd_data->probing_timeout_msec, - acd_data, - &acd_not_supported, - &failure_reason); - if (acd_not_supported) { - nm_assert(!acd_data->nacd_probe); - _LOGT_acd( - acd_data, - "state: probe-good (interface does not support ACD anymore after timeout)"); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; - } + log_reason = "retry probing on timeout"; + goto handle_start_probing; - if (!acd_data->nacd_probe) { - _LOGT_acd(acd_data, - "state: probing not possible at this time (%s). Wait longer", - failure_reason); - _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec); - return; - } + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: - /* probing started (with the original timeout. Note that acd_data->probing_time*_msec - * no longer corresponds to the actual timeout of the nacd_probe. This is not a problem - * because at this point we only trust the internal timer from nacd_probe to get - * it right. Instead, we keep acd_data->probing_time*_msec unchanged, to remember when - * we originally wanted to start. */ - _LOGT_acd(acd_data, - "state: probing started (after retry, timeout %u msec)", - acd_data->probing_timeout_msec); - return; - } - - if (acd_data->acd_state == ACD_STATE_PROBE_DONE && !acd_data->probe_result) { - /* Probing is done, but previously we detected a conflict. After a restart, we retry to - * probe. */ nm_assert(!acd_data->nacd_probe); - nm_assert(!acd_data->announcing_failed_is_retrying); - _LOGT_acd(acd_data, "state: restart a new probe after previous conflict"); - acd_data->acd_state = ACD_STATE_INIT; - goto handle_post_commit; - } - - if (acd_data->acd_state == ACD_STATE_PROBE_DONE && acd_data->probe_result - && !acd_data->nacd_probe && acd_data->announcing_failed_is_retrying) { - /* Probing is done, but previously we failed to start announcing. Retry now. */ - nm_assert(!was_probing); - _LOGT_acd(acd_data, "state: retry announcing address"); - acd_data->announcing_failed_is_retrying = FALSE; - goto handle_probe_done; - } - - return; - } - - case ACD_STATE_CHANGE_MODE_NACD_READY: - if (acd_data->acd_state == ACD_STATE_PROBING) { - log_reason = "acd indicates ready"; - goto handle_probing_acd_good; - } - if (acd_data->acd_state == ACD_STATE_ANNOUNCING) { - _LOGT_acd(acd_data, "state: ready to start announcing"); - if (n_acd_probe_announce(acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0) + /* after a timeout, re-probe the address. This only happens if the caller + * does not deconfigure the address after USED/CONFLICT. But in that case, + * we eventually want to retry. */ + if (_acd_data_collect_tracks_data(acd_data, + NM_TERNARY_TRUE, + &acd_timeout_msec, + &acd_defend_type) + <= 0) nm_assert_not_reached(); + + acd_data->acd_defend_type_desired = acd_defend_type; + + if (acd_timeout_msec <= 0) { + log_reason = "acd disabled by configuration (restart after previous conflict)"; + goto handle_probing_done; + } + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + log_reason = "address already configured (restart after previous conflict)"; + goto handle_probing_done; + } + + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + acd_data->probing_timestamp_msec = (*p_now_msec); + acd_data->probing_timeout_msec = acd_timeout_msec; + if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_USED) + log_reason = "restart probing after previously used address"; + else + log_reason = "restart probing after previous conflict"; + goto handle_start_probing; + + case NM_L3_ACD_ADDR_STATE_READY: + nm_assert_not_reached(); + return; + + case NM_L3_ACD_ADDR_STATE_DEFENDING: + + nm_assert(!acd_data->nacd_probe); + _LOGT_acd(acd_data, "retry announcing address"); + goto handle_start_defending; + + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); return; } - /* nacd really shouldn't call us in this state. There is a bug somewhere. */ nm_assert_not_reached(); return; case ACD_STATE_CHANGE_MODE_NACD_USED: - { - gs_free char *str_to_free = NULL; + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING); + nm_assert(acd_data->nacd_probe); - nm_assert(acd_data->acd_state == ACD_STATE_PROBING); + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->last_conflict_addr = *sender_addr; + _l3_acd_data_state_set_full(self, + acd_data, + NM_L3_ACD_ADDR_STATE_USED, + TRUE, + "acd completed with address already in use by %s", + nm_ether_addr_to_string_a(sender_addr)); + + if (!acd_data->acd_data_timeout_source) + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC); + + if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) { + _LOGI("IPv4 address %s is used on network connected to interface %d%s%s%s from " + "host %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + self->priv.ifindex, + NM_PRINT_FMT_QUOTED(self->priv.plobj_next, + " (", + NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name, + ")", + ""), + nm_ether_addr_to_string_a(sender_addr)); + } + return; + + case ACD_STATE_CHANGE_MODE_NACD_DEFENDED: + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); _LOGT_acd(acd_data, - "state: probe-done bad (address already in use by %s)", - nm_utils_bin2hexstr_a(event->_acd_event_payload.sender, - event->_acd_event_payload.n_sender, - ':', - FALSE, - &str_to_free)); - acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = FALSE; - goto handle_probe_done; - } + "address %s defended from %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + /* we just log an info message. Nothing else to do. */ + return; + + case ACD_STATE_CHANGE_MODE_NACD_CONFLICT: + nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); + + _LOGT_acd(acd_data, + "address conflict for %s detected with %s", + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + + if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) { + _LOGW("IPv4 address collision detection sees conflict on interface %d%s%s%s for " + "address %s from host %s", + self->priv.ifindex, + NM_PRINT_FMT_QUOTED(self->priv.plobj_next, + " (", + NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name, + ")", + ""), + _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr), + nm_ether_addr_to_string_a(sender_addr)); + } + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + acd_data->last_conflict_addr = *sender_addr; + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_CONFLICT, TRUE); + if (!acd_data->acd_data_timeout_source) + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_CONFLICT_RESTART_MSEC); + return; + + case ACD_STATE_CHANGE_MODE_NACD_READY: + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_PROBING: + nm_assert(acd_data->nacd_probe); + /* we theoretically could re-use this probe for defending. But as we + * may not start defending right away, it makes it more complicated. */ + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + log_reason = "acd indicates ready"; + goto handle_probing_done; + case NM_L3_ACD_ADDR_STATE_DEFENDING: + nm_assert(!acd_data->acd_defend_type_is_active); + acd_data->acd_defend_type_is_active = TRUE; + _LOGT_acd(acd_data, + "start announcing (defend=%s) (after new probe ready)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + if (n_acd_probe_announce(acd_data->nacd_probe, + _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current)) + != 0) + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_INIT: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; + } + + nm_assert_not_reached(); + return; case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED: - /* the address is configured on the link. This means, ACD passed */ - log_reason = "address configured on link"; - goto handle_probing_acd_good; + + if (self->priv.p->commit_reentrant_count > 0) + return; + + _LOGT_acd(acd_data, "address was externally added"); + + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + log_reason = "address configured on link"; + goto handle_probing_done; + } + + nm_assert_not_reached(); + return; case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED: - /* The address got removed. Either we ourself removed it or it was removed externally. - * In either case, it's not clear what we should do about that, regardless in which - * ACD state we are, so ignore it. */ - _LOGT_acd(acd_data, "state: address was externally removed. Ignore"); + + if (self->priv.p->commit_reentrant_count > 0) + return; + + if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + /* this can happen, because there might still be the same address with different + * plen or peer_address. */ + return; + } + + _LOGT_acd(acd_data, "address was externally removed"); + + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, FALSE); return; case ACD_STATE_CHANGE_MODE_NACD_DOWN: - if (acd_data->acd_state < ACD_STATE_PROBE_DONE) { - /* we are probing, but the probe has a problem that the link went down. Maybe - * we need to restart. */ - - nm_assert(acd_data->acd_state == ACD_STATE_PROBING); - - if (!acd_data->nacd_probe) { - /* we are in probing state, but currently not really probing. A timer is - * running, and we will handle this situation that way. */ - return; - } - - /* We abort the probing, but we also schedule a timer to restart it. Let - * the regular re-start handling handle this. */ - _LOGT_acd(acd_data, - "state: interface-down. Probing aborted but we keep waiting to retry"); - acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); - _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec); - return; - } - - /* We already completed a probe and acted accordingly (by either configuring the address - * already or by rejecting it). We cannot (easily) re-evaluate what to do now. Should - * we later restart probing? But what about the decisions we already made?? - * Ignore the situation. */ - return; - case ACD_STATE_CHANGE_MODE_LINK_NOW_UP: - /* The interface just came up. */ - - if (acd_data->acd_state <= ACD_STATE_PROBING) { - nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL; - const char * failure_reason; - gboolean acd_not_supported; - - /* the interface was probing. We will restart the probe. */ - nm_assert(acd_data->acd_state == ACD_STATE_PROBING); - - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_PROBING: if (!acd_data->nacd_probe) { - /* We currently are waiting to restart probing. We don't handle the link-up - * event here, we only trigger a timeout right away. */ + /* we failed starting to probe before and have a timer running to + * restart. We don't do anything now, but let the timer handle it. + * This also implements some rate limiting for us. */ _LOGT_acd(acd_data, - "state: ignore link up event while we are waiting to start probing"); - _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE); + "ignore link %s event while we are waiting to start probing", + state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN ? "down" : "up"); return; } - if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_RESTART_TIME_MSEC >= now_msec) { - /* This probe was already started quite a while ago. We ignore the link-up event - * and let it complete regularly. This is to avoid restarting to probing indefinitely. */ - _LOGT_acd(acd_data, "state: ignore link up event for a probe started long ago"); + nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec); + + if (acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec + + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC + >= (*p_now_msec)) { + /* The probing already started quite a while ago. We ignore the link event + * and let the probe come to it's natural end. */ + _LOGT_acd(acd_data, "ignore link up event for a probe started long ago"); return; } - probe = _l3_acd_nacd_instance_create_probe(self, - acd_data->addr, - acd_data->probing_timeout_msec, - acd_data, - &acd_not_supported, - &failure_reason); - if (!probe) { - _LOGT_acd(acd_data, - "state: link up event would cause to retry probing, but creating a probe " - "failed (%s). Ignore and keep previous probe", - failure_reason); - return; - } + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); + if (state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN) + log_reason = "restart probing after down event"; + else + log_reason = "restart probing after link up"; + goto handle_start_probing; - NM_SWAP(&probe, &acd_data->nacd_probe); - - /* We just restarted probing. Note that we don't touch the original acd_data->probing_time*_msec - * times, otherwise a repeated link up/down cycle could extend the probing indefinitely. - * - * This is despite the new probe just started counting now. So, at this point, the - * timestamp/timeout of acd_data no longer corresponds to the internal timestamp of - * acd_data->nacd_probe. But since we don't run our own timer against the internal timer of - * acd_data->nacd_probe, that is not a problem. - */ - _LOGT_acd(acd_data, - "state: probing restarted (after link up, new timeout %u msec)", - acd_data->probing_timeout_msec); + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + /* if the link was down/came up, it's no clear what we should do about these + * cases. Ignore the event. */ return; } - - /* we are already done with the ACD state. Bringing up an interface has - * no further consequence w.r.t. the ACD state. */ + nm_assert_not_reached(); return; case ACD_STATE_CHANGE_MODE_INSTANCE_RESET: - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - if (acd_data->acd_state <= ACD_STATE_PROBING) { - nm_assert(acd_data->acd_state == ACD_STATE_PROBING); - _LOGT_acd( - acd_data, - "state: n-acd instance reset. Trigger a restart of the probing (was %sprobing)", - acd_data->nacd_probe ? "" : "not"); - /* Just destroy the current probe (if any) and retrigger a restart right away. */ + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + nm_assert_not_reached(); + return; + case NM_L3_ACD_ADDR_STATE_PROBING: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + + if (!acd_data->nacd_probe) { + /* we failed starting to probe before and have a timer running to + * restart. We don't do anything now, but let the timer handle it. + * This also implements some rate limiting for us. */ + _LOGT_acd(acd_data, + "n-acd instance reset. Ignore event while restarting %s", + (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing" + : "defending"); + return; + } + + _LOGT_acd(acd_data, + "n-acd instance reset. Trigger a restart of the %s", + (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing" + : "defending"); acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); - _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE); + _l3_acd_data_timeout_schedule(acd_data, 0); + return; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_USED: + case NM_L3_ACD_ADDR_STATE_CONFLICT: + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert(!acd_data->nacd_probe); return; } - - if (acd_data->probe_result) { - _LOGT_acd(acd_data, "state: n-acd instance reset. Restart announcing"); - } else { - _LOGT_acd(acd_data, - "state: n-acd instance reset. Reprobe the address that conflicted before"); - } - acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE); + nm_assert_not_reached(); return; } nm_assert_not_reached(); return; -handle_post_commit: - /* we just did a commit of the IP configuration and now visit all ACD states - * and kick off the necessary actions... */ - if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->addr)) { - log_reason = "address already configured"; - goto handle_probing_acd_good; - } - if (_acd_data_collect_tracks_data(acd_data, TRUE, NM_TERNARY_DEFAULT, &acd_timeout_msec) <= 0) - nm_assert_not_reached(); - if (acd_timeout_msec <= 0) { - log_reason = "ACD disabled by configuration"; - goto handle_probing_acd_good; - } - - switch (acd_data->acd_state) { - case ACD_STATE_INIT: - { - const char *failure_reason; - gboolean acd_not_supported; - NAcdProbe * probe; - - nm_assert(!acd_data->nacd_probe); - - probe = _l3_acd_nacd_instance_create_probe(self, - acd_data->addr, - acd_timeout_msec, - acd_data, - &acd_not_supported, - &failure_reason); - if (acd_not_supported) { - nm_assert(!probe); - _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD)"); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; - } - - if (!probe) { - _LOGT_acd(acd_data, - "state: probing currently not possible (timeout %u msec; %s)", - acd_timeout_msec, - failure_reason); - acd_data->acd_state = ACD_STATE_PROBING; - acd_data->probing_timeout_msec = acd_timeout_msec; - acd_data->probing_timestamp_msec = - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec); - return; - } - - _LOGT_acd(acd_data, "state: start probing (timeout %u msec)", acd_timeout_msec); - acd_data->acd_state = ACD_STATE_PROBING; - acd_data->nacd_probe = probe; - acd_data->probing_timeout_msec = acd_timeout_msec; - acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - return; - } - - case ACD_STATE_PROBING: - { - nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL; +handle_start_probing: + if (TRUE) { + const NML3AcdAddrState orig_state = acd_data->info.state; + nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL; const char * failure_reason; gboolean acd_not_supported; - gint64 old_expiry_msec; - gint64 new_expiry_msec; - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + nm_assert(NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_USED, + NM_L3_ACD_ADDR_STATE_CONFLICT)); - new_expiry_msec = now_msec + acd_timeout_msec; - old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec; + /* note that we reach this line also during a ACD_STATE_CHANGE_MODE_TIMEOUT, when + * or when we restart the probing (with a new timeout). In all cases, we still + * give the original timeout (acd_data->probing_timeout_msec), and not the remaining + * time. That means, the probing step might take longer then originally planned + * (e.g. if we initially cannot start probing right away). */ - if (!acd_data->nacd_probe) { - /* we are currently waiting for restarting a probe. At this point, at most we have - * to adjust the timeout/timestamp and let the regular timeouts handle this. */ - - if (new_expiry_msec >= old_expiry_msec) { - /* the running timeout expires before the new timeout. We don't update the timestamp/timerout, - * because we don't want to prolong the overall probing time. */ - return; - } - /* update the timers after out timeout got reduced. Also, reschedule the timeout - * so that it expires immediately. */ - acd_data->probing_timestamp_msec = now_msec; - acd_data->probing_timeout_msec = acd_timeout_msec; - _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE); - return; - } - - if (new_expiry_msec >= old_expiry_msec) { - /* we already have ACD running with a timeout that expires before the requested one. There - * is nothing to do at this time. */ - return; - } - - /* the timeout got reduced. We try to restart the probe. */ probe = _l3_acd_nacd_instance_create_probe(self, - acd_data->addr, - acd_timeout_msec, + acd_data->info.addr, + acd_data->probing_timeout_msec, acd_data, &acd_not_supported, &failure_reason); @@ -2062,143 +2341,144 @@ handle_post_commit: if (acd_not_supported) { nm_assert(!acd_data->nacd_probe); - _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD anymore)"); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; + _LOGT_acd(acd_data, + "probe-good (interface does not support acd%s, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" + : (state_change_mode != ACD_STATE_CHANGE_MODE_TIMEOUT) + ? " anymore" + : " anymore after timeout", + log_reason); + goto handle_start_defending; } + _l3_acd_data_state_set(self, + acd_data, + NM_L3_ACD_ADDR_STATE_PROBING, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT)); + if (!acd_data->nacd_probe) { _LOGT_acd(acd_data, - "state: probing currently still not possible (timeout %u msec; %s)", - acd_timeout_msec, - failure_reason); - acd_data->acd_state = ACD_STATE_PROBING; - acd_data->probing_timeout_msec = acd_timeout_msec; - acd_data->probing_timestamp_msec = now_msec; - _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec); + "probing currently %snot possible (timeout %u msec; %s, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still", + acd_data->probing_timeout_msec, + failure_reason, + log_reason); + _l3_acd_data_timeout_schedule_probing_restart(acd_data, (*p_now_msec)); return; } - /* We update the timestamps (after also restarting the probe). - * - * Note that we only reduced the overall expiry. */ - acd_data->probing_timestamp_msec = now_msec; - acd_data->probing_timeout_msec = acd_timeout_msec; - _LOGT_acd(acd_data, "state: restart probing (timeout %u msec)", acd_timeout_msec); + _LOGT_acd(acd_data, + "%sstart probing (timeout %u msec, %s)", + orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "re", + acd_data->probing_timeout_msec, + log_reason); return; } - case ACD_STATE_PROBE_DONE: - case ACD_STATE_ANNOUNCING: - goto handle_probe_done; - } - nm_assert_not_reached(); - return; - -handle_probing_acd_good: - switch (acd_data->acd_state) { - case ACD_STATE_INIT: - _LOGT_acd(acd_data, "state: probe-done good (%s, initializing)", log_reason); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; - case ACD_STATE_PROBING: - _LOGT_acd(acd_data, "state: probe-done good (%s, probing done)", log_reason); +handle_probing_done: + switch (acd_data->info.state) { + case NM_L3_ACD_ADDR_STATE_INIT: + _LOGT_acd(acd_data, "probe-done good (%s, initializing)", log_reason); + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_PROBING: + _LOGT_acd(acd_data, "probe-done good (%s, probing done)", log_reason); if (state_change_mode != ACD_STATE_CHANGE_MODE_NACD_READY) acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); - acd_data->acd_state = ACD_STATE_PROBE_DONE; - acd_data->probe_result = TRUE; - goto handle_probe_done; - case ACD_STATE_PROBE_DONE: - if (!acd_data->probe_result) { - nm_assert(!acd_data->nacd_probe); - _LOGT_acd(acd_data, "state: probe-done good (%s, after probe failed)", log_reason); - acd_data->probe_result = TRUE; - } - goto handle_probe_done; - case ACD_STATE_ANNOUNCING: - nm_assert(acd_data->probe_result); - goto handle_probe_done; + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_USED: + _LOGT_acd(acd_data, "probe-done good (%s, after probe failed)", log_reason); + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_READY: + case NM_L3_ACD_ADDR_STATE_DEFENDING: + goto handle_start_defending; + case NM_L3_ACD_ADDR_STATE_CONFLICT: + return; + case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED: + nm_assert_not_reached(); + return; } nm_assert_not_reached(); return; -handle_probe_done: - nm_assert(NM_IN_SET(acd_data->acd_state, ACD_STATE_PROBE_DONE, ACD_STATE_ANNOUNCING)); - - if (acd_data->initializing) - return; - - if (acd_data->acd_state >= ACD_STATE_ANNOUNCING) { - nm_assert(acd_data->nacd_probe); - nm_assert(acd_data->probe_result); - return; - } - - if (!acd_data->probe_result) { - nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE); - nm_assert(!acd_data->nacd_probe); - /* we just completed probing with negative result. - * Emit a signal, but also reschedule a timer to restart. */ - if (was_probing) { - _LOGT_acd(acd_data, "state: acd probe failed; signal failure"); - acd_data->probing_timestamp_msec = - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - _l3_acd_data_timeout_schedule_probing_full_restart(acd_data, now_msec); +handle_start_defending: + if (!_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) { + if (acd_data->info.state != NM_L3_ACD_ADDR_STATE_READY) { + _l3_acd_data_state_set_full(self, + acd_data, + NM_L3_ACD_ADDR_STATE_READY, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT), + "probe is ready, waiting for address to be configured"); } - _l3_acd_data_notify_acd_completed_queue(self, acd_data); return; } - if (was_probing && acd_data->probe_result) { - /* probing just completed. Schedule handling the change. */ - _LOGT_acd(acd_data, "state: acd probe succeed"); - _l3_acd_data_notify_acd_completed_queue(self, acd_data); - if (state_change_mode != ACD_STATE_CHANGE_MODE_POST_COMMIT) - _l3_changed_configs_set_dirty(self); - nm_l3cfg_commit_on_idle_schedule(self); + _l3_acd_data_state_set(self, + acd_data, + NM_L3_ACD_ADDR_STATE_DEFENDING, + !NM_IN_SET(state_change_mode, + ACD_STATE_CHANGE_MODE_INIT, + ACD_STATE_CHANGE_MODE_INIT_REAPPLY, + ACD_STATE_CHANGE_MODE_POST_COMMIT)); + + nm_assert(acd_data->acd_defend_type_desired > NM_L3_ACD_DEFEND_TYPE_NONE); + nm_assert(acd_data->acd_defend_type_desired <= NM_L3_ACD_DEFEND_TYPE_ALWAYS); + + if (acd_data->acd_defend_type_desired != acd_data->acd_defend_type_current) { + acd_data->acd_defend_type_current = acd_data->acd_defend_type_desired; + acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe); } if (!acd_data->nacd_probe) { const char *failure_reason; NAcdProbe * probe; - if (acd_data->announcing_failed_is_retrying) { + if (acd_data->acd_data_timeout_source) { /* we already failed to create a probe. We are ratelimited to retry, but * we have a timer pending... */ return; } probe = _l3_acd_nacd_instance_create_probe(self, - acd_data->addr, + acd_data->info.addr, 0, acd_data, NULL, &failure_reason); if (!probe) { - /* we failed to create a probe for announcing the address. We log a (rate limited) - * warning and start a timer to retry. */ - _LOGT_acd(acd_data, - "state: start announcing failed to create probe (%s)", - failure_reason); - acd_data->announcing_failed_is_retrying = TRUE; - acd_data->probing_timestamp_msec = - nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); - _l3_acd_data_timeout_schedule_announce_restart(acd_data, now_msec); + /* we failed to create a probe for announcing the address. We log a + * warning and start a timer to retry. This way (of having a timer pending) + * we also back off and are rate limited from retrying too frequently. */ + _LOGT_acd(acd_data, "start announcing failed to create probe (%s)", failure_reason); + _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC); return; } - _LOGT_acd(acd_data, "state: start announcing (with new probe)"); - acd_data->nacd_probe = probe; - acd_data->acd_state = ACD_STATE_ANNOUNCING; + _LOGT_acd(acd_data, + "start announcing (defend=%s) (probe created)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + acd_data->acd_defend_type_is_active = FALSE; + acd_data->nacd_probe = probe; return; } - if (acd_data->acd_state == ACD_STATE_PROBE_DONE) { - _LOGT_acd(acd_data, "state: start announcing (with existing probe)"); - acd_data->acd_state = ACD_STATE_ANNOUNCING; - if (n_acd_probe_announce(acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0) + if (!acd_data->acd_defend_type_is_active) { + acd_data->acd_defend_type_is_active = TRUE; + _LOGT_acd(acd_data, + "start announcing (defend=%s) (with existing probe)", + _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current, + sbuf256, + sizeof(sbuf256))); + if (n_acd_probe_announce(acd_data->nacd_probe, + _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current)) + != 0) nm_assert_not_reached(); return; } @@ -2207,28 +2487,47 @@ handle_probe_done: static void _l3_acd_data_process_changes(NML3Cfg *self) { - gboolean acd_is_announcing = FALSE; - gboolean acd_is_pending = FALSE; + gboolean acd_is_pending = FALSE; + gboolean acd_busy = FALSE; AcdData *acd_data; + gint64 now_msec = 0; _l3_acd_data_prune(self, FALSE); c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) { - _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_POST_COMMIT, NULL); - if (acd_data->acd_state < ACD_STATE_PROBE_DONE) + _l3_acd_data_state_change(self, + acd_data, + ACD_STATE_CHANGE_MODE_POST_COMMIT, + NULL, + &now_msec); + if (acd_data->info.state <= NM_L3_ACD_ADDR_STATE_PROBING) acd_is_pending = TRUE; - else if (acd_data->acd_state >= ACD_STATE_ANNOUNCING - || (acd_data->acd_state >= ACD_STATE_PROBE_DONE && acd_data->probe_result)) - acd_is_announcing = TRUE; + if (acd_data->nacd_probe) + acd_busy = TRUE; } - self->priv.p->acd_is_pending = acd_is_pending; - self->priv.p->acd_is_announcing = acd_is_announcing; + self->priv.p->acd_is_pending = acd_is_pending; - if (!acd_is_pending && !acd_is_announcing) + if (!acd_busy) _l3_acd_nacd_instance_reset(self, NM_TERNARY_DEFAULT, FALSE); - _l3_acd_data_notify_acd_completed_all(self); + _nm_l3cfg_emit_signal_notify_acd_event_all(self); +} + +/*****************************************************************************/ + +const NML3AcdAddrInfo * +nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr) +{ + AcdData *acd_data; + + nm_assert(NM_IS_L3CFG(self)); + + acd_data = _l3_acd_data_find(self, addr); + if (!acd_data) + return NULL; + + return &acd_data->info; } /*****************************************************************************/ @@ -2240,7 +2539,7 @@ _l3_commit_on_idle_cb(gpointer user_data) nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); - _LOGT("platform commit on idle"); + _LOGT("commit on idle"); _l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE); return G_SOURCE_REMOVE; } @@ -2253,6 +2552,7 @@ nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self) if (self->priv.p->commit_on_idle_source) return; + _LOGT("commit on idle (scheduled)"); self->priv.p->commit_on_idle_source = nm_g_idle_source_new(G_PRIORITY_DEFAULT, _l3_commit_on_idle_cb, self, NULL); g_source_attach(self->priv.p->commit_on_idle_source, NULL); @@ -2277,7 +2577,7 @@ _l3_config_datas_find_next(GArray * l3_config_datas, for (i = start_idx; i < l3_config_datas->len; i++) { const L3ConfigData *l3_config_data = _l3_config_datas_at(l3_config_datas, i); - if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag) + if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag_confdata) && NM_IN_SET(needle_l3cd, NULL, l3_config_data->l3cd)) return i; } @@ -2296,54 +2596,15 @@ _l3_config_datas_get_sorted_cmp(gconstpointer p_a, gconstpointer p_b, gpointer u /* we sort the entries with higher priority (more important, lower numerical value) * first. */ - NM_CMP_FIELD(a, b, priority); + NM_CMP_FIELD(a, b, priority_confdata); /* if the priority is not unique, we sort them in the order they were added, * with the oldest first (lower numerical value). */ - NM_CMP_FIELD(a, b, pseudo_timestamp); + NM_CMP_FIELD(a, b, pseudo_timestamp_confdata); return nm_assert_unreachable_val(0); } -#define _l3_config_datas_get_sorted_a(l3_config_datas, out_infos, out_infos_len, out_infos_free) \ - G_STMT_START \ - { \ - GArray *const _l3_config_datas = (l3_config_datas); \ - const L3ConfigData *const **const _out_infos = (out_infos); \ - guint *const _out_infos_len = (out_infos_len); \ - const L3ConfigData ***const _out_infos_free = (out_infos_free); \ - gs_free const L3ConfigData **_infos_free = NULL; \ - const L3ConfigData ** _infos; \ - guint _l3_config_datas_len; \ - guint _i; \ - \ - _l3_config_datas_len = nm_g_array_len(_l3_config_datas); \ - \ - if (_l3_config_datas_len == 0) \ - _infos = NULL; \ - else if (_l3_config_datas_len < 300 / sizeof(_infos[0])) \ - _infos = g_alloca(_l3_config_datas_len * sizeof(_infos[0])); \ - else { \ - _infos_free = g_new(const L3ConfigData *, _l3_config_datas_len); \ - _infos = _infos_free; \ - } \ - for (_i = 0; _i < _l3_config_datas_len; _i++) \ - _infos[_i] = _l3_config_datas_at(_l3_config_datas, _i); \ - \ - if (_l3_config_datas_len > 1) { \ - g_qsort_with_data(_infos, \ - _l3_config_datas_len, \ - sizeof(_infos[0]), \ - _l3_config_datas_get_sorted_cmp, \ - NULL); \ - } \ - \ - *_out_infos = _infos; \ - *_out_infos_len = _l3_config_datas_len; \ - *_out_infos_free = g_steal_pointer(&_infos_free); \ - } \ - G_STMT_END - static void _l3_config_datas_remove_index_fast(GArray *arr, guint idx) { @@ -2378,7 +2639,7 @@ nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty) if (idx < 0) return; - _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty = dirty; + _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata = dirty; idx++; } } @@ -2395,6 +2656,7 @@ nm_l3cfg_add_config(NML3Cfg * self, guint32 default_route_metric_6, guint32 default_route_penalty_4, guint32 default_route_penalty_6, + NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, NML3ConfigMergeFlags merge_flags) { @@ -2406,6 +2668,11 @@ nm_l3cfg_add_config(NML3Cfg * self, nm_assert(tag); nm_assert(l3cd); nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex); + nm_assert(acd_timeout_msec < ACD_MAX_TIMEOUT_MSEC); + nm_assert(NM_IN_SET(acd_defend_type, + NM_L3_ACD_DEFEND_TYPE_NEVER, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_L3_ACD_DEFEND_TYPE_ALWAYS)); nm_assert(default_route_metric_6 != 0u); /* IPv6 default route metric cannot be zero. */ @@ -2451,29 +2718,30 @@ nm_l3cfg_add_config(NML3Cfg * self, if (idx < 0) { l3_config_data = nm_g_array_append_new(self->priv.p->l3_config_datas, L3ConfigData); *l3_config_data = (L3ConfigData){ - .tag = tag, - .l3cd = nm_l3_config_data_ref_and_seal(l3cd), - .merge_flags = merge_flags, - .default_route_table_4 = default_route_table_4, - .default_route_table_6 = default_route_table_6, - .default_route_metric_4 = default_route_metric_4, - .default_route_metric_6 = default_route_metric_6, - .default_route_penalty_4 = default_route_penalty_4, - .default_route_penalty_6 = default_route_penalty_6, - .acd_timeout_msec = acd_timeout_msec, - .priority = priority, - .pseudo_timestamp = ++self->priv.p->pseudo_timestamp_counter, - .dirty = FALSE, + .tag_confdata = tag, + .l3cd = nm_l3_config_data_ref_and_seal(l3cd), + .merge_flags = merge_flags, + .default_route_table_4 = default_route_table_4, + .default_route_table_6 = default_route_table_6, + .default_route_metric_4 = default_route_metric_4, + .default_route_metric_6 = default_route_metric_6, + .default_route_penalty_4 = default_route_penalty_4, + .default_route_penalty_6 = default_route_penalty_6, + .acd_defend_type_confdata = acd_defend_type, + .acd_timeout_msec_confdata = acd_timeout_msec, + .priority_confdata = priority, + .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter, + .dirty_confdata = FALSE, }; changed = TRUE; } else { - l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx); - l3_config_data->dirty = FALSE; - nm_assert(l3_config_data->tag == tag); + l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx); + l3_config_data->dirty_confdata = FALSE; + nm_assert(l3_config_data->tag_confdata == tag); nm_assert(l3_config_data->l3cd == l3cd); - if (l3_config_data->priority != priority) { - l3_config_data->priority = priority; - changed = TRUE; + if (l3_config_data->priority_confdata != priority) { + l3_config_data->priority_confdata = priority; + changed = TRUE; } if (l3_config_data->merge_flags != merge_flags) { l3_config_data->merge_flags = merge_flags; @@ -2503,12 +2771,18 @@ nm_l3cfg_add_config(NML3Cfg * self, l3_config_data->default_route_penalty_6 = default_route_penalty_6; changed = TRUE; } - if (l3_config_data->acd_timeout_msec != acd_timeout_msec) { - l3_config_data->acd_timeout_msec = acd_timeout_msec; - changed = TRUE; + if (l3_config_data->acd_defend_type_confdata != acd_defend_type) { + l3_config_data->acd_defend_type_confdata = acd_defend_type; + changed = TRUE; + } + if (l3_config_data->acd_timeout_msec_confdata != acd_timeout_msec) { + l3_config_data->acd_timeout_msec_confdata = acd_timeout_msec; + changed = TRUE; } } + nm_assert(l3_config_data->acd_defend_type_confdata == acd_defend_type); + if (changed) _l3_changed_configs_set_dirty(self); @@ -2539,7 +2813,8 @@ _l3cfg_remove_config(NML3Cfg * self, if (idx < 0) break; - if (only_dirty && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty) { + if (only_dirty + && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata) { idx++; continue; } @@ -2593,6 +2868,7 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, NML3Cfg * self = hook_data->self; AcdData * acd_data; in_addr_t addr; + gboolean acd_bad = FALSE; nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT); @@ -2602,46 +2878,91 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; if (ACD_ADDR_SKIP(addr)) - return TRUE; + goto out; acd_data = _l3_acd_data_find(self, addr); - nm_assert(acd_data); + + if (!acd_data) { + /* we don't yet track an ACD state for this address. That can only + * happend during _l3cfg_update_combined_config() with !to_commit, + * where we didn't update the ACD state. + * + * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE) + * won't consider IPv4 addresses ready, that have no known ACD state yet. */ + nm_assert(self->priv.p->changed_configs_acd_state); + acd_bad = TRUE; + goto out; + } + nm_assert( _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag))); - return _acd_data_probe_result_is_good(acd_data); + if (!NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)) + acd_bad = TRUE; + +out: + *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; + return TRUE; } static void _l3cfg_update_combined_config(NML3Cfg * self, gboolean to_commit, + gboolean reapply, const NML3ConfigData **out_old /* transfer reference */, - gboolean * out_changed_configs, gboolean * out_changed_combined_l3cd) { - nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; - nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; - gs_free const L3ConfigData **l3_config_datas_free = NULL; - const L3ConfigData *const * l3_config_datas_sorted; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL; + gs_free const L3ConfigData **l3_config_datas_free = NULL; + const L3ConfigData ** l3_config_datas_arr; guint l3_config_datas_len; guint i; + gboolean merged_changed = FALSE; + gboolean commited_changed = FALSE; nm_assert(NM_IS_L3CFG(self)); - nm_assert(!out_old || !*out_old); - NM_SET_OUT(out_changed_configs, self->priv.changed_configs); + NM_SET_OUT(out_changed_combined_l3cd, FALSE); - if (!self->priv.changed_configs) - goto out; + if (!self->priv.p->changed_configs_configs) { + if (!self->priv.p->changed_configs_acd_state) + goto out; + if (!to_commit) { + /* since we are not going to commit, we don't care about the + * ACD state. */ + goto out; + } + } - self->priv.changed_configs = FALSE; + self->priv.p->changed_configs_configs = FALSE; - _l3_config_datas_get_sorted_a(self->priv.p->l3_config_datas, - &l3_config_datas_sorted, - &l3_config_datas_len, - &l3_config_datas_free); + l3_config_datas_len = nm_g_array_len(self->priv.p->l3_config_datas); + l3_config_datas_arr = nm_malloc_maybe_a(300, + l3_config_datas_len * sizeof(l3_config_datas_arr[0]), + &l3_config_datas_free); + for (i = 0; i < l3_config_datas_len; i++) + l3_config_datas_arr[i] = _l3_config_datas_at(self->priv.p->l3_config_datas, i); - _l3_acd_data_add_all(self, l3_config_datas_sorted, l3_config_datas_len); + if (l3_config_datas_len > 1) { + g_qsort_with_data(l3_config_datas_arr, + l3_config_datas_len, + sizeof(l3_config_datas_arr[0]), + _l3_config_datas_get_sorted_cmp, + NULL); + } + + if (!to_commit) { + /* we are not going to commit these changes. Hence, we don't update the + * ACD states, but we need to remember that we have to on the next commit. */ + self->priv.p->changed_configs_acd_state = TRUE; + } else { + _l3_acd_data_add_all(self, l3_config_datas_arr, l3_config_datas_len, reapply); + self->priv.p->changed_configs_acd_state = FALSE; + } if (l3_config_datas_len > 0) { L3ConfigMergeHookAddObjData hook_data = { @@ -2652,12 +2973,12 @@ _l3cfg_update_combined_config(NML3Cfg * self, self->priv.ifindex); for (i = 0; i < l3_config_datas_len; i++) { - const L3ConfigData *l3cd_data = l3_config_datas_sorted[i]; + const L3ConfigData *l3cd_data = l3_config_datas_arr[i]; if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD)) continue; - hook_data.tag = l3cd_data->tag; + hook_data.tag = l3cd_data->tag_confdata; nm_l3_config_data_merge(l3cd, l3cd_data->l3cd, l3cd_data->merge_flags, @@ -2679,24 +3000,43 @@ _l3cfg_update_combined_config(NML3Cfg * self, l3cd_old = g_steal_pointer(&self->priv.p->combined_l3cd_merged); self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd)); - + merged_changed = TRUE; if (!to_commit) { NM_SET_OUT(out_old, g_steal_pointer(&l3cd_old)); NM_SET_OUT(out_changed_combined_l3cd, TRUE); - _LOGT("desired IP configuration changed"); } out: if (to_commit && self->priv.p->combined_l3cd_commited != self->priv.p->combined_l3cd_merged) { - nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL; - l3cd_commited_old = g_steal_pointer(&self->priv.p->combined_l3cd_commited); self->priv.p->combined_l3cd_commited = nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged); - + commited_changed = TRUE; NM_SET_OUT(out_old, g_steal_pointer(&l3cd_commited_old)); NM_SET_OUT(out_changed_combined_l3cd, TRUE); - _LOGT("desired IP configuration changed for commit"); + } + + if ((merged_changed || commited_changed) && _LOGT_ENABLED()) { + char sbuf256[256]; + char sbuf30[30]; + + _LOGT("IP configuration changed (merged=%c%s, commited=%c%s)", + merged_changed ? '>' : '=', + NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_merged, sbuf256), + commited_changed ? '>' : '=', + NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_commited, sbuf30)); + + if (merged_changed) { + nm_l3_config_data_log(self->priv.p->combined_l3cd_merged, + NULL, + nm_sprintf_buf(sbuf256, + "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT + ",ifindex=%d]: ", + NM_HASH_OBFUSCATE_PTR(self), + nm_l3cfg_get_ifindex(self)), + LOGL_TRACE, + _NMLOG_DOMAIN); + } } } @@ -2881,10 +3221,13 @@ out_prune: /*****************************************************************************/ static gboolean -_l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) +_l3_commit_one(NML3Cfg * self, + int addr_family, + NML3CfgCommitType commit_type, + gboolean changed_combined_l3cd, + const NML3ConfigData *l3cd_old) { - const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); - nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + const gboolean IS_IPv4 = NM_IS_IPv4(addr_family); gs_unref_ptrarray GPtrArray *addresses = NULL; gs_unref_ptrarray GPtrArray *routes = NULL; gs_unref_ptrarray GPtrArray *addresses_prune = NULL; @@ -2892,8 +3235,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL; NMIPRouteTableSyncMode route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE; gboolean final_failure_for_temporary_not_available = FALSE; - gboolean changed_combined_l3cd; - gboolean changed_configs; char sbuf_commit_type[50]; gboolean success = TRUE; @@ -2909,8 +3250,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) nm_utils_addr_family_to_char(addr_family), _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type))); - _l3cfg_update_combined_config(self, TRUE, &l3cd_old, &changed_configs, &changed_combined_l3cd); - if (changed_combined_l3cd) { /* our combined configuration changed. We may track entries in externally_removed_objs_hash, * which are not longer to be considered by our configuration. We need to forget about them. */ @@ -2976,22 +3315,13 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) self->priv.ifindex, route_table_sync); } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) { - /* during update, we do a cross with the previous configuration. - * - * Of course, if an entry is both to be pruned and to be added, then - * the latter wins. So, this works just nicely. */ - if (l3cd_old) { - const NMDedupMultiHeadEntry *head_entry; - - head_entry = - nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)); - addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL); - - head_entry = nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)); - addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL); - } + addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]); + routes_prune = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]); } + nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses); + nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes); + /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */ /* 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_ip6_mtu(). */ @@ -3026,8 +3356,10 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type) static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) { - gboolean commit_type_detected = FALSE; - char sbuf_ct[30]; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + gboolean commit_type_detected = FALSE; + char sbuf_ct[30]; + gboolean changed_combined_l3cd; g_return_if_fail(NM_IS_L3CFG(self)); nm_assert(NM_IN_SET(commit_type, @@ -3036,6 +3368,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) NM_L3_CFG_COMMIT_TYPE_ASSUME, NM_L3_CFG_COMMIT_TYPE_UPDATE, NM_L3_CFG_COMMIT_TYPE_REAPPLY)); + nm_assert(self->priv.p->commit_reentrant_count == 0); switch (commit_type) { case NM_L3_CFG_COMMIT_TYPE_AUTO: @@ -3061,7 +3394,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) break; } - _LOGT("platform-commit %s%s%s", + _LOGT("commit %s%s%s", _l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)), commit_type_detected ? " (auto)" : "", is_idle ? " (idle handler)" : ""); @@ -3069,18 +3402,29 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) return; + self->priv.p->commit_reentrant_count++; + nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) _l3cfg_externally_removed_objs_drop(self); + _l3cfg_update_combined_config(self, + TRUE, + commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY, + &l3cd_old, + &changed_combined_l3cd); + /* FIXME(l3cfg): handle items currently not configurable in kernel. */ - _l3_commit_one(self, AF_INET, commit_type); - _l3_commit_one(self, AF_INET6, commit_type); + _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); _l3_acd_data_process_changes(self); + nm_assert(self->priv.p->commit_reentrant_count == 1); + self->priv.p->commit_reentrant_count--; + _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT); } @@ -3196,7 +3540,7 @@ nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited) if (get_commited) return self->priv.p->combined_l3cd_commited; - _l3cfg_update_combined_config(self, FALSE, NULL, NULL, NULL); + _l3cfg_update_combined_config(self, FALSE, FALSE, NULL, NULL); return self->priv.p->combined_l3cd_merged; } @@ -3290,7 +3634,7 @@ nm_l3cfg_init(NML3Cfg *self) self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate); c_list_init(&self->priv.p->acd_lst_head); - c_list_init(&self->priv.p->acd_notify_complete_lst_head); + c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); } @@ -3337,7 +3681,7 @@ finalize(GObject *object) _l3_acd_data_prune(self, TRUE); nm_assert(c_list_is_empty(&self->priv.p->acd_lst_head)); - nm_assert(c_list_is_empty(&self->priv.p->acd_notify_complete_lst_head)); + nm_assert(c_list_is_empty(&self->priv.p->acd_event_notify_lst_head)); nm_assert(nm_g_hash_table_size(self->priv.p->acd_lst_hash) == 0); nm_clear_pointer(&self->priv.p->acd_lst_hash, g_hash_table_unref); @@ -3345,6 +3689,11 @@ finalize(GObject *object) nm_clear_g_source_inst(&self->priv.p->nacd_source); nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry); + nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref); + nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref); + nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id); nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref); diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h index 9f957ae10..d89e2fa28 100644 --- a/src/nm-l3cfg.h +++ b/src/nm-l3cfg.h @@ -18,10 +18,47 @@ #define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify" +typedef enum _nm_packed { + NM_L3_ACD_DEFEND_TYPE_NONE, + NM_L3_ACD_DEFEND_TYPE_NEVER, + NM_L3_ACD_DEFEND_TYPE_ONCE, + NM_L3_ACD_DEFEND_TYPE_ALWAYS, +} NML3AcdDefendType; + +typedef enum _nm_packed { + NM_L3_ACD_ADDR_STATE_INIT, + NM_L3_ACD_ADDR_STATE_PROBING, + NM_L3_ACD_ADDR_STATE_USED, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING, + NM_L3_ACD_ADDR_STATE_CONFLICT, + NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, +} NML3AcdAddrState; + +typedef struct { + const NMPObject * obj; + const NML3ConfigData *l3cd; + gconstpointer tag; + + char _padding[sizeof(struct { + guint32 a; + NML3AcdDefendType b; + guint8 c; + })]; +} NML3AcdAddrTrackInfo; + +typedef struct { + in_addr_t addr; + guint n_track_infos; + NML3AcdAddrState state; + NML3Cfg * l3cfg; + const NML3AcdAddrTrackInfo *track_infos; +} NML3AcdAddrInfo; + typedef enum { NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, - NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, + NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, /* emitted at the end of nm_l3cfg_platform_commit(). */ NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, @@ -41,21 +78,12 @@ typedef enum { _NM_L3_CONFIG_NOTIFY_TYPE_NUM, } NML3ConfigNotifyType; -typedef struct { - const NMPObject * obj; - const NML3ConfigData *l3cd; - gconstpointer tag; -} NML3ConfigNotifyPayloadAcdFailedSource; - typedef struct { NML3ConfigNotifyType notify_type; union { struct { - in_addr_t addr; - guint sources_len; - bool probe_result : 1; - const NML3ConfigNotifyPayloadAcdFailedSource *sources; - } acd_completed; + NML3AcdAddrInfo info; + } acd_event; struct { const NMPObject * obj; @@ -79,7 +107,6 @@ struct _NML3Cfg { const NMPObject * plobj; const NMPObject * plobj_next; int ifindex; - bool changed_configs : 1; } priv; }; @@ -205,6 +232,7 @@ gboolean nm_l3cfg_add_config(NML3Cfg * self, guint32 default_route_metric_6, guint32 default_route_penalty_4, guint32 default_route_penalty_6, + NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, NML3ConfigMergeFlags merge_flags); @@ -250,6 +278,10 @@ void nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self); /*****************************************************************************/ +const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr); + +/*****************************************************************************/ + NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self); typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle; diff --git a/src/tests/test-l3cfg.c b/src/tests/test-l3cfg.c index d55dcfbca..3c37cd1c4 100644 --- a/src/tests/test-l3cfg.c +++ b/src/tests/test-l3cfg.c @@ -93,6 +93,9 @@ typedef enum { typedef struct { const TestFixture1 *f; + guint32 acd_timeout_msec_a; + NML3AcdDefendType acd_defend_type_a; + TestL3cfgNotifyType notify_type; guint post_commit_event_count; guint general_event_count; @@ -101,7 +104,7 @@ typedef struct { int cb_count; bool expected_probe_result : 1; } wait_for_acd_ready_1; - } notify_data; + } notify_result; } TestL3cfgData; static void @@ -112,7 +115,7 @@ _test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notif tdata->notify_type = notify_type; tdata->post_commit_event_count = 0; tdata->general_event_count = 0; - memset(&tdata->notify_data, 0, sizeof(tdata->notify_data)); + memset(&tdata->notify_result, 0, sizeof(tdata->notify_result)); } static void @@ -120,6 +123,8 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg, const NML3ConfigNotifyData *notify_data, TestL3cfgData * tdata) { + guint i; + g_assert(NM_IS_L3CFG(l3cfg)); g_assert(tdata); g_assert(notify_data); @@ -131,6 +136,18 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg, else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) { g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj)); g_assert(notify_data->platform_change.change_type != 0); + } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { + g_assert_cmpint(notify_data->acd_event.info.n_track_infos, >=, 1); + g_assert(notify_data->acd_event.info.track_infos); + for (i = 0; i < notify_data->acd_event.info.n_track_infos; i++) { + const NML3AcdAddrTrackInfo *ti = ¬ify_data->acd_event.info.track_infos[i]; + + nm_assert(NMP_OBJECT_GET_TYPE(ti->obj) == NMP_OBJECT_TYPE_IP4_ADDRESS); + nm_assert(NMP_OBJECT_CAST_IP4_ADDRESS(ti->obj)->address + == notify_data->acd_event.info.addr); + nm_assert(NM_IS_L3_CONFIG_DATA(ti->l3cd)); + nm_assert(ti->tag); + } } switch (tdata->notify_type) { @@ -150,11 +167,15 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg, case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT: tdata->post_commit_event_count++; return; - case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED: + case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: switch (tdata->f->test_idx) { case 2: - nmtst_assert_ip4_address(notify_data->acd_completed.addr, "192.167.133.45"); - g_assert(notify_data->acd_completed.probe_result); + case 3: + nmtst_assert_ip4_address(notify_data->acd_event.info.addr, "192.168.133.45"); + if (tdata->f->test_idx == 2) + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_DEFENDING); + else + g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING); g_assert(tdata->general_event_count == 0); tdata->general_event_count++; return; @@ -169,30 +190,46 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg, return; } case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1: + { + int num_acd_completed_events = 1; + if (NM_IN_SET(notify_data->notify_type, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE)) return; - if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED) { - g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 0); - tdata->notify_data.wait_for_acd_ready_1.cb_count++; + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) { + if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) { + g_assert(NM_IN_SET(notify_data->acd_event.info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)); + } else + g_assert_not_reached(); + + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, + <, + 2 * num_acd_completed_events); + tdata->notify_result.wait_for_acd_ready_1.cb_count++; return; } if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) { - g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 1); - tdata->notify_data.wait_for_acd_ready_1.cb_count++; + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, >, 0); + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, + ==, + 2 * num_acd_completed_events); + tdata->notify_result.wait_for_acd_ready_1.cb_count++; nmtstp_platform_ip_addresses_assert(tdata->f->platform, tdata->f->ifindex0, TRUE, TRUE, TRUE, - "192.167.133.45", + "192.168.133.45", "1:2:3:4::45"); return; } g_assert_not_reached(); return; } + } g_assert_not_reached(); } @@ -200,19 +237,20 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg, static void test_l3cfg(gconstpointer test_data) { - nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; + const int TEST_IDX = GPOINTER_TO_INT(test_data); + const guint32 ACD_TIMEOUT_BASE_MSEC = 1000; + nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; const TestFixture1 * f; NML3CfgCommitTypeHandle * commit_type_1; NML3CfgCommitTypeHandle * commit_type_2; - gs_unref_object NML3Cfg *l3cfg0 = NULL; - nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL; - guint32 acd_timeout_msec = 0; - TestL3cfgData tdata_stack = { + gs_unref_object NML3Cfg *l3cfg0 = NULL; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL; + TestL3cfgData tdata_stack = { .f = NULL, }; TestL3cfgData *const tdata = &tdata_stack; - _LOGD("test start (/l3cfg/%d)", GPOINTER_TO_INT(test_data)); + _LOGD("test start (/l3cfg/%d)", TEST_IDX); if (nmtst_test_quick()) { gs_free char *msg = @@ -223,10 +261,13 @@ test_l3cfg(gconstpointer test_data) return; } - f = _test_fixture_1_setup(&test_fixture, GPOINTER_TO_INT(test_data)); + f = _test_fixture_1_setup(&test_fixture, TEST_IDX); tdata->f = f; + tdata->acd_timeout_msec_a = NM_IN_SET(f->test_idx, 3) ? ACD_TIMEOUT_BASE_MSEC : 0u; + tdata->acd_defend_type_a = NM_L3_ACD_DEFEND_TYPE_NEVER; + l3cfg0 = nm_netns_access_l3cfg(f->netns, f->ifindex0); g_assert(NM_IS_L3CFG(l3cfg0)); @@ -256,8 +297,8 @@ test_l3cfg(gconstpointer test_data) nm_l3_config_data_add_address_4( l3cd, - NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.167.133.45"), - .peer_address = nmtst_inet4_from_string("192.167.133.45"), + NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.45"), + .peer_address = nmtst_inet4_from_string("192.168.133.45"), .plen = 24, )); nm_l3_config_data_add_address_6( @@ -272,7 +313,7 @@ test_l3cfg(gconstpointer test_data) } } - acd_timeout_msec = (f->test_idx == 3) ? 2000u : 0u; + nm_l3_config_data_log(l3cd_a, "l3cd_a", "platform-test: l3cd_a: ", LOGL_DEBUG, LOGD_PLATFORM); if (l3cd_a) { nm_l3cfg_add_config(l3cfg0, @@ -286,7 +327,8 @@ test_l3cfg(gconstpointer test_data) NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, 0, 0, - acd_timeout_msec, + tdata->acd_defend_type_a, + tdata->acd_timeout_msec_a, NM_L3_CONFIG_MERGE_FLAGS_NONE); } @@ -306,24 +348,30 @@ test_l3cfg(gconstpointer test_data) TRUE, TRUE, TRUE, - NM_IN_SET(f->test_idx, 2) ? "192.167.133.45" : NULL, + NM_IN_SET(f->test_idx, 2) ? "192.168.133.45" : NULL, NM_IN_SET(f->test_idx, 2, 3) ? "1:2:3:4::45" : NULL); if (NM_IN_SET(f->test_idx, 1, 2)) { _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL); _LOGT("poll 1 start"); - nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 5000u, FALSE); + nmtst_main_context_iterate_until(NULL, + nmtst_get_rand_uint32() % (ACD_TIMEOUT_BASE_MSEC * 5u), + FALSE); _LOGT("poll 1 end"); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); } if (NM_IN_SET(f->test_idx, 3)) { _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1); - tdata->notify_data.wait_for_acd_ready_1.expected_probe_result = TRUE; + tdata->notify_result.wait_for_acd_ready_1.expected_probe_result = TRUE; _LOGT("poll 2 start"); - nmtst_main_context_iterate_until(NULL, 2500u + (nmtst_get_rand_uint32() % 4000u), FALSE); + nmtst_main_context_iterate_until( + NULL, + ACD_TIMEOUT_BASE_MSEC * 3u / 2u + + (nmtst_get_rand_uint32() % (2u * ACD_TIMEOUT_BASE_MSEC)), + FALSE); _LOGT("poll 2 end"); - g_assert_cmpint(tdata->notify_data.wait_for_acd_ready_1.cb_count, ==, 2); + g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, ==, 3); _test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE); } @@ -340,7 +388,7 @@ test_l3cfg(gconstpointer test_data) if ((nmtst_get_rand_uint32() % 3) == 0) _test_fixture_1_teardown(&test_fixture); - _LOGD("test end (/l3cfg/%d)", f->test_idx); + _LOGD("test end (/l3cfg/%d)", TEST_IDX); } /*****************************************************************************/