summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-08-03 17:33:31 +0200
committerThomas Haller <thaller@redhat.com>2020-09-03 11:52:39 +0200
commite4f04267bb684c7a73b634237801f13971de3dfc (patch)
tree19cf70d0c8f3872492034b51c6392f88d15ee22a
parentf81360bbbf973884b684d8bb9f46aeec128ba5b7 (diff)
downloadNetworkManager-th/l3cfg-6-acd.tar.gz
l3cfg: implement IPv4 DAD/ACD (address collision detection) in NML3Cfgth/l3cfg-6-acd
Currently, NMDevice does ACD. It intercepts certain NMIP4Config instances, and tries to perform ACD on the addresses. I think this functionality should be handled by NML3Cfg instead. For one, NML3Cfg sees all configurations, and can perform ACD for all (relevant) addresses. Also, it moves logic away from NMDevice and makes the functionality available without an NMDevice. As such, it also will allow that independent "controllers" contribute NML3ConfigData instances and ACD will performed for all of them (as requested). This will be our implementation for IPv4 ACD (https://tools.ietf.org/html/rfc5227) based on nettools' n-acd library. The code is not actually tested yes, because NMDevice did not yet switch over to use NML3Cfg. Once that happens, surely issues with this patch will be found that will need fixing.
-rw-r--r--Makefile.am2
-rw-r--r--src/meson.build21
-rw-r--r--src/nm-l3cfg.c1949
-rw-r--r--src/nm-l3cfg.h22
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,