diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | src/meson.build | 21 | ||||
-rw-r--r-- | src/nm-l3cfg.c | 1949 | ||||
-rw-r--r-- | src/nm-l3cfg.h | 22 |
4 files changed, 1886 insertions, 108 deletions
diff --git a/Makefile.am b/Makefile.am index 9808c25478..bfb448c29f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2470,7 +2470,9 @@ src_nm_iface_helper_LDADD = \ shared/nm-std-aux/libnm-std-aux.la \ src/libnm-systemd-core.la \ shared/systemd/libnm-systemd-shared.la \ + shared/libnacd.la \ shared/libndhcp4.la \ + shared/libcrbtree.la \ shared/libcsiphash.la \ $(GLIB_LIBS) \ $(LIBUDEV_LIBS) \ diff --git a/src/meson.build b/src/meson.build index 5fc03d8d59..667a6a2ce2 100644 --- a/src/meson.build +++ b/src/meson.build @@ -194,19 +194,16 @@ libnetwork_manager = static_library( link_with: nm_links, ) -deps = [ - daemon_nm_default_dep, - dl_dep, - libndp_dep, - libudev_dep, -] - -name = 'nm-iface-helper' - executable( - name, - name + '.c', - dependencies: deps, + 'nm-iface-helper', + 'nm-iface-helper.c', + dependencies: [ + daemon_nm_default_dep, + dl_dep, + libndp_dep, + libudev_dep, + libn_acd_dep, + ], c_args: daemon_c_flags, link_with: nm_links, link_args: ldflags_linker_script_binary, diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c index 09611b4474..01a7f00e51 100644 --- a/src/nm-l3cfg.c +++ b/src/nm-l3cfg.c @@ -4,12 +4,97 @@ #include "nm-l3cfg.h" +#include <net/if.h> + #include "platform/nm-platform.h" #include "platform/nmp-object.h" #include "nm-netns.h" +#include "n-acd/src/n-acd.h" /*****************************************************************************/ +#define ACD_SUPPORTED_ETH_ALEN ETH_ALEN +#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) + +static gboolean +ACD_ADDR_SKIP (in_addr_t addr) +{ + return addr == 0u; +} + +#define ACD_TRACK_FMT "["NM_HASH_OBFUSCATE_PTR_FMT","NM_HASH_OBFUSCATE_PTR_FMT","NM_HASH_OBFUSCATE_PTR_FMT"]" +#define ACD_TRACK_PTR2(l3cd, obj, tag) NM_HASH_OBFUSCATE_PTR (l3cd), NM_HASH_OBFUSCATE_PTR (obj), NM_HASH_OBFUSCATE_PTR (tag) +#define ACD_TRACK_PTR(acd_track) 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_READY, + ACD_STATE_CHANGE_MODE_NACD_USED, + ACD_STATE_CHANGE_MODE_NACD_DOWN, + + ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, + ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, + ACD_STATE_CHANGE_MODE_LINK_NOW_UP, + ACD_STATE_CHANGE_MODE_INSTANCE_RESET, + ACD_STATE_CHANGE_MODE_TIMEOUT, +} 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; +} AcdTrackData; + +typedef enum _nm_packed { + ACD_STATE_INIT, + ACD_STATE_PROBING, + ACD_STATE_PROBE_DONE, + ACD_STATE_ANNOUNCING, +} AcdState; + +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; + + CList acd_lst; + CList acd_track_lst_head; + + NML3Cfg *self; + + NAcdProbe *nacd_probe; + + GSource *acd_timeout_source; + gint64 acd_timeout_expiry_msec; + + /* see probing_timeout_msec. */ + gint64 probing_timestamp_msec; + + /* the ACD state for this address. */ + AcdState acd_state; + + /* 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; + + bool announcing_failed_is_retrying:1; +} AcdData; + typedef struct { const NML3ConfigData *l3cd; NML3ConfigMergeFlags merge_flags; @@ -23,6 +108,7 @@ typedef struct { gconstpointer tag; guint64 pseudo_timestamp; int priority; + guint32 acd_timeout_msec; bool dirty:1; } L3ConfigData; @@ -40,8 +126,6 @@ enum { static guint signals[LAST_SIGNAL] = { 0 }; -static GQuark signal_notify_quarks[_NM_L3_CONFIG_NOTIFY_TYPE_NUM]; - typedef struct _NML3CfgPrivate { GArray *property_emit_list; GArray *l3_config_datas; @@ -51,6 +135,19 @@ typedef struct _NML3CfgPrivate { GHashTable *externally_removed_objs_hash; + GHashTable *acd_ipv4_addresses_on_link; + + GHashTable *acd_lst_hash; + CList acd_lst_head; + + NAcd *nacd; + GSource *nacd_source; + + /* This is for rate-limiting the creation of nacd instance. */ + GSource *nacd_instance_ensure_retry; + + GSource *acd_ready_on_idle_source; + guint64 pseudo_timestamp_counter; union { @@ -70,6 +167,13 @@ typedef struct _NML3CfgPrivate { }; guint routes_temporary_not_available_id; + + bool acd_is_pending:1; + bool acd_is_announcing:1; + + bool nacd_acd_not_supported:1; + bool acd_ipv4_addresses_on_link_has:1; + } NML3CfgPrivate; struct _NML3CfgClass { @@ -91,10 +195,38 @@ G_DEFINE_TYPE (NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ } 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__)); \ + } G_STMT_END + /*****************************************************************************/ static void _property_emit_notify (NML3Cfg *self, NML3CfgPropertyEmitType emit_type); +static gboolean _acd_has_valid_link (const NMPObject *obj, + const guint8 **out_addr_bin, + gboolean *out_acd_not_supported); + +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); + +static AcdData *_l3_acd_data_find (NML3Cfg *self, + in_addr_t addr); + /*****************************************************************************/ static @@ -109,20 +241,103 @@ NM_UTILS_ENUM2STR_DEFINE (_l3_cfg_commit_type_to_string, NML3CfgCommitType, static void _l3cfg_emit_signal_notify (NML3Cfg *self, NML3ConfigNotifyType notify_type, - gpointer pay_load) + const NML3ConfigNotifyPayload *pay_load) { nm_assert (_NM_INT_NOT_NEGATIVE (notify_type)); - nm_assert (notify_type < G_N_ELEMENTS (signal_notify_quarks)); + nm_assert (notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM); g_signal_emit (self, signals[SIGNAL_NOTIFY], - signal_notify_quarks[notify_type], + 0, (int) notify_type, pay_load); } /*****************************************************************************/ +static void +_l3_acd_ipv4_addresses_on_link_update (NML3Cfg *self, + in_addr_t addr, + gboolean add /* or else remove */) +{ + AcdData *acd_data; + + acd_data = _l3_acd_data_find (self, addr); + + if (add) { + if (self->priv.p->acd_ipv4_addresses_on_link) + g_hash_table_add (self->priv.p->acd_ipv4_addresses_on_link, GUINT_TO_POINTER (addr)); + 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); + return; + } + + /* when we remove an IPv4 address from kernel, we cannot know whether the same address is still + * present (with a different prefix length or peer). So we cannot be sure whether we removed + * the only address, or whether more are still present. All we can do is forget about the + * 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); +} + +static gboolean +_l3_acd_ipv4_addresses_on_link_contains (NML3Cfg *self, + in_addr_t addr) +{ + if (!self->priv.p->acd_ipv4_addresses_on_link) { + if (self->priv.p->acd_ipv4_addresses_on_link_has) + return FALSE; + self->priv.p->acd_ipv4_addresses_on_link_has = TRUE; + self->priv.p->acd_ipv4_addresses_on_link = nm_platform_ip4_address_addr_to_hash (self->priv.platform, + self->priv.ifindex); + if (!self->priv.p->acd_ipv4_addresses_on_link) + return FALSE; + } + return g_hash_table_contains (self->priv.p->acd_ipv4_addresses_on_link, + GUINT_TO_POINTER (addr)); +} + +/*****************************************************************************/ + +static NAcdProbe * +_nm_n_acd_data_probe_new (NML3Cfg *self, + in_addr_t addr, + guint32 timeout_msec, + gpointer user_data) +{ + nm_auto (n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL; + NAcdProbe *probe; + int r; + + nm_assert (self); + + if (!self->priv.p->nacd) + return NULL; + + if (addr == 0) + return nm_assert_unreachable_val (NULL); + + r = n_acd_probe_config_new (&probe_config); + if (r) + return NULL; + + n_acd_probe_config_set_ip (probe_config, (struct in_addr) { addr }); + n_acd_probe_config_set_timeout (probe_config, timeout_msec); + + r = n_acd_probe (self->priv.p->nacd, &probe, probe_config); + if (r) + return NULL; + + n_acd_probe_set_userdata (probe, user_data); + return probe; +} + +/*****************************************************************************/ + static guint * _l3cfg_externally_removed_objs_counter (NML3Cfg *self, NMPObjectType obj_type) @@ -312,6 +527,13 @@ _load_link (NML3Cfg *self, gboolean initial) const NMPObject *obj; const char *ifname; const char *ifname_old; + gboolean nacd_changed; + gboolean nacd_new_valid; + gboolean nacd_old_valid; + const guint8 *nacd_old_addr; + const guint8 *nacd_new_addr; + gboolean nacd_link_now_up; + AcdData *acd_data; obj = nm_platform_link_get_obj (self->priv.platform, self->priv.ifindex, TRUE); @@ -322,6 +544,31 @@ _load_link (NML3Cfg *self, gboolean initial) obj_old = g_steal_pointer (&self->priv.pllink); self->priv.pllink = nmp_object_ref (obj); + if ( obj + && NM_FLAGS_HAS (NMP_OBJECT_CAST_LINK (obj)->n_ifi_flags, IFF_UP) + && ( !obj_old + || !NM_FLAGS_HAS (NMP_OBJECT_CAST_LINK (obj_old)->n_ifi_flags, IFF_UP))) + nacd_link_now_up = TRUE; + else + nacd_link_now_up = FALSE; + + nacd_changed = FALSE; + nacd_old_valid = _acd_has_valid_link (obj_old, &nacd_old_addr, NULL); + nacd_new_valid = _acd_has_valid_link (obj, &nacd_new_addr, NULL); + if (self->priv.p->nacd_instance_ensure_retry) { + if ( nacd_new_valid + && ( !nacd_old_valid + || memcmp (nacd_new_addr, nacd_old_addr, ACD_SUPPORTED_ETH_ALEN) == 0)) + nacd_changed = TRUE; + } else if (self->priv.p->nacd) { + if (!nacd_new_valid) + nacd_changed = TRUE; + else if (!nacd_old_valid) + nacd_changed = nm_assert_unreachable_val (TRUE); + else if (memcmp (nacd_old_addr, nacd_new_addr, ACD_SUPPORTED_ETH_ALEN) != 0) + nacd_changed = TRUE; + } else if (nacd_new_valid) + nacd_changed = TRUE; ifname_old = nmp_object_link_get_ifname (obj_old); ifname = nmp_object_link_get_ifname (self->priv.pllink); @@ -333,6 +580,18 @@ _load_link (NML3Cfg *self, gboolean initial) NM_PRINT_FMT_QUOTE_STRING (ifname), NM_PRINT_FMT_QUOTE_STRING (ifname_old)); } + + if (nacd_changed) { + if (!c_list_is_empty (&self->priv.p->acd_lst_head)) + _LOGT ("acd: link change causes restart of ACD"); + _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)) { + _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); + } + } } /*****************************************************************************/ @@ -357,6 +616,10 @@ _nm_l3cfg_notify_platform_change (NML3Cfg *self, switch (NMP_OBJECT_GET_TYPE (obj)) { case NMP_OBJECT_TYPE_IP4_ADDRESS: + _l3_acd_ipv4_addresses_on_link_update (self, + NMP_OBJECT_CAST_IP4_ADDRESS (obj)->address, + change_type != NM_PLATFORM_SIGNAL_REMOVED); + /* fall-through */ case NMP_OBJECT_TYPE_IP6_ADDRESS: case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: @@ -507,6 +770,1393 @@ nm_l3cfg_property_emit_unregister (NML3Cfg *self, /*****************************************************************************/ +gboolean +nm_l3cfg_get_acd_is_pending (NML3Cfg *self) +{ + g_return_val_if_fail (NM_IS_L3CFG (self), FALSE); + + return self->priv.p->acd_is_pending; +} + +static gboolean +_acd_track_data_is_not_dirty (const AcdTrackData *acd_track) +{ + return acd_track + && !acd_track->acd_dirty; +} + +static void +_acd_track_data_free (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)); + + n_acd_probe_free (acd_data->nacd_probe); + nm_clear_g_source_inst (&acd_data->acd_timeout_source); + c_list_unlink_stale (&acd_data->acd_lst); + 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) +{ + guint32 best_acd_timeout_msec = G_MAXUINT32; + AcdTrackData *acd_track; + guint n = 0; + + 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)) + continue; + } + n++; + if (best_acd_timeout_msec > acd_track->acd_timeout_msec) + best_acd_timeout_msec = acd_track->acd_timeout_msec; + } + + NM_SET_OUT (out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u); + return n; +} + +static AcdTrackData * +_acd_data_find_track (const AcdData *acd_data, + const NML3ConfigData *l3cd, + const NMPObject *obj, + gconstpointer tag) +{ + AcdTrackData *acd_track; + + 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 NULL; +} + +/*****************************************************************************/ + +static void +_l3_acd_platform_commit_acd_update (NML3Cfg *self) +{ + /* The idea with NML3Cfg is that multiple users (NMDevice/NMVpnConnection) share one layer 3 configuration + * and push their (portion of) IP configuration to it. That implies, that any user may issue nm_l3cfg_platform_commit() + * at any time, in order to say that a new configuration is ready. + * + * This makes the mechanism also suitable for internally triggering a commit when ACD completes. */ + _LOGT ("acd: acd update now"); + self->priv.changed_configs = TRUE; + nm_l3cfg_platform_commit (self, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + AF_INET, + NULL); +} + +static gboolean +_acd_has_valid_link (const NMPObject *obj, + const guint8 **out_addr_bin, + gboolean *out_acd_not_supported) +{ + const NMPlatformLink *link; + const guint8 *addr_bin; + gsize addr_len; + + if (!obj) { + NM_SET_OUT (out_acd_not_supported, FALSE); + return FALSE; + } + + 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) { + NM_SET_OUT (out_acd_not_supported, TRUE); + return FALSE; + } + + NM_SET_OUT (out_acd_not_supported, FALSE); + NM_SET_OUT (out_addr_bin, addr_bin); + return TRUE; +} + +static gboolean +_l3_acd_nacd_event (int fd, + GIOCondition condition, + gpointer user_data) +{ + NML3Cfg *self = user_data; + int r; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (self->priv.p->nacd); + + r = n_acd_dispatch (self->priv.p->nacd); + if (!NM_IN_SET (r, 0, N_ACD_E_PREEMPTED)) { + _LOGT ("acd: dispatch failed with error %d", r); + goto handle_failure; + } + + while (TRUE) { + AcdData *acd_data; + NAcdEvent *event; + + r = n_acd_pop_event (self->priv.p->nacd, &event); + if (r) { + _LOGT ("acd: pop-event failed with error %d", r); + goto handle_failure; + } + if (!event) + return G_SOURCE_CONTINUE; + +#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); + 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]; + + /* 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 (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.pllink, " (", NMP_OBJECT_CAST_LINK (self->priv.pllink)->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))); + } + 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); + break; + default: + _LOGT ("acd: unexpected event %u. Ignore", event->event); + break; + } + } + + nm_assert_not_reached (); + +handle_failure: + /* Something is seriously wrong with our nacd instance. We handle that by resetting the + * ACD instance. */ + _l3_acd_nacd_instance_reset (self, NM_TERNARY_TRUE, TRUE); + return G_SOURCE_CONTINUE; +} + +static gboolean +_l3_acd_nacd_instance_ensure_retry_cb (gpointer user_data) +{ + NML3Cfg *self = user_data; + + nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry); + + _l3_acd_platform_commit_acd_update (self); + + return G_SOURCE_REMOVE; +} + +static void +_l3_acd_nacd_instance_reset (NML3Cfg *self, + NMTernary start_timer, + gboolean acd_data_notify) +{ + nm_assert (NM_IS_L3CFG (self)); + + if (self->priv.p->nacd) { + _LOGT ("acd: clear nacd instance"); + self->priv.p->nacd = n_acd_unref (self->priv.p->nacd); + } + nm_clear_g_source_inst (&self->priv.p->nacd_source); + nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry); + + if (c_list_is_empty (&self->priv.p->acd_lst_head)) + start_timer = NM_TERNARY_DEFAULT; + + switch (start_timer) { + case NM_TERNARY_FALSE: + self->priv.p->nacd_instance_ensure_retry = nm_g_idle_source_new (G_PRIORITY_DEFAULT, + _l3_acd_nacd_instance_ensure_retry_cb, + self, + NULL); + g_source_attach (self->priv.p->nacd_instance_ensure_retry, NULL); + break; + case NM_TERNARY_TRUE: + self->priv.p->nacd_instance_ensure_retry = nm_g_timeout_source_new_seconds (ACD_ENSURE_RATELIMIT_MSEC / 1000u, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_instance_ensure_retry_cb, + self, + NULL); + g_source_attach (self->priv.p->nacd_instance_ensure_retry, NULL); + break; + case NM_TERNARY_DEFAULT: + break; + } + + if (acd_data_notify) { + AcdData *acd_data; + + 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); + } +} + +static NAcd * +_l3_acd_nacd_instance_ensure (NML3Cfg *self, + gboolean *out_acd_not_supported) +{ + nm_auto (n_acd_config_freep) NAcdConfig *config = NULL; + nm_auto (n_acd_unrefp) NAcd *nacd = NULL; + const guint8 *addr_bin; + gboolean acd_not_supported; + gboolean valid; + int fd; + int r; + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (self->priv.ifindex > 0); + +again: + if (G_LIKELY (self->priv.p->nacd)) { + NM_SET_OUT (out_acd_not_supported, FALSE); + return self->priv.p->nacd; + } + + if (self->priv.p->nacd_instance_ensure_retry) { + /* we just tried to create an instance and failed. We are rate-limited, + * don't yet try again. */ + NM_SET_OUT (out_acd_not_supported, self->priv.p->nacd_acd_not_supported); + return NULL; + } + + valid = _acd_has_valid_link (self->priv.pllink, &addr_bin, &acd_not_supported); + if (!valid) + goto failed_create_acd; + + nm_assert (!acd_not_supported); + + r = n_acd_config_new (&config); + if (r) + goto failed_create_acd; + + n_acd_config_set_ifindex (config, self->priv.ifindex); + n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET); + n_acd_config_set_mac (config, addr_bin, ACD_SUPPORTED_ETH_ALEN); + + r = n_acd_new (&nacd, config); + if (r) + goto failed_create_acd; + + self->priv.p->nacd = g_steal_pointer (&nacd); + + n_acd_get_fd (self->priv.p->nacd, &fd); + + self->priv.p->nacd_source = nm_g_unix_fd_source_new (fd, + G_IO_IN, + G_PRIORITY_DEFAULT, + _l3_acd_nacd_event, + self, + NULL); + nm_g_source_attach (self->priv.p->nacd_source, NULL); + + NM_SET_OUT (out_acd_not_supported, FALSE); + return self->priv.p->nacd; + +failed_create_acd: + /* is-internal-error means that we failed to create the NAcd instance. Most likely due + * to being unable to create a file descriptor. Anyway, something is seriously wrong here. + * + * Otherwise, the MAC address might just not be suitable (ETH_ALEN) or we might have + * not NMPlatformLink. In that case, it means the interface is currently not ready to + * do acd. */ + self->priv.p->nacd_acd_not_supported = acd_not_supported; + _l3_acd_nacd_instance_reset (self, NM_TERNARY_TRUE, FALSE); + goto again; +} + +static NAcdProbe * +_l3_acd_nacd_instance_create_probe (NML3Cfg *self, + in_addr_t addr, + guint32 timeout_msec, + gpointer user_data, + gboolean *out_acd_not_supported, + const char **out_failure_reason) +{ + gboolean acd_not_supported; + NAcdProbe *probe; + + if (!_l3_acd_nacd_instance_ensure (self, &acd_not_supported)) { + NM_SET_OUT (out_acd_not_supported, acd_not_supported); + if (acd_not_supported) + NM_SET_OUT (out_failure_reason, "interface not suitable for ACD"); + else + NM_SET_OUT (out_failure_reason, "failure to create nacd instance"); + return NULL; + } + + nm_assert (!acd_not_supported); + NM_SET_OUT (out_acd_not_supported, FALSE); + + probe = _nm_n_acd_data_probe_new (self, addr, timeout_msec, user_data); + if (!probe) { + NM_SET_OUT (out_failure_reason, "failure to create nacd probe"); + return NULL; + } + + NM_SET_OUT (out_failure_reason, NULL); + return probe; +} + +static void +_l3_acd_data_free_trackers (NML3Cfg *self, + AcdData *acd_data, + gboolean all /* or only dirty */) +{ + AcdTrackData *acd_track; + AcdTrackData *acd_track_safe; + + 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; + continue; + } + + _LOGT_acd (acd_data, + "untrack "ACD_TRACK_FMT"", + ACD_TRACK_PTR (acd_track)); + + _acd_track_data_free (acd_track); + } + + if (!c_list_is_empty (&acd_data->acd_track_lst_head)) + return; + + if (!g_hash_table_remove (self->priv.p->acd_lst_hash, acd_data)) + nm_assert_not_reached (); + _acd_data_free (acd_data); +} + +static void +_l3_acd_data_prune (NML3Cfg *self, + gboolean all /* or only dirty */) +{ + AcdData *acd_data_safe; + 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); +} + +static AcdData * +_l3_acd_data_find (NML3Cfg *self, + in_addr_t addr) +{ + return nm_g_hash_table_lookup (self->priv.p->acd_lst_hash, &addr); +} + +static void +_l3_acd_data_add (NML3Cfg *self, + const NML3ConfigData *l3cd, + const NMPObject *obj, + gconstpointer tag, + 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; + + if (ACD_ADDR_SKIP (addr)) + return; + + acd_data = _l3_acd_data_find (self, addr); + + if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC) { + /* we limit the maximum timeout. Otherwise we have to handle integer overflow + * when adding timeouts. */ + acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC; + } + + if (!acd_data) { + + if (G_UNLIKELY (!self->priv.p->acd_lst_hash)) { + G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET(AcdData, 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_state = ACD_STATE_INIT, + .probing_timestamp_msec = 0, + .probe_result = 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 { + acd_track = _acd_data_find_track (acd_data, + l3cd, + obj, + tag); + } + + if (!acd_track) { + acd_track = g_slice_new (AcdTrackData); + *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, + }; + 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"; + } else + track_mode = NULL; + } + + 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); + } +} + +static void +_l3_acd_data_add_all (NML3Cfg *self, + const L3ConfigData *const*infos, + guint infos_len) +{ + AcdData *acd_data; + guint i_info; + + /* First we add/track all the relevant addresses for ACD. */ + for (i_info = 0; i_info < infos_len; i_info++) { + const L3ConfigData *info = infos[i_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); + } + + /* 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); +} + +static gboolean +_l3_acd_ready_on_idle_cb (gpointer user_data) +{ + NML3Cfg *self = user_data; + + nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source); + + _LOGT ("acd: handle ACD changes on idle"); + + _l3_acd_platform_commit_acd_update (self); + + return G_SOURCE_REMOVE; +} + +static gboolean +_l3_acd_data_timeout_cb (gpointer user_data) +{ + AcdData *acd_data = user_data; + NML3Cfg *self = acd_data->self; + + 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); + return G_SOURCE_REMOVE; +} + +static void +_l3_acd_data_timeout_schedule (AcdData *acd_data, + gint64 now_msec, + gint64 expiry_msec, + gboolean msec_granularity) +{ + 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); +} + +static void +_l3_acd_data_timeout_schedule_probing_restart (AcdData *acd_data, + gint64 now_msec) +{ + gint64 expiry_msec; + gint64 timeout_msec; + + nm_assert (acd_data); + nm_assert (now_msec > 0); + nm_assert (acd_data->acd_state == ACD_STATE_PROBING); + nm_assert (!acd_data->nacd_probe); + nm_assert (acd_data->probing_timeout_msec > 0); + nm_assert (acd_data->probing_timestamp_msec > 0); + + expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_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; + } + + _l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec + timeout_msec, TRUE); +} + +static void +_l3_acd_data_timeout_schedule_probing_full_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_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_failed (NML3Cfg *self, + AcdData *acd_data, + gboolean force_all) +{ + gs_free NML3ConfigNotifyPayloadAcdFailedSource *sources_free = NULL; + NML3ConfigNotifyPayloadAcdFailedSource *sources = NULL; + NML3ConfigNotifyPayload payload; + 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); + + payload = (NML3ConfigNotifyPayload) { + .acd_failed = { + .addr = acd_data->addr, + .sources_len = n, + .sources = sources, + }, + }; + + _l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ACD_FAILED, &payload); + + for (i = 0; i < n; i++) { + nmp_object_unref (sources[i].obj); + nm_l3_config_data_unref (sources[i].l3cd); + } +} + +static void +_l3_acd_data_state_change (NML3Cfg *self, + AcdData *acd_data, + AcdStateChangeMode state_change_mode, + NAcdEvent *event) +{ + guint32 acd_timeout_msec; + gint64 now_msec = 0; + const char *log_reason; + gboolean was_probing; + + /* 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. + * + * Here, all the state for one address that we probe/announce is tracked in AcdData/acd_data. + * + * The acd_data has a list of AcdTrackData/acd_track_lst_head, which are configuration items + * that are interested in configuring this address. The "owners" of the ACD check for a certain + * address. + * + * We try to do all the state changes in this _l3_acd_data_state_change() function, where -- + * depending on the @state_change_mode -- we progress the state. + * + * It is complicated, but I think this is not really avoidable if you want to handle all + * the special things (state-changes) that can happen. + */ + + nm_assert (NM_IS_L3CFG (self)); + nm_assert (acd_data); + nm_assert (!c_list_is_empty (&acd_data->acd_track_lst_head)); + + was_probing = acd_data->acd_state < ACD_STATE_PROBE_DONE; + + switch (state_change_mode) { + + case ACD_STATE_CHANGE_MODE_INIT: { + AcdTrackData *acd_track; + gboolean any_no_timeout; + + /* 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. */ + + 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. */ + return; + } + log_reason = "address initially already configured"; + goto handle_probing_acd_good; + } + + /* 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. */ + 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 (!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. + */ + _LOGT_acd (acd_data, + "state: probe-done good (ACD disabled by configuration from the start)"); + acd_data->acd_state = ACD_STATE_PROBE_DONE; + acd_data->probe_result = TRUE; + return; + } + + case ACD_STATE_CHANGE_MODE_POST_COMMIT: + goto handle_post_commit; + + 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); + + if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC >= now_msec) { + _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; + } + + /* 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; + } + + 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; + } + + /* 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) + 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->acd_state == ACD_STATE_PROBING); + _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; + } + + 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; + + 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"); + 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); + + 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. */ + _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); + 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"); + 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; + } + + 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); + return; + } + + /* we are already done with the ACD state. Bringing up an interface has + * no further consequence w.r.t. the ACD state. */ + return; + + case ACD_STATE_CHANGE_MODE_INSTANCE_RESET: + 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. */ + acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe); + _l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec, TRUE); + 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); + break; + } + + 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; + 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); + + new_expiry_msec = 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/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, + &acd_not_supported, + &failure_reason); + NM_SWAP (&probe, &acd_data->nacd_probe); + + 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; + } + + 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); + 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); + 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, inializingbcwin)", + 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); + 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; + } + 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 (state_change_mode == ACD_STATE_CHANGE_MODE_INIT) + 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); + } + _l3_acd_data_notify_acd_failed (self, acd_data, was_probing); + return; + } + + if ( was_probing + && acd_data->probe_result) { + /* probing just completed. Schedule handling the change. */ + _LOGT_acd (acd_data, + "state: acd probe succeed"); + if (!self->priv.p->acd_ready_on_idle_source) { + self->priv.p->acd_ready_on_idle_source = nm_g_idle_source_new (G_PRIORITY_DEFAULT, + _l3_acd_ready_on_idle_cb, + self, + NULL); + g_source_attach (self->priv.p->acd_ready_on_idle_source, NULL); + } + } + + if (!acd_data->nacd_probe) { + const char *failure_reason; + NAcdProbe *probe; + + if (acd_data->announcing_failed_is_retrying) { + /* 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, + 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); + return; + } + + _LOGT_acd (acd_data, "state: start announcing (with new probe)"); + acd_data->nacd_probe = probe; + acd_data->acd_state = ACD_STATE_ANNOUNCING; + 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) + nm_assert_not_reached (); + return; + } +} + +static void +_l3_acd_data_process_changes (NML3Cfg *self) +{ + gboolean acd_is_announcing = FALSE; + gboolean acd_is_pending = FALSE; + AcdData *acd_data; + + _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) + 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; + } + + self->priv.p->acd_is_pending = acd_is_pending; + self->priv.p->acd_is_announcing = acd_is_announcing; + + if ( !acd_is_pending + && !acd_is_announcing) + _l3_acd_nacd_instance_reset (self, NM_TERNARY_DEFAULT, FALSE); +} + +/*****************************************************************************/ + static GArray * _l3_config_datas_ensure (GArray **p_arr) { @@ -539,6 +2189,69 @@ _l3_config_datas_find_next (GArray *l3_config_datas, return -1; } +static int +_l3_config_datas_get_sorted_cmp (gconstpointer p_a, + gconstpointer p_b, + gpointer user_data) +{ + const L3ConfigData *a = *((L3ConfigData **) p_a); + const L3ConfigData *b = *((L3ConfigData **) p_b); + + nm_assert (a); + nm_assert (b); + nm_assert (nm_l3_config_data_get_ifindex (a->l3cd) == nm_l3_config_data_get_ifindex (b->l3cd)); + + /* we sort the entries with higher priority (more important, lower numerical value) + * first. */ + NM_CMP_FIELD (a, b, priority); + + /* 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); + + 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) @@ -592,6 +2305,7 @@ nm_l3cfg_add_config (NML3Cfg *self, int priority, guint32 default_route_penalty_4, guint32 default_route_penalty_6, + guint32 acd_timeout_msec, NML3ConfigMergeFlags merge_flags) { GArray *l3_config_datas; @@ -642,6 +2356,7 @@ nm_l3cfg_add_config (NML3Cfg *self, .merge_flags = merge_flags, .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, @@ -668,6 +2383,10 @@ 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 (changed) @@ -732,81 +2451,84 @@ nm_l3cfg_remove_config_all (NML3Cfg *self, /*****************************************************************************/ -static int -_l3_config_combine_sort_fcn (gconstpointer p_a, - gconstpointer p_b, - gpointer user_data) +typedef struct { + NML3Cfg *self; + gconstpointer tag; +} L3ConfigMergeHookAddObjData; + +static gboolean +_l3_hook_add_addr_cb (const NML3ConfigData *l3cd, + const NMPObject *obj, + gpointer user_data) { - const L3ConfigData *a = *((L3ConfigData **) p_a); - const L3ConfigData *b = *((L3ConfigData **) p_b); + const L3ConfigMergeHookAddObjData *hook_data = user_data; + NML3Cfg *self = hook_data->self; + AcdData *acd_data; + in_addr_t addr; - nm_assert (a); - nm_assert (b); - nm_assert (nm_l3_config_data_get_ifindex (a->l3cd) == nm_l3_config_data_get_ifindex (b->l3cd)); + if (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_IP4_ADDRESS) + return TRUE; - /* we sort the entries with higher priority (more important, lower numerical value) - * first. */ - NM_CMP_FIELD (a, b, priority); + addr = NMP_OBJECT_CAST_IP4_ADDRESS (obj)->address; - /* 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); + if (ACD_ADDR_SKIP (addr)) + return TRUE; - return nm_assert_unreachable_val (0); + acd_data = _l3_acd_data_find (self, addr); + nm_assert (acd_data); + 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); } -static gboolean +static void _l3cfg_update_combined_config (NML3Cfg *self, - const NML3ConfigData **out_old /* transfer reference */) + 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; - NMDedupMultiIndex *multi_idx; - gs_free L3ConfigData **infos_heap = NULL; - L3ConfigData **infos; - GArray *l3_config_datas; + gs_free const L3ConfigData **l3_config_datas_free = NULL; + const L3ConfigData *const*l3_config_datas; + guint l3_config_datas_len; guint i; 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) - return FALSE; + return; self->priv.changed_configs = FALSE; - multi_idx = nm_platform_get_multi_idx (self->priv.platform); + _l3_config_datas_get_sorted_a (self->priv.p->l3_config_datas, + &l3_config_datas, + &l3_config_datas_len, + &l3_config_datas_free); - l3_config_datas = self->priv.p->l3_config_datas; - - if ( l3_config_datas - && l3_config_datas->len > 0) { - - l3cd = nm_l3_config_data_new (multi_idx, self->priv.ifindex); - - if (l3_config_datas->len < 300 / sizeof (infos[0])) - infos = g_alloca (l3_config_datas->len * sizeof (infos[0])); - else { - infos_heap = g_new (L3ConfigData *, l3_config_datas->len); - infos = infos_heap; - } + _l3_acd_data_add_all (self, + l3_config_datas, + l3_config_datas_len); - for (i = 0; i < l3_config_datas->len; i++) - infos[i] = _l3_config_datas_at (l3_config_datas, i); + if (l3_config_datas_len > 0) { + L3ConfigMergeHookAddObjData hook_data = { + .self = self, + }; - g_qsort_with_data (infos, - l3_config_datas->len, - sizeof (infos[0]), - _l3_config_combine_sort_fcn, - NULL); + l3cd = nm_l3_config_data_new (nm_platform_get_multi_idx (self->priv.platform), + self->priv.ifindex); - for (i = 0; i < l3_config_datas->len; i++) { + for (i = 0; i < l3_config_datas_len; i++) { + hook_data.tag = l3_config_datas[i]->tag; nm_l3_config_data_merge (l3cd, - infos[i]->l3cd, - infos[i]->merge_flags, - infos[i]->default_route_penalty_x, - NULL, - NULL); + l3_config_datas[i]->l3cd, + l3_config_datas[i]->merge_flags, + l3_config_datas[i]->default_route_penalty_x, + _l3_hook_add_addr_cb, + &hook_data); } nm_assert (l3cd); @@ -817,14 +2539,14 @@ _l3cfg_update_combined_config (NML3Cfg *self, if (nm_l3_config_data_equal (l3cd, self->priv.p->combined_l3cd)) - return FALSE; + return; _LOGT ("desired IP configuration changed"); l3cd_old = g_steal_pointer (&self->priv.p->combined_l3cd); self->priv.p->combined_l3cd = nm_l3_config_data_seal (g_steal_pointer (&l3cd)); NM_SET_OUT (out_old, nm_l3_config_data_ref (self->priv.p->combined_l3cd)); - return TRUE; + NM_SET_OUT (out_changed_combined_l3cd, TRUE); } /*****************************************************************************/ @@ -1006,12 +2728,13 @@ out_prune: /*****************************************************************************/ -gboolean -nm_l3cfg_platform_commit (NML3Cfg *self, - NML3CfgCommitType commit_type, - int addr_family, - gboolean *out_final_failure_for_temporary_not_available) +static gboolean +_platform_commit (NML3Cfg *self, + int addr_family, + NML3CfgCommitType commit_type, + gboolean *out_final_failure_for_temporary_not_available) { + const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family); nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; gs_unref_ptrarray GPtrArray *addresses = NULL; gs_unref_ptrarray GPtrArray *routes = NULL; @@ -1020,41 +2743,24 @@ nm_l3cfg_platform_commit (NML3Cfg *self, 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 combined_changed; gboolean success = TRUE; - int IS_IPv4; - g_return_val_if_fail (NM_IS_L3CFG (self), FALSE); + nm_assert (NM_IS_L3CFG (self)); nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_REAPPLY, NM_L3_CFG_COMMIT_TYPE_UPDATE, NM_L3_CFG_COMMIT_TYPE_ASSUME)); - - if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) - _l3cfg_externally_removed_objs_drop (self, addr_family); - - if (addr_family == AF_UNSPEC) { - gboolean final_failure_for_temporary_not_available_6 = FALSE; - - if (!nm_l3cfg_platform_commit (self, AF_INET, commit_type, &final_failure_for_temporary_not_available)) - success = FALSE; - if (!nm_l3cfg_platform_commit (self, AF_INET6, commit_type, &final_failure_for_temporary_not_available_6)) - success = FALSE; - NM_SET_OUT (out_final_failure_for_temporary_not_available, - ( final_failure_for_temporary_not_available - || final_failure_for_temporary_not_available_6)); - return success; - } + nm_assert_addr_family (addr_family); _LOGT ("committing IPv%c configuration (%s)", nm_utils_addr_family_to_char (addr_family), _l3_cfg_commit_type_to_string (commit_type, sbuf_commit_type, sizeof (sbuf_commit_type))); - combined_changed = _l3cfg_update_combined_config (self, &l3cd_old); + _l3cfg_update_combined_config (self, &l3cd_old, &changed_configs, &changed_combined_l3cd); - IS_IPv4 = NM_IS_IPv4 (addr_family); - - if (combined_changed) { + 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. */ _l3cfg_externally_removed_objs_drop_unused (self); @@ -1145,7 +2851,51 @@ nm_l3cfg_platform_commit (NML3Cfg *self, routes_temporary_not_available_arr)) final_failure_for_temporary_not_available = TRUE; - NM_SET_OUT (out_final_failure_for_temporary_not_available, final_failure_for_temporary_not_available); + if (final_failure_for_temporary_not_available) + NM_SET_OUT (out_final_failure_for_temporary_not_available, TRUE); + return success; +} + +gboolean +nm_l3cfg_platform_commit (NML3Cfg *self, + NML3CfgCommitType commit_type, + int addr_family, + gboolean *out_final_failure_for_temporary_not_available) +{ + gboolean success = TRUE; + gboolean acd_was_pending; + + g_return_val_if_fail (NM_IS_L3CFG (self), FALSE); + nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_REAPPLY, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NM_L3_CFG_COMMIT_TYPE_ASSUME)); + + NM_SET_OUT (out_final_failure_for_temporary_not_available, FALSE); + + acd_was_pending = self->priv.p->acd_is_pending; + + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) + nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source); + + if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) + _l3cfg_externally_removed_objs_drop (self, addr_family); + + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) { + if (!_platform_commit (self, AF_INET, commit_type, out_final_failure_for_temporary_not_available)) + success = FALSE; + } + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) { + if (!_platform_commit (self, AF_INET6, commit_type, out_final_failure_for_temporary_not_available)) + success = FALSE; + } + + if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) + _l3_acd_data_process_changes (self); + + if ( acd_was_pending + && !self->priv.p->acd_is_pending) + _l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, NULL); + return success; } @@ -1181,11 +2931,9 @@ set_property (GObject *object, static void nm_l3cfg_init (NML3Cfg *self) { - NML3CfgPrivate *p; - - p = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_L3CFG, NML3CfgPrivate); + self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE (self, NM_TYPE_L3CFG, NML3CfgPrivate); - self->priv.p = p; + c_list_init (&self->priv.p->acd_lst_head); } static void @@ -1224,8 +2972,20 @@ finalize (GObject *object) { NML3Cfg *self = NM_L3CFG (object); + nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source); + nm_assert (nm_g_array_len (self->priv.p->property_emit_list) == 0u); + _l3_acd_data_prune (self, TRUE); + + nm_assert (c_list_is_empty (&self->priv.p->acd_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); + nm_clear_pointer (&self->priv.p->nacd, n_acd_unref); + nm_clear_g_source_inst (&self->priv.p->nacd_source); + nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry); + 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); @@ -1238,6 +2998,8 @@ finalize (GObject *object) nm_clear_pointer (&self->priv.pllink, nmp_object_unref); + nm_clear_pointer (&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref); + _LOGT ("finalized"); G_OBJECT_CLASS (nm_l3cfg_parent_class)->finalize (object); @@ -1274,13 +3036,10 @@ nm_l3cfg_class_init (NML3CfgClass *klass) signals[SIGNAL_NOTIFY] = g_signal_new (NM_L3CFG_SIGNAL_NOTIFY, G_OBJECT_CLASS_TYPE (object_class), - G_SIGNAL_DETAILED - | G_SIGNAL_RUN_FIRST, + G_SIGNAL_RUN_FIRST, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_INT /* NML3ConfigNotifyType */, - G_TYPE_POINTER /* pay-load */ ); - - signal_notify_quarks[NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED] = g_quark_from_static_string (NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED_DETAIL); + G_TYPE_POINTER /* (const NML3ConfigNotifyPayload *) */ ); } diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h index 69748b6e79..335b59e614 100644 --- a/src/nm-l3cfg.h +++ b/src/nm-l3cfg.h @@ -20,10 +20,26 @@ typedef enum { NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, + NM_L3_CONFIG_NOTIFY_TYPE_ACD_FAILED, + NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, _NM_L3_CONFIG_NOTIFY_TYPE_NUM, } NML3ConfigNotifyType; -#define NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED_DETAIL "routes-temporary-not-available" +typedef struct { + const NMPObject *obj; + const NML3ConfigData *l3cd; + gconstpointer tag; +} NML3ConfigNotifyPayloadAcdFailedSource; + +typedef struct { + union { + struct { + in_addr_t addr; + guint sources_len; + const NML3ConfigNotifyPayloadAcdFailedSource *sources; + } acd_failed; + }; +} NML3ConfigNotifyPayload; struct _NML3CfgPrivate; @@ -87,6 +103,8 @@ nm_l3cfg_get_platform (const NML3Cfg *self) return self->priv.platform; } +gboolean nm_l3cfg_get_acd_is_pending (NML3Cfg *self); + /*****************************************************************************/ typedef enum { @@ -117,6 +135,7 @@ void nm_l3cfg_add_config (NML3Cfg *self, int priority, guint32 default_route_penalty_4, guint32 default_route_penalty_6, + guint32 acd_timeout_msec, NML3ConfigMergeFlags merge_flags); void nm_l3cfg_remove_config (NML3Cfg *self, @@ -145,6 +164,7 @@ typedef enum { /* This is a full sync. It configures the IP addresses/routes that are indicated, * while removing the existing ones from the interface. */ NM_L3_CFG_COMMIT_TYPE_REAPPLY, + } NML3CfgCommitType; gboolean nm_l3cfg_platform_commit (NML3Cfg *self, |