summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-10-13 13:49:07 +0200
committerThomas Haller <thaller@redhat.com>2020-10-13 13:49:07 +0200
commitf0c2e35cfe1b34f6ed49171f4ab5df169881ce71 (patch)
tree44f41aa5c2c94695232f14969465aeed00a88cc4
parent28bbe2a7980158f10ea6cbab2077ea5848c16411 (diff)
parent96c2aee186714a68efae28dc030839464f5f3b47 (diff)
downloadNetworkManager-f0c2e35cfe1b34f6ed49171f4ab5df169881ce71.tar.gz
l3cfg: merge branch 'th/l3cfg-12'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/651
-rw-r--r--shared/nm-glib-aux/nm-shared-utils.h49
-rw-r--r--src/nm-l3-config-data.c18
-rw-r--r--src/nm-l3-config-data.h1
-rw-r--r--src/nm-l3cfg.c2207
-rw-r--r--src/nm-l3cfg.h58
-rw-r--r--src/platform/nm-platform.c25
-rw-r--r--src/platform/nm-platform.h7
-rw-r--r--src/tests/test-l3cfg.c195
8 files changed, 1579 insertions, 981 deletions
diff --git a/shared/nm-glib-aux/nm-shared-utils.h b/shared/nm-glib-aux/nm-shared-utils.h
index 887e00c2a4..308728cf08 100644
--- a/shared/nm-glib-aux/nm-shared-utils.h
+++ b/shared/nm-glib-aux/nm-shared-utils.h
@@ -1524,6 +1524,55 @@ nm_g_array_len(const GArray *arr)
/*****************************************************************************/
+static inline GPtrArray *
+nm_g_ptr_array_ref(GPtrArray *arr)
+{
+ return arr ? g_ptr_array_ref(arr) : NULL;
+}
+
+static inline void
+nm_g_ptr_array_unref(GPtrArray *arr)
+{
+ if (arr)
+ g_ptr_array_unref(arr);
+}
+
+#define nm_g_ptr_array_set(pdst, val) \
+ ({ \
+ GPtrArray **_pdst = (pdst); \
+ GPtrArray * _val = (val); \
+ gboolean _changed = FALSE; \
+ \
+ nm_assert(_pdst); \
+ \
+ if (*_pdst != _val) { \
+ _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \
+ \
+ *_pdst = nm_g_ptr_array_ref(_val); \
+ _changed = TRUE; \
+ } \
+ _changed; \
+ })
+
+#define nm_g_ptr_array_set_take(pdst, val) \
+ ({ \
+ GPtrArray **_pdst = (pdst); \
+ GPtrArray * _val = (val); \
+ gboolean _changed = FALSE; \
+ \
+ nm_assert(_pdst); \
+ \
+ if (*_pdst != _val) { \
+ _nm_unused gs_unref_ptrarray GPtrArray *_old = *_pdst; \
+ \
+ *_pdst = _val; \
+ _changed = TRUE; \
+ } else { \
+ nm_g_ptr_array_unref(_val); \
+ } \
+ _changed; \
+ })
+
static inline guint
nm_g_ptr_array_len(const GPtrArray *arr)
{
diff --git a/src/nm-l3-config-data.c b/src/nm-l3-config-data.c
index b34afd616a..864924e88d 100644
--- a/src/nm-l3-config-data.c
+++ b/src/nm-l3-config-data.c
@@ -2546,13 +2546,25 @@ nm_l3_config_data_merge(NML3ConfigData * self,
src,
&obj,
NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) {
- if (hook_add_addr && !hook_add_addr(src, obj, hook_user_data))
+ NMPlatformIPXAddress addr_stack;
+ const NMPlatformIPAddress *addr = NULL;
+ NMTernary ip4acd_not_ready = NM_TERNARY_DEFAULT;
+
+ if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data))
continue;
+ if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT
+ && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) {
+ addr_stack.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj);
+ addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready);
+ addr = &addr_stack.ax;
+ } else
+ nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT);
+
nm_l3_config_data_add_address_full(self,
addr_family,
- obj,
- NULL,
+ addr ? NULL : obj,
+ addr,
NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE,
NULL);
}
diff --git a/src/nm-l3-config-data.h b/src/nm-l3-config-data.h
index 81dae7dea4..0da98079de 100644
--- a/src/nm-l3-config-data.h
+++ b/src/nm-l3-config-data.h
@@ -141,6 +141,7 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex * mu
typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd,
const NMPObject * obj,
+ NMTernary * out_ip4acd_not_ready,
gpointer user_data);
void nm_l3_config_data_merge(NML3ConfigData * self,
diff --git a/src/nm-l3cfg.c b/src/nm-l3cfg.c
index 9cb6ca976a..b689206992 100644
--- a/src/nm-l3cfg.c
+++ b/src/nm-l3cfg.c
@@ -19,10 +19,11 @@
#define ACD_ENSURE_RATELIMIT_MSEC ((guint32) 4000u)
#define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32)(1000u + ACD_ENSURE_RATELIMIT_MSEC))
#define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC ((guint32) 1000u)
-#define ACD_WAIT_PROBING_RESTART_TIME_MSEC ((guint32) 8000u)
#define ACD_MAX_TIMEOUT_MSEC ((guint32) 30000u)
#define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u)
-#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 20000u)
+#define ACD_WAIT_TIME_CONFLICT_RESTART_MSEC ((guint32) 120000u)
+#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 30000u)
+#define ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC ((guint32) 30000u)
static gboolean
ACD_ADDR_SKIP(in_addr_t addr)
@@ -39,13 +40,16 @@ ACD_ADDR_SKIP(in_addr_t addr)
ACD_TRACK_PTR2((acd_track)->l3cd, (acd_track)->obj, (acd_track)->tag)
typedef enum {
- ACD_STATE_CHANGE_MODE_INIT,
+ ACD_STATE_CHANGE_MODE_NACD_CONFLICT = N_ACD_EVENT_CONFLICT,
+ ACD_STATE_CHANGE_MODE_NACD_DEFENDED = N_ACD_EVENT_DEFENDED,
+ ACD_STATE_CHANGE_MODE_NACD_DOWN = N_ACD_EVENT_DOWN,
+ ACD_STATE_CHANGE_MODE_NACD_READY = N_ACD_EVENT_READY,
+ ACD_STATE_CHANGE_MODE_NACD_USED = N_ACD_EVENT_USED,
+
+ ACD_STATE_CHANGE_MODE_INIT = _N_ACD_EVENT_N,
+ ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
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,
@@ -54,56 +58,66 @@ typedef enum {
} AcdStateChangeMode;
typedef struct {
- CList acd_track_lst;
- const NMPObject * obj;
- const NML3ConfigData *l3cd;
- gconstpointer tag;
- guint32 acd_timeout_msec;
- bool acd_dirty : 1;
- bool acd_failed_notified : 1;
+ union {
+ NML3AcdAddrTrackInfo track_info;
+ struct {
+ const NMPObject * obj;
+ const NML3ConfigData *l3cd;
+ gconstpointer tag;
+
+ guint32 acd_timeout_msec_track;
+ NML3AcdDefendType acd_defend_type_track;
+ bool acd_dirty_track : 1;
+ bool acd_failed_notified_track : 1;
+ };
+ };
} AcdTrackData;
-typedef enum _nm_packed {
- ACD_STATE_INIT,
- ACD_STATE_PROBING,
- ACD_STATE_PROBE_DONE,
- ACD_STATE_ANNOUNCING,
-} AcdState;
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, track_info) == 0);
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, obj) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, obj));
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, l3cd) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, l3cd));
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, tag) == G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, tag));
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdTrackData, acd_timeout_msec_track)
+ >= G_STRUCT_OFFSET(NML3AcdAddrTrackInfo, _padding));
+G_STATIC_ASSERT(sizeof(AcdTrackData) == sizeof(NML3AcdAddrTrackInfo));
-typedef struct {
- in_addr_t addr;
+#define ACD_TRACK_DATA(arg) NM_CONSTCAST(AcdTrackData, arg, NML3AcdAddrTrackInfo)
- /* 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;
+G_STATIC_ASSERT(G_STRUCT_OFFSET(NML3AcdAddrInfo, addr) == 0);
- CList acd_lst;
- CList acd_notify_complete_lst;
- CList acd_track_lst_head;
+typedef struct {
+ NML3AcdAddrInfo info;
- NML3Cfg *self;
+ CList acd_lst;
+ CList acd_event_notify_lst;
NAcdProbe *nacd_probe;
- GSource *acd_timeout_source;
- gint64 acd_timeout_expiry_msec;
+ GSource *acd_data_timeout_source;
/* see probing_timeout_msec. */
gint64 probing_timestamp_msec;
- /* the ACD state for this address. */
- AcdState acd_state;
+ gint64 last_defendconflict_timestamp_msec;
- /* The probe result. This is only relevant if @acd_state is ACD_STATE_PROBE_DONE.
- * In state ACD_STATE_ANNOUNCING the @probe_result must be TRUE. */
- bool probe_result : 1;
+ guint n_track_infos_alloc;
- bool announcing_failed_is_retrying : 1;
+ /* This is only relevant while in state NM_L3_ACD_ADDR_STATE_PROBING. It's the
+ * duration for how long we probe, and @probing_timestamp_msec is the
+ * timestamp when we start probing. */
+ guint32 probing_timeout_msec;
+
+ NMEtherAddr last_conflict_addr;
- bool initializing : 1;
+ NML3AcdDefendType acd_defend_type_desired : 3;
+ NML3AcdDefendType acd_defend_type_current : 3;
+ bool acd_defend_type_is_active : 1;
+
+ bool track_infos_changed : 1;
} AcdData;
+G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
+
struct _NML3CfgCommitTypeHandle {
CList commit_type_lst;
NML3CfgCommitType commit_type;
@@ -133,11 +147,12 @@ typedef struct {
};
guint32 default_route_penalty_x[2];
};
- gconstpointer tag;
- guint64 pseudo_timestamp;
- int priority;
- guint32 acd_timeout_msec;
- bool dirty : 1;
+ gconstpointer tag_confdata;
+ guint64 pseudo_timestamp_confdata;
+ int priority_confdata;
+ guint32 acd_timeout_msec_confdata;
+ NML3AcdDefendType acd_defend_type_confdata : 3;
+ bool dirty_confdata : 1;
} L3ConfigData;
/*****************************************************************************/
@@ -170,11 +185,14 @@ typedef struct _NML3CfgPrivate {
GHashTable *acd_lst_hash;
CList acd_lst_head;
- CList acd_notify_complete_lst_head;
+ CList acd_event_notify_lst_head;
NAcd * nacd;
GSource *nacd_source;
+ GSource *nacd_event_down_source;
+ gint64 nacd_event_down_ratelimited_until_msec;
+
/* This is for rate-limiting the creation of nacd instance. */
GSource *nacd_instance_ensure_retry;
@@ -198,16 +216,35 @@ typedef struct _NML3CfgPrivate {
guint externally_removed_objs_cnt_routes_x[2];
};
+ union {
+ struct {
+ GPtrArray *last_addresses_6;
+ GPtrArray *last_addresses_4;
+ };
+ GPtrArray *last_addresses_x[2];
+ };
+
+ union {
+ struct {
+ GPtrArray *last_routes_6;
+ GPtrArray *last_routes_4;
+ };
+ GPtrArray *last_routes_x[2];
+ };
+
guint routes_temporary_not_available_id;
+ gint8 commit_reentrant_count;
+
bool commit_type_update_sticky : 1;
bool acd_is_pending : 1;
- bool acd_is_announcing : 1;
bool nacd_acd_not_supported : 1;
bool acd_ipv4_addresses_on_link_has : 1;
+ bool changed_configs_configs : 1;
+ bool changed_configs_acd_state : 1;
} NML3CfgPrivate;
struct _NML3CfgClass {
@@ -234,15 +271,16 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
} \
G_STMT_END
-#define _LOGT_acd(acd_data, ...) \
- G_STMT_START \
- { \
- char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \
- \
- _LOGT("acd[%s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
- _nm_utils_inet4_ntop((acd_data)->addr, _sbuf_acd) \
- _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
- } \
+#define _LOGT_acd(acd_data, ...) \
+ G_STMT_START \
+ { \
+ char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \
+ \
+ _LOGT("acd[%s, %s]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
+ _nm_utils_inet4_ntop((acd_data)->info.addr, _sbuf_acd), \
+ _l3_acd_addr_state_to_string((acd_data)->info.state) \
+ _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
+ } \
G_STMT_END
/*****************************************************************************/
@@ -251,7 +289,7 @@ static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is
static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type);
-static void _l3_acd_data_notify_acd_completed_all(NML3Cfg *self);
+static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self);
static gboolean _acd_has_valid_link(const NMPObject *obj,
const guint8 ** out_addr_bin,
@@ -260,12 +298,11 @@ static gboolean _acd_has_valid_link(const NMPObject *obj,
static void
_l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_data_notify);
-static void _l3_acd_data_prune(NML3Cfg *self, gboolean all);
-
static void _l3_acd_data_state_change(NML3Cfg * self,
AcdData * acd_data,
AcdStateChangeMode mode,
- NAcdEvent * event);
+ const NMEtherAddr *sender,
+ gint64 * p_now_msec);
static AcdData *_l3_acd_data_find(NML3Cfg *self, in_addr_t addr);
@@ -282,7 +319,7 @@ static NM_UTILS_ENUM2STR_DEFINE(_l3_cfg_commit_type_to_string,
static NM_UTILS_ENUM2STR_DEFINE(
_l3_config_notify_type_to_string,
NML3ConfigNotifyType,
- NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, "acd-complete"),
+ NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, "acd-event"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, "platform-change"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, "platform-change-on-idle"),
NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"),
@@ -290,6 +327,36 @@ static NM_UTILS_ENUM2STR_DEFINE(
"routes-temporary-not-available-expired"),
NM_UTILS_ENUM2STR_IGNORE(_NM_L3_CONFIG_NOTIFY_TYPE_NUM), );
+static NM_UTILS_ENUM2STR_DEFINE(_l3_acd_defend_type_to_string,
+ NML3AcdDefendType,
+ NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ALWAYS, "always"),
+ NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NEVER, "never"),
+ NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_NONE, "none"),
+ NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), );
+
+static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd,
+ NML3AcdDefendType,
+ int,
+ NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(0),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ALWAYS,
+ N_ACD_DEFEND_ALWAYS),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_ONCE, N_ACD_DEFEND_ONCE),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_DEFEND_TYPE_NEVER, N_ACD_DEFEND_NEVER),
+ NM_UTILS_LOOKUP_ITEM_IGNORE_OTHER(), );
+
+static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string,
+ NML3AcdAddrState,
+ const char *,
+ NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT(NULL),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_CONFLICT, "conflict"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_READY, "ready"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_DEFENDING, "defending"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_INIT, "init"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_PROBING, "probing"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+ "external-removed"),
+ NM_UTILS_LOOKUP_ITEM(NM_L3_ACD_ADDR_STATE_USED, "used"), );
+
/*****************************************************************************/
static const char *
@@ -308,12 +375,12 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data,
nm_utils_strbuf_seek_end(&s, &l);
switch (notify_data->notify_type) {
- case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED:
+ case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
nm_utils_strbuf_append(&s,
&l,
- ", addr=%s, probe-result=%d",
- _nm_utils_inet4_ntop(notify_data->acd_completed.addr, sbuf_addr),
- (int) notify_data->acd_completed.probe_result);
+ ", addr=%s, state=%s",
+ _nm_utils_inet4_ntop(notify_data->acd_event.info.addr, sbuf_addr),
+ _l3_acd_addr_state_to_string(notify_data->acd_event.info.state));
break;
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
nm_utils_strbuf_append(
@@ -365,8 +432,9 @@ _nm_l3cfg_emit_signal_notify_simple(NML3Cfg *self, NML3ConfigNotifyType notify_t
static void
_l3_changed_configs_set_dirty(NML3Cfg *self)
{
- _LOGT("configuration changed");
- self->priv.changed_configs = TRUE;
+ _LOGT("IP configuration changed (mark dirty)");
+ self->priv.p->changed_configs_configs = TRUE;
+ self->priv.p->changed_configs_acd_state = TRUE;
}
/*****************************************************************************/
@@ -386,7 +454,11 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self,
else
self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
if (acd_data)
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, NULL);
+ _l3_acd_data_state_change(self,
+ acd_data,
+ ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
+ NULL,
+ NULL);
return;
}
@@ -396,8 +468,13 @@ _l3_acd_ipv4_addresses_on_link_update(NML3Cfg * self,
* cached addresses, and fetch them new the next time we need the information. */
nm_clear_pointer(&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref);
self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
- if (acd_data)
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, NULL);
+ if (acd_data) {
+ _l3_acd_data_state_change(self,
+ acd_data,
+ ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED,
+ NULL,
+ NULL);
+ }
}
static gboolean
@@ -588,7 +665,11 @@ _l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointe
const NMPObject *obj = o;
GHashTable * externally_removed_objs_hash = user_data;
- return !g_hash_table_contains(externally_removed_objs_hash, obj);
+ if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS
+ && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready)
+ return FALSE;
+
+ return !nm_g_hash_table_contains(externally_removed_objs_hash, obj);
}
/*****************************************************************************/
@@ -662,9 +743,16 @@ _load_link(NML3Cfg *self, gboolean initial)
_l3_acd_nacd_instance_reset(self, NM_TERNARY_FALSE, TRUE);
} else if (nacd_link_now_up) {
if (!c_list_is_empty(&self->priv.p->acd_lst_head)) {
+ gint64 now_msec = 0;
+
_LOGT("acd: link up requires are re-initialize of ACD probes");
- c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_LINK_NOW_UP, NULL);
+ c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+ _l3_acd_data_state_change(self,
+ acd_data,
+ ACD_STATE_CHANGE_MODE_LINK_NOW_UP,
+ NULL,
+ &now_msec);
+ }
}
}
}
@@ -685,7 +773,7 @@ _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags)
};
_nm_l3cfg_emit_signal_notify(self, &notify_data);
- _l3_acd_data_notify_acd_completed_all(self);
+ _nm_l3cfg_emit_signal_notify_acd_event_all(self);
if (NM_FLAGS_ANY(obj_type_flags, nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ROUTE)))
_property_emit_notify(self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE);
@@ -891,68 +979,59 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self)
static gboolean
_acd_track_data_is_not_dirty(const AcdTrackData *acd_track)
{
- return acd_track && !acd_track->acd_dirty;
+ return acd_track && !acd_track->acd_dirty_track;
}
static void
-_acd_track_data_free(AcdTrackData *acd_track)
+_acd_track_data_clear(AcdTrackData *acd_track)
{
- c_list_unlink_stale(&acd_track->acd_track_lst);
nm_l3_config_data_unref(acd_track->l3cd);
nmp_object_unref(acd_track->obj);
- nm_g_slice_free(acd_track);
}
static void
_acd_data_free(AcdData *acd_data)
{
- nm_assert(c_list_is_empty(&acd_data->acd_track_lst_head));
+ nm_assert(acd_data->info.n_track_infos == 0u);
n_acd_probe_free(acd_data->nacd_probe);
- nm_clear_g_source_inst(&acd_data->acd_timeout_source);
+ nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
c_list_unlink_stale(&acd_data->acd_lst);
+ c_list_unlink_stale(&acd_data->acd_event_notify_lst);
+ g_free((AcdTrackData *) acd_data->info.track_infos);
nm_g_slice_free(acd_data);
}
-static gboolean
-_acd_data_probe_result_is_good(const AcdData *acd_data)
-{
- nm_assert(acd_data);
-
- if (acd_data->acd_state < ACD_STATE_PROBE_DONE) {
- /* we are currently probing. Wait. */
- return FALSE;
- }
-
- /* Probing is already completed. Use the probe result. */
- return acd_data->probe_result;
-}
-
static guint
-_acd_data_collect_tracks_data(const AcdData *acd_data,
- NMTernary dirty_selector,
- NMTernary acd_failed_notified_selector,
- guint32 * out_best_acd_timeout_msec)
-{
- guint32 best_acd_timeout_msec = G_MAXUINT32;
- AcdTrackData *acd_track;
- guint n = 0;
+_acd_data_collect_tracks_data(const AcdData * acd_data,
+ NMTernary dirty_selector,
+ guint32 * out_best_acd_timeout_msec,
+ NML3AcdDefendType *out_best_acd_defend_type)
+{
+ NML3AcdDefendType best_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_NONE;
+ guint32 best_acd_timeout_msec = G_MAXUINT32;
+ guint n = 0;
+ guint i;
+
+ for (i = 0; i < acd_data->info.n_track_infos; i++) {
+ const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]);
- c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
if (dirty_selector != NM_TERNARY_DEFAULT) {
- if ((!!dirty_selector) != (!!acd_track->acd_dirty))
- continue;
- }
- if (acd_failed_notified_selector != NM_TERNARY_DEFAULT) {
- if ((!!acd_failed_notified_selector) != (!!acd_track->acd_failed_notified))
+ if ((!!dirty_selector) != (!!acd_track->acd_dirty_track))
continue;
}
n++;
- if (best_acd_timeout_msec > acd_track->acd_timeout_msec)
- best_acd_timeout_msec = acd_track->acd_timeout_msec;
+ if (best_acd_timeout_msec > acd_track->acd_timeout_msec_track)
+ best_acd_timeout_msec = acd_track->acd_timeout_msec_track;
+ if (best_acd_defend_type < acd_track->acd_defend_type_track)
+ best_acd_defend_type = acd_track->acd_defend_type_track;
}
+ nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE);
+ nm_assert(best_acd_defend_type <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
+
NM_SET_OUT(out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u);
+ NM_SET_OUT(out_best_acd_defend_type, best_acd_defend_type);
return n;
}
@@ -962,11 +1041,13 @@ _acd_data_find_track(const AcdData * acd_data,
const NMPObject * obj,
gconstpointer tag)
{
- AcdTrackData *acd_track;
+ guint i;
+
+ for (i = 0; i < acd_data->info.n_track_infos; i++) {
+ const AcdTrackData *acd_track = ACD_TRACK_DATA(&acd_data->info.track_infos[i]);
- c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
if (acd_track->obj == obj && acd_track->l3cd == l3cd && acd_track->tag == tag)
- return acd_track;
+ return (AcdTrackData *) acd_track;
}
return NULL;
@@ -991,7 +1072,7 @@ _acd_has_valid_link(const NMPObject *obj,
link = NMP_OBJECT_CAST_LINK(obj);
addr_bin = nmp_link_address_get(&link->l_address, &addr_len);
- if (!addr_bin || addr_len != ACD_SUPPORTED_ETH_ALEN) {
+ if (addr_len != ACD_SUPPORTED_ETH_ALEN) {
NM_SET_OUT(out_acd_not_supported, TRUE);
return FALSE;
}
@@ -1002,6 +1083,21 @@ _acd_has_valid_link(const NMPObject *obj,
}
static gboolean
+_l3_acd_nacd_event_down_timeout_cb(gpointer user_data)
+{
+ NML3Cfg *self = user_data;
+ AcdData *acd_data;
+ gint64 now_msec = 0;
+
+ _LOGT("acd: message possibly dropped due to device down (handle events)");
+ nm_clear_g_source_inst(&self->priv.p->nacd_event_down_source);
+ c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
+ _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL, &now_msec);
+ _nm_l3cfg_emit_signal_notify_acd_event_all(self);
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
_l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
{
NML3Cfg *self = user_data;
@@ -1018,8 +1114,10 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
}
while (TRUE) {
- AcdData * acd_data;
- NAcdEvent *event;
+ NMEtherAddr sender_addr_data;
+ const NMEtherAddr *sender_addr;
+ AcdData * acd_data;
+ NAcdEvent * event;
r = n_acd_pop_event(self->priv.p->nacd, &event);
if (r) {
@@ -1031,77 +1129,83 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data)
goto out;
}
-#define _acd_event_payload used
- G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
- == G_STRUCT_OFFSET(NAcdEvent, defended));
- G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
- == G_STRUCT_OFFSET(NAcdEvent, conflict));
- G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(NAcdEvent, _acd_event_payload)
- == G_STRUCT_OFFSET(NAcdEvent, used));
- nm_assert(&event->_acd_event_payload == &event->defended);
- nm_assert(&event->_acd_event_payload == &event->conflict);
- nm_assert(&event->_acd_event_payload == &event->used);
-
switch (event->event) {
case N_ACD_EVENT_READY:
- n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data);
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, event);
+ n_acd_probe_get_userdata(event->ready.probe, (void **) &acd_data);
+ _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, NULL, NULL);
break;
case N_ACD_EVENT_USED:
- n_acd_probe_get_userdata(event->_acd_event_payload.probe, (void **) &acd_data);
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_USED, event);
- break;
case N_ACD_EVENT_DEFENDED:
case N_ACD_EVENT_CONFLICT:
{
- gs_free char *sender_str = NULL;
- const char * addr_str = NULL;
- char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
-
- /* 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.plobj,
- " (",
- NMP_OBJECT_CAST_LINK(self->priv.plobj)->name,
- ")",
- ""),
- addr_str ?: _nm_utils_inet4_ntop(acd_data->addr, sbuf_addr),
- sender_str
- ?: (sender_str =
- nm_utils_bin2hexstr_full(event->_acd_event_payload.sender,
- event->_acd_event_payload.n_sender,
- ':',
- FALSE,
- NULL)));
+#define _acd_event_payload_with_sender(event) \
+ ({ \
+ NAcdEvent *_event = (event); \
+ \
+ nm_assert(event); \
+ nm_assert(NM_IN_SET(event->event, \
+ N_ACD_EVENT_USED, \
+ N_ACD_EVENT_DEFENDED, \
+ N_ACD_EVENT_CONFLICT)); \
+ nm_assert(&_event->used == &_event->defended); \
+ nm_assert(&_event->used == &_event->conflict); \
+ &_event->used; \
+ })
+
+ n_acd_probe_get_userdata(_acd_event_payload_with_sender(event)->probe,
+ (void **) &acd_data);
+
+ if (_acd_event_payload_with_sender(event)->n_sender == ETH_ALEN) {
+ G_STATIC_ASSERT_EXPR(_nm_alignof(NMEtherAddr) == 1);
+ nm_assert(_acd_event_payload_with_sender(event)->sender);
+ memcpy(&sender_addr_data, _acd_event_payload_with_sender(event)->sender, ETH_ALEN);
+ sender_addr = &sender_addr_data;
+ } else {
+ nm_assert_not_reached();
+ sender_addr = &nm_ether_addr_zero;
}
+
+ _l3_acd_data_state_change(self,
+ acd_data,
+ (AcdStateChangeMode) event->event,
+ sender_addr,
+ NULL);
break;
}
case N_ACD_EVENT_DOWN:
- _LOGT("acd: message possibly dropped due to device down.");
- c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL);
+ if (!self->priv.p->nacd_event_down_source) {
+ gint64 now_msec;
+ guint32 timeout_msec;
+
+ now_msec = nm_utils_get_monotonic_timestamp_msec();
+ if (self->priv.p->nacd_event_down_ratelimited_until_msec > 0
+ && now_msec < self->priv.p->nacd_event_down_ratelimited_until_msec)
+ timeout_msec = self->priv.p->nacd_event_down_ratelimited_until_msec - now_msec;
+ else {
+ timeout_msec = 0;
+ self->priv.p->nacd_event_down_ratelimited_until_msec = now_msec + 2000;
+ }
+ _LOGT("acd: message possibly dropped due to device down (schedule handling event "
+ "in %u msec)",
+ timeout_msec);
+ self->priv.p->nacd_event_down_source =
+ nm_g_timeout_source_new(timeout_msec,
+ G_PRIORITY_DEFAULT,
+ _l3_acd_nacd_event_down_timeout_cb,
+ self,
+ NULL);
+ g_source_attach(self->priv.p->nacd_event_down_source, NULL);
+ }
break;
default:
- _LOGT("acd: unexpected event %u. Ignore", event->event);
+ _LOGE("acd: unexpected event %u. Ignore", event->event);
+ nm_assert_not_reached();
break;
}
+
+ /* We are on an idle handler, and the n-acd events are expected to be independent. So, after
+ * each event emit all queued AcdEvent signals. */
+ _nm_l3cfg_emit_signal_notify_acd_event_all(self);
}
nm_assert_not_reached();
@@ -1113,8 +1217,6 @@ out:
_l3_acd_nacd_instance_reset(self, NM_TERNARY_TRUE, TRUE);
}
- _l3_acd_data_notify_acd_completed_all(self);
-
return G_SOURCE_CONTINUE;
}
@@ -1165,9 +1267,15 @@ _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_d
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);
+ gint64 now_msec = 0;
+
+ c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+ _l3_acd_data_state_change(self,
+ acd_data,
+ ACD_STATE_CHANGE_MODE_INSTANCE_RESET,
+ NULL,
+ &now_msec);
+ }
}
}
@@ -1273,30 +1381,37 @@ _l3_acd_nacd_instance_create_probe(NML3Cfg * self,
}
static void
-_l3_acd_data_free_trackers(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */)
+_l3_acd_data_prune_one(NML3Cfg *self, AcdData *acd_data, gboolean all /* or only dirty */)
{
- AcdTrackData *acd_track;
- AcdTrackData *acd_track_safe;
+ AcdTrackData *acd_tracks;
+ guint i;
+ guint j;
+
+ acd_tracks = (AcdTrackData *) acd_data->info.track_infos;
+ j = 0;
+ for (i = 0; i < acd_data->info.n_track_infos; i++) {
+ AcdTrackData *acd_track = &acd_tracks[i];
- c_list_for_each_entry_safe (acd_track,
- acd_track_safe,
- &acd_data->acd_track_lst_head,
- acd_track_lst) {
/* If not "all" is requested, we only delete the dirty ones
* (and mark the survivors as dirty right away). */
- if (!all && !acd_track->acd_dirty) {
- acd_track->acd_dirty = TRUE;
+ if (!all && !acd_track->acd_dirty_track) {
+ acd_track->acd_dirty_track = TRUE;
+ if (j != i)
+ acd_tracks[j] = *acd_track;
+ j++;
continue;
}
_LOGT_acd(acd_data, "untrack " ACD_TRACK_FMT "", ACD_TRACK_PTR(acd_track));
- _acd_track_data_free(acd_track);
+ _acd_track_data_clear(acd_track);
}
- if (!c_list_is_empty(&acd_data->acd_track_lst_head))
+ acd_data->info.n_track_infos = j;
+ if (j > 0)
return;
+ _LOGT_acd(acd_data, "removed");
if (!g_hash_table_remove(self->priv.p->acd_lst_hash, acd_data))
nm_assert_not_reached();
_acd_data_free(acd_data);
@@ -1309,7 +1424,7 @@ _l3_acd_data_prune(NML3Cfg *self, gboolean all /* or only dirty */)
AcdData *acd_data;
c_list_for_each_entry_safe (acd_data, acd_data_safe, &self->priv.p->acd_lst_head, acd_lst)
- _l3_acd_data_free_trackers(self, acd_data, all);
+ _l3_acd_data_prune_one(self, acd_data, all);
}
static AcdData *
@@ -1318,17 +1433,33 @@ _l3_acd_data_find(NML3Cfg *self, in_addr_t addr)
return nm_g_hash_table_lookup(self->priv.p->acd_lst_hash, &addr);
}
+static gboolean
+_l3_acd_data_defendconflict_warning_ratelimited(AcdData *acd_data, gint64 *p_now_msec)
+{
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+
+ if (acd_data->last_defendconflict_timestamp_msec == 0
+ || acd_data->last_defendconflict_timestamp_msec
+ > *p_now_msec - ACD_DEFENDCONFLICT_INFO_RATELIMIT_MSEC) {
+ acd_data->last_defendconflict_timestamp_msec = *p_now_msec;
+ return FALSE;
+ }
+ return TRUE;
+}
+
static void
_l3_acd_data_add(NML3Cfg * self,
const NML3ConfigData *l3cd,
const NMPObject * obj,
gconstpointer tag,
+ NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec)
{
in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
AcdTrackData *acd_track;
AcdData * acd_data;
const char * track_mode;
+ char sbuf100[100];
if (ACD_ADDR_SKIP(addr))
return;
@@ -1343,64 +1474,91 @@ _l3_acd_data_add(NML3Cfg * self,
if (!acd_data) {
if (G_UNLIKELY(!self->priv.p->acd_lst_hash)) {
- G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, addr) == 0);
+ G_STATIC_ASSERT_EXPR(G_STRUCT_OFFSET(AcdData, info.addr) == 0);
self->priv.p->acd_lst_hash = g_hash_table_new(nm_puint32_hash, nm_puint32_equals);
}
acd_data = g_slice_new(AcdData);
*acd_data = (AcdData){
- .self = self,
- .addr = addr,
- .acd_track_lst_head = C_LIST_INIT(acd_data->acd_track_lst_head),
- .acd_notify_complete_lst = C_LIST_INIT(acd_data->acd_notify_complete_lst),
- .acd_state = ACD_STATE_INIT,
- .probing_timestamp_msec = 0,
- .probe_result = FALSE,
- .initializing = TRUE,
+ .info =
+ {
+ .l3cfg = self,
+ .addr = addr,
+ .state = NM_L3_ACD_ADDR_STATE_INIT,
+ .n_track_infos = 0,
+ .track_infos = NULL,
+ },
+ .n_track_infos_alloc = 0,
+ .acd_event_notify_lst = C_LIST_INIT(acd_data->acd_event_notify_lst),
+ .probing_timestamp_msec = 0,
+ .acd_defend_type_desired = NM_L3_ACD_DEFEND_TYPE_NONE,
+ .acd_defend_type_current = NM_L3_ACD_DEFEND_TYPE_NONE,
+ .acd_defend_type_is_active = FALSE,
};
c_list_link_tail(&self->priv.p->acd_lst_head, &acd_data->acd_lst);
if (!g_hash_table_add(self->priv.p->acd_lst_hash, acd_data))
nm_assert_not_reached();
acd_track = NULL;
- } else {
+ } else
acd_track = _acd_data_find_track(acd_data, l3cd, obj, tag);
- }
if (!acd_track) {
- acd_track = g_slice_new(AcdTrackData);
+ if (acd_data->info.n_track_infos >= acd_data->n_track_infos_alloc) {
+ acd_data->n_track_infos_alloc = NM_MAX(2u, acd_data->n_track_infos_alloc * 2u);
+ acd_data->info.track_infos =
+ g_realloc((gpointer) acd_data->info.track_infos,
+ acd_data->n_track_infos_alloc * sizeof(acd_data->info.track_infos[0]));
+ }
+ acd_track = (AcdTrackData *) &acd_data->info.track_infos[acd_data->info.n_track_infos++];
*acd_track = (AcdTrackData){
- .l3cd = nm_l3_config_data_ref(l3cd),
- .obj = nmp_object_ref(obj),
- .tag = tag,
- .acd_dirty = FALSE,
- .acd_timeout_msec = acd_timeout_msec,
+ .l3cd = nm_l3_config_data_ref(l3cd),
+ .obj = nmp_object_ref(obj),
+ .tag = tag,
+ .acd_dirty_track = FALSE,
+ .acd_defend_type_track = acd_defend_type,
+ .acd_timeout_msec_track = acd_timeout_msec,
};
- c_list_link_tail(&acd_data->acd_track_lst_head, &acd_track->acd_track_lst);
track_mode = "new";
} else {
- nm_assert(acd_track->acd_dirty);
- acd_track->acd_dirty = FALSE;
- if (acd_track->acd_timeout_msec != acd_timeout_msec) {
- acd_track->acd_timeout_msec = acd_timeout_msec;
- track_mode = "update";
+ nm_assert(acd_track->acd_dirty_track);
+ acd_track->acd_dirty_track = FALSE;
+ if (acd_track->acd_timeout_msec_track != acd_timeout_msec
+ || acd_track->acd_defend_type_track != acd_defend_type) {
+ acd_track->acd_defend_type_track = acd_defend_type;
+ acd_track->acd_timeout_msec_track = acd_timeout_msec;
+ track_mode = "update";
} else
- track_mode = NULL;
+ return;
}
- if (track_mode) {
- _LOGT_acd(acd_data,
- "track " ACD_TRACK_FMT " with timeout %u msec (%s)",
- ACD_TRACK_PTR(acd_track),
- acd_timeout_msec,
- track_mode);
- }
+ acd_data->track_infos_changed = TRUE;
+ _LOGT_acd(
+ acd_data,
+ "track " ACD_TRACK_FMT " with timeout %u msec, defend=%s (%s)",
+ ACD_TRACK_PTR(acd_track),
+ acd_timeout_msec,
+ _l3_acd_defend_type_to_string(acd_track->acd_defend_type_track, sbuf100, sizeof(sbuf100)),
+ track_mode);
}
static void
-_l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint infos_len)
+_l3_acd_data_add_all(NML3Cfg * self,
+ const L3ConfigData *const *infos,
+ guint infos_len,
+ gboolean reapply)
{
AcdData *acd_data;
guint i_info;
+ gint64 now_msec = 0;
+ guint i;
+
+#if NM_MORE_ASSERTS > 5
+ c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+ nm_assert(acd_data->info.n_track_infos > 0u);
+ for (i = 0; i < acd_data->info.n_track_infos; i++)
+ nm_assert(((const AcdTrackData *) &acd_data->info.track_infos[i])->acd_dirty_track);
+ }
+#endif
/* First we add/track all the relevant addresses for ACD. */
for (i_info = 0; i_info < infos_len; i_info++) {
@@ -1408,63 +1566,54 @@ _l3_acd_data_add_all(NML3Cfg *self, const L3ConfigData *const *infos, guint info
NMDedupMultiIter iter;
const NMPObject * obj;
- nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS)
- _l3_acd_data_add(self, info->l3cd, obj, info->tag, info->acd_timeout_msec);
+ nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) {
+ _l3_acd_data_add(self,
+ info->l3cd,
+ obj,
+ info->tag_confdata,
+ info->acd_defend_type_confdata,
+ info->acd_timeout_msec_confdata);
+ }
}
/* Then we do a pre-flight check, whether some of the acd_data entries can already
* move forward to automatically pass ACD. That is the case if acd_timeout_msec
* is zero (to disable ACD) or if the address is already configured on the
* interface. */
- c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_INIT, NULL);
+ c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
+ _l3_acd_data_state_change(self,
+ acd_data,
+ reapply ? ACD_STATE_CHANGE_MODE_INIT_REAPPLY
+ : ACD_STATE_CHANGE_MODE_INIT,
+ NULL,
+ &now_msec);
+ }
}
static gboolean
_l3_acd_data_timeout_cb(gpointer user_data)
{
AcdData *acd_data = user_data;
- NML3Cfg *self = acd_data->self;
+ NML3Cfg *self = acd_data->info.l3cfg;
nm_assert(NM_IS_L3CFG(self));
- nm_clear_g_source_inst(&acd_data->acd_timeout_source);
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL);
+ nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+ _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL, NULL);
return G_SOURCE_REMOVE;
}
static void
-_l3_acd_data_timeout_schedule(AcdData *acd_data,
- gint64 now_msec,
- gint64 expiry_msec,
- gboolean msec_granularity)
+_l3_acd_data_timeout_schedule(AcdData *acd_data, gint64 timeout_msec)
{
- nm_assert(expiry_msec > 0);
- nm_assert(now_msec > 0);
-
- if (acd_data->acd_timeout_source && acd_data->acd_timeout_expiry_msec == expiry_msec)
- return;
-
- nm_clear_g_source_inst(&acd_data->acd_timeout_source);
-
- acd_data->acd_timeout_expiry_msec = expiry_msec;
-
- if (msec_granularity) {
- acd_data->acd_timeout_source = nm_g_timeout_source_new(NM_MAX(0, expiry_msec - now_msec),
- G_PRIORITY_DEFAULT,
- _l3_acd_data_timeout_cb,
- acd_data,
- NULL);
- } else {
- acd_data->acd_timeout_source =
- nm_g_timeout_source_new_seconds((NM_MAX(0, expiry_msec - now_msec) + 999) / 1000,
- G_PRIORITY_DEFAULT,
- _l3_acd_data_timeout_cb,
- acd_data,
- NULL);
- }
-
- g_source_attach(acd_data->acd_timeout_source, NULL);
+ nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+ acd_data->acd_data_timeout_source =
+ nm_g_timeout_source_new(NM_CLAMP((gint64) 0, timeout_msec, (gint64) G_MAXUINT),
+ G_PRIORITY_DEFAULT,
+ _l3_acd_data_timeout_cb,
+ acd_data,
+ NULL);
+ g_source_attach(acd_data->acd_data_timeout_source, NULL);
}
static void
@@ -1475,7 +1624,7 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec
nm_assert(acd_data);
nm_assert(now_msec > 0);
- nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
+ nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING);
nm_assert(!acd_data->nacd_probe);
nm_assert(acd_data->probing_timeout_msec > 0);
nm_assert(acd_data->probing_timestamp_msec > 0);
@@ -1484,145 +1633,161 @@ _l3_acd_data_timeout_schedule_probing_restart(AcdData *acd_data, gint64 now_msec
timeout_msec = NM_MAX(0, expiry_msec - now_msec);
- if (timeout_msec > 1000) {
- /* we poll at least once per second to re-check the state. */
- timeout_msec = 1000;
+ if (timeout_msec > 1500) {
+ /* we poll at least every 1.5 seconds to re-check the state. */
+ timeout_msec = 1500;
}
- _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec + timeout_msec, TRUE);
+ _l3_acd_data_timeout_schedule(acd_data, timeout_msec);
}
static void
-_l3_acd_data_timeout_schedule_probing_full_restart(AcdData *acd_data, gint64 now_msec)
+_nm_l3cfg_emit_signal_notify_acd_event(NML3Cfg *self, AcdData *acd_data)
{
- 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);
-}
+ gs_free NML3AcdAddrTrackInfo *track_infos_clone = NULL;
+ NML3ConfigNotifyData notify_data;
+ NML3AcdAddrInfo * info;
+ guint i;
-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);
+ nm_assert(acd_data->info.state > NM_L3_ACD_ADDR_STATE_INIT);
+ nm_assert(acd_data->info.n_track_infos > 0);
- _l3_acd_data_timeout_schedule(acd_data,
- now_msec,
- now_msec + ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC,
- FALSE);
-}
-
-static void
-_l3_acd_data_notify_acd_completed(NML3Cfg *self, AcdData *acd_data, gboolean force_all)
-{
- gs_free NML3ConfigNotifyPayloadAcdFailedSource *sources_free = NULL;
- NML3ConfigNotifyPayloadAcdFailedSource * sources = NULL;
- NML3ConfigNotifyData notify_data;
- AcdTrackData * acd_track;
- guint i, n;
- NMTernary acd_failed_notified_selector;
-
- nm_assert(NM_IS_L3CFG(self));
- nm_assert(acd_data);
- nm_assert(_acd_data_collect_tracks_data(acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) == 0);
-
- acd_failed_notified_selector = force_all ? NM_TERNARY_DEFAULT : FALSE;
+ notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT;
+ notify_data.acd_event = (typeof(notify_data.acd_event)){
+ .info = acd_data->info,
+ };
- n = _acd_data_collect_tracks_data(acd_data,
- NM_TERNARY_DEFAULT,
- acd_failed_notified_selector,
- NULL);
+ /* we need to clone the track-data, because the callee is allowed to add/remove
+ * configs. This means, the event data is stale. If you need the current
+ * value, look it up with nm_l3cfg_get_acd_addr_info(). */
+ info = &notify_data.acd_event.info;
+ info->track_infos = nm_memdup_maybe_a(300,
+ info->track_infos,
+ info->n_track_infos * sizeof(info->track_infos[0]),
+ &track_infos_clone);
- if (n == 0)
- return;
+ for (i = 0; i < info->n_track_infos; i++) {
+ NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i];
- if (!force_all) {
- _LOGT_acd(acd_data, "state: acd probe failed earlier. Emit notification for new trackers");
+ nmp_object_ref(ti->obj);
+ nm_l3_config_data_ref(ti->l3cd);
}
- if (n * sizeof(sources[0]) > 300) {
- sources_free = g_new(NML3ConfigNotifyPayloadAcdFailedSource, n);
- sources = sources_free;
- } else
- sources = g_newa(NML3ConfigNotifyPayloadAcdFailedSource, n);
-
- i = 0;
- c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
- if (!force_all && acd_track->acd_failed_notified) {
- /* already notified before. Skip. */
- continue;
- }
- nm_assert(i < n);
- acd_track->acd_failed_notified = TRUE;
- sources[i++] = (NML3ConfigNotifyPayloadAcdFailedSource){
- .obj = nmp_object_ref(acd_track->obj),
- .l3cd = nm_l3_config_data_ref(acd_track->l3cd),
- .tag = acd_track->tag,
- };
- }
- nm_assert(i == n);
-
- notify_data.notify_type = NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED;
- notify_data.acd_completed = (typeof(notify_data.acd_completed)){
- .addr = acd_data->addr,
- .probe_result = acd_data->probe_result,
- .sources_len = n,
- .sources = sources,
- };
_nm_l3cfg_emit_signal_notify(self, &notify_data);
- for (i = 0; i < n; i++) {
- nmp_object_unref(sources[i].obj);
- nm_l3_config_data_unref(sources[i].l3cd);
+ for (i = 0; i < info->n_track_infos; i++) {
+ NML3AcdAddrTrackInfo *ti = (NML3AcdAddrTrackInfo *) &info->track_infos[i];
+
+ nmp_object_unref(ti->obj);
+ nm_l3_config_data_unref(ti->l3cd);
}
}
static void
-_l3_acd_data_notify_acd_completed_queue(NML3Cfg *self, AcdData *acd_data)
+_nm_l3cfg_emit_signal_notify_acd_event_queue(NML3Cfg *self, AcdData *acd_data)
{
- if (!c_list_is_empty(&acd_data->acd_notify_complete_lst)) {
- nm_assert(c_list_contains(&self->priv.p->acd_notify_complete_lst_head,
- &acd_data->acd_notify_complete_lst));
+ if (!c_list_is_empty(&acd_data->acd_event_notify_lst)) {
+ nm_assert(c_list_contains(&self->priv.p->acd_event_notify_lst_head,
+ &acd_data->acd_event_notify_lst));
return;
}
- c_list_link_tail(&self->priv.p->acd_notify_complete_lst_head,
- &acd_data->acd_notify_complete_lst);
+ c_list_link_tail(&self->priv.p->acd_event_notify_lst_head, &acd_data->acd_event_notify_lst);
}
static void
-_l3_acd_data_notify_acd_completed_all(NML3Cfg *self)
+_nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self)
{
gs_unref_object NML3Cfg *self_keep_alive = NULL;
AcdData * acd_data;
- while ((acd_data = c_list_first_entry(&self->priv.p->acd_notify_complete_lst_head,
+ while ((acd_data = c_list_first_entry(&self->priv.p->acd_event_notify_lst_head,
AcdData,
- acd_notify_complete_lst))) {
+ acd_event_notify_lst))) {
if (!self_keep_alive)
self_keep_alive = g_object_ref(self);
- c_list_unlink(&acd_data->acd_notify_complete_lst);
- _l3_acd_data_notify_acd_completed(self, acd_data, TRUE);
+ c_list_unlink(&acd_data->acd_event_notify_lst);
+ _nm_l3cfg_emit_signal_notify_acd_event(self, acd_data);
+ }
+}
+
+_nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg * self,
+ AcdData * acd_data,
+ NML3AcdAddrState state,
+ gboolean allow_commit,
+ const char * format,
+ ...)
+{
+ NML3AcdAddrState old_state;
+ gboolean changed;
+
+ if (acd_data->info.state == state)
+ return;
+
+ /* in every state we only have one timer possibly running. Resetting
+ * the states makes the previous timeout obsolete. */
+ nm_clear_g_source_inst(&acd_data->acd_data_timeout_source);
+
+ old_state = acd_data->info.state;
+ acd_data->info.state = state;
+ _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+
+ if (state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED)
+ changed = FALSE;
+ else if (NM_IN_SET(old_state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING)
+ != NM_IN_SET(state, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING))
+ changed = TRUE;
+ else
+ changed = FALSE;
+
+ if (format) {
+ gs_free char *msg = NULL;
+ va_list args;
+
+ va_start(args, format);
+ msg = g_strdup_vprintf(format, args);
+ va_end(args);
+
+ _LOGT_acd(acd_data, "set state to %s (%s)", _l3_acd_addr_state_to_string(state), msg);
+ } else
+ _LOGT_acd(acd_data, "set state to %s", _l3_acd_addr_state_to_string(state));
+
+ if (changed && allow_commit) {
+ /* The availability of an address just changed (and we are instructed to
+ * trigger a new commit). Do it. */
+ _l3_changed_configs_set_dirty(self);
+ nm_l3cfg_commit_on_idle_schedule(self);
}
}
static void
+_l3_acd_data_state_set(NML3Cfg * self,
+ AcdData * acd_data,
+ NML3AcdAddrState state,
+ gboolean allow_commit)
+{
+ _l3_acd_data_state_set_full(self, acd_data, state, allow_commit, NULL);
+}
+
+static void
_l3_acd_data_state_change(NML3Cfg * self,
AcdData * acd_data,
AcdStateChangeMode state_change_mode,
- NAcdEvent * event)
+ const NMEtherAddr *sender_addr,
+ gint64 * p_now_msec)
+
{
- guint32 acd_timeout_msec;
- gint64 now_msec = 0;
- const char *log_reason;
- gboolean was_probing;
+ guint32 acd_timeout_msec;
+ NML3AcdDefendType acd_defend_type;
+ gint64 now_msec;
+ const char * log_reason;
+ char sbuf256[256];
+ char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
+
+ if (!p_now_msec) {
+ now_msec = 0;
+ p_now_msec = &now_msec;
+ }
/* Keeping track of ACD inevitably requires keeping (and mutating) state. Then a multitude of
* things can happen, and depending on the state, we need to do something.
@@ -1642,415 +1807,533 @@ _l3_acd_data_state_change(NML3Cfg * self,
nm_assert(NM_IS_L3CFG(self));
nm_assert(acd_data);
- nm_assert(!c_list_is_empty(&acd_data->acd_track_lst_head));
-
- was_probing = acd_data->acd_state < ACD_STATE_PROBE_DONE;
+ nm_assert(acd_data->info.n_track_infos);
+ nm_assert(NM_IN_SET(acd_data->info.state,
+ NM_L3_ACD_ADDR_STATE_CONFLICT,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING,
+ NM_L3_ACD_ADDR_STATE_INIT,
+ NM_L3_ACD_ADDR_STATE_PROBING,
+ NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+ NM_L3_ACD_ADDR_STATE_USED));
+ nm_assert(!acd_data->track_infos_changed
+ || NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_INIT,
+ ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT,
+ ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
+ ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED));
+ nm_assert((!!sender_addr)
+ == NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_NACD_USED,
+ ACD_STATE_CHANGE_MODE_NACD_CONFLICT,
+ ACD_STATE_CHANGE_MODE_NACD_DEFENDED));
+
+ if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED) {
+ /* once remove, the state can only change by external added or during
+ * the POST-COMMIT check. */
+ if (!NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT,
+ ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED))
+ return;
+ }
switch (state_change_mode) {
case ACD_STATE_CHANGE_MODE_INIT:
- {
- AcdTrackData *acd_track;
- gboolean any_no_timeout;
-
- /* 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. */
+ case ACD_STATE_CHANGE_MODE_INIT_REAPPLY:
+
+ /* We are called right before commit. We check whether we have a acd_data
+ * in INIT or PROBING state. In that case, maybe the new configuration
+ * disables ACD, or we have the address already configured (which also let's
+ * us skip/cancel the probing). The point is that if the address would be ready
+ * already, we want to commit it right away. */
+
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ goto handle_init;
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ if (state_change_mode != ACD_STATE_CHANGE_MODE_INIT_REAPPLY)
return;
+ goto handle_init;
+ }
+ nm_assert_not_reached();
+ return;
+
+handle_init:
+ if (_acd_data_collect_tracks_data(acd_data,
+ NM_TERNARY_FALSE,
+ &acd_timeout_msec,
+ &acd_defend_type)
+ <= 0u) {
+ /* the acd_data has no active trackers. It will soon be pruned. */
+ return;
+ }
+
+ if (acd_timeout_msec == 0u)
+ log_reason = "acd disabled by configuration";
+ else if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr))
+ log_reason = "address already configured";
+ else {
+ if (state_change_mode == ACD_STATE_CHANGE_MODE_INIT_REAPPLY) {
+ /* during a reapply, we forget all the state and start from scratch. */
+ _LOGT_acd(acd_data, "reset state for reapply");
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_INIT, FALSE);
}
- log_reason = "address initially already configured";
- goto handle_probing_acd_good;
+ return;
}
- /* we are called at the end of _l3_acd_data_add_all(). We updated the list of a
- * all tracked IP addresses before we actually collect the addresses that are
- * ready. We don't do regular handling of ACD states at this point, however,
- * we check whether ACD for new elements is disabled entirely, so we can signal
- * the address are ready right away (without going through another hop). */
+ _LOGT_acd(acd_data,
+ "%s probing (%s, during pre-check)",
+ acd_data->info.state == NM_L3_ACD_ADDR_STATE_INIT ? "skip" : "cancel",
+ log_reason);
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ acd_data->acd_defend_type_desired = acd_defend_type;
+ _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_READY, FALSE);
+ return;
+
+ case ACD_STATE_CHANGE_MODE_POST_COMMIT:
+
+ if (acd_data->track_infos_changed) {
+ acd_data->track_infos_changed = FALSE;
+ _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+ }
+
+ if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+ log_reason = "address already configured";
+ goto handle_probing_done;
+ }
- if (acd_data->acd_state != ACD_STATE_INIT) {
- /* this element is not new and we don't perform the quick-check. */
+ if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED)
return;
+
+ /* we just did a commit of the IP configuration and now visit all ACD states
+ * and kick off the necessary actions... */
+ if (_acd_data_collect_tracks_data(acd_data,
+ NM_TERNARY_TRUE,
+ &acd_timeout_msec,
+ &acd_defend_type)
+ <= 0)
+ nm_assert_not_reached();
+
+ acd_data->acd_defend_type_desired = acd_defend_type;
+
+ if (acd_timeout_msec <= 0) {
+ log_reason = "acd disabled by configuration";
+ goto handle_probing_done;
}
- 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;
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ nm_assert(!acd_data->nacd_probe);
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+ acd_data->probing_timestamp_msec = (*p_now_msec);
+ acd_data->probing_timeout_msec = acd_timeout_msec;
+ _nm_l3cfg_emit_signal_notify_acd_event_queue(self, acd_data);
+ log_reason = "initial post-commit";
+ goto handle_start_probing;
+
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ {
+ gint64 old_expiry_msec;
+ gint64 new_expiry_msec;
+
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+
+ new_expiry_msec = (*p_now_msec) + acd_timeout_msec;
+ old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec;
+
+ if (!acd_data->nacd_probe) {
+ /* we are currently waiting for restarting a probe. At this point, at most we have
+ * to adjust the timeout/timestamp and let the regular timeouts handle this. */
+
+ if (new_expiry_msec >= old_expiry_msec) {
+ /* the running timeout expires before the new timeout. We don't update the timestamp/timeout,
+ * because we don't want to prolong the overall probing time. */
+ return;
+ }
+ /* update the timers after out timeout got reduced. Also, reschedule the timeout
+ * so that it expires immediately. */
+ acd_data->probing_timestamp_msec = (*p_now_msec);
+ acd_data->probing_timeout_msec = acd_timeout_msec;
+ _l3_acd_data_timeout_schedule(acd_data, 0);
+ return;
}
+
+ if (new_expiry_msec >= old_expiry_msec) {
+ /* we already have ACD running with a timeout that expires before the requested one. There
+ * is nothing to do at this time. */
+ return;
+ }
+
+ /* the timeout got reduced. We try to restart the probe. */
+ acd_data->probing_timestamp_msec = (*p_now_msec);
+ acd_data->probing_timeout_msec = acd_timeout_msec;
+ log_reason = "post-commit timeout update";
+ goto handle_start_probing;
}
- if (!any_no_timeout) {
- /* there are elements that request the address, but they all specify
- * an ACD timeout. We cannot progress the state. */
+
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ /* we are done for now. We however scheduled a timeout to restart. This
+ * will be handled with the ACD_STATE_CHANGE_MODE_TIMEOUT event. */
return;
- }
- /* ACD is disabled, we can artificially moving the state further to
- * ACD_STATE_PROBE_DONE and configure the address right away. This avoids
- * that we go through another hop.
- */
- log_reason = "ACD disabled by configuration from the start";
- goto handle_probing_acd_good;
- }
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ goto handle_start_defending;
- case ACD_STATE_CHANGE_MODE_POST_COMMIT:
- acd_data->initializing = FALSE;
- goto handle_post_commit;
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ nm_assert_not_reached();
+ return;
+ }
+ nm_assert_not_reached();
+ return;
case ACD_STATE_CHANGE_MODE_TIMEOUT:
- {
- if (acd_data->acd_state == ACD_STATE_PROBING && !acd_data->nacd_probe) {
- const char *failure_reason;
- gboolean acd_not_supported;
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ nm_assert_not_reached();
+ return;
+
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ if (acd_data->nacd_probe) {
+ /* we are already probing. There is nothing to do for this timeout. */
+ return;
+ }
+
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC
+ ACD_WAIT_PROBING_EXTRA_TIME2_MSEC
- >= now_msec) {
+ >= (*p_now_msec)) {
+ /* hm. We failed to create a new probe too long. Something is really wrong
+ * internally, but let's ignore the issue and assume the address is good. What
+ * else would we do? Assume the address is USED? */
_LOGT_acd(acd_data,
- "state: probe-good (waiting for creating probe timed out. Assume good)");
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
+ "probe-good (waiting for creating probe timed out. Assume good)");
+ goto handle_start_defending;
}
- /* try create a new probe. The timeout is always as originally requested. */
- acd_data->nacd_probe =
- _l3_acd_nacd_instance_create_probe(self,
- acd_data->addr,
- acd_data->probing_timeout_msec,
- acd_data,
- &acd_not_supported,
- &failure_reason);
- if (acd_not_supported) {
- nm_assert(!acd_data->nacd_probe);
- _LOGT_acd(
- acd_data,
- "state: probe-good (interface does not support ACD anymore after timeout)");
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
+ log_reason = "retry probing on timeout";
+ goto handle_start_probing;
+
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+
+ nm_assert(!acd_data->nacd_probe);
+
+ /* after a timeout, re-probe the address. This only happens if the caller
+ * does not deconfigure the address after USED/CONFLICT. But in that case,
+ * we eventually want to retry. */
+ if (_acd_data_collect_tracks_data(acd_data,
+ NM_TERNARY_TRUE,
+ &acd_timeout_msec,
+ &acd_defend_type)
+ <= 0)
+ nm_assert_not_reached();
+
+ acd_data->acd_defend_type_desired = acd_defend_type;
+
+ if (acd_timeout_msec <= 0) {
+ log_reason = "acd disabled by configuration (restart after previous conflict)";
+ goto handle_probing_done;
}
- if (!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;
+ if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+ log_reason = "address already configured (restart after previous conflict)";
+ goto handle_probing_done;
}
- /* 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);
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
+ acd_data->probing_timestamp_msec = (*p_now_msec);
+ acd_data->probing_timeout_msec = acd_timeout_msec;
+ if (acd_data->info.state == NM_L3_ACD_ADDR_STATE_USED)
+ log_reason = "restart probing after previously used address";
+ else
+ log_reason = "restart probing after previous conflict";
+ goto handle_start_probing;
+
+ case NM_L3_ACD_ADDR_STATE_READY:
+ nm_assert_not_reached();
return;
- }
- 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. */
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+
nm_assert(!acd_data->nacd_probe);
- nm_assert(!acd_data->announcing_failed_is_retrying);
+ _LOGT_acd(acd_data, "retry announcing address");
+ goto handle_start_defending;
- _LOGT_acd(acd_data, "state: restart a new probe after previous conflict");
- acd_data->acd_state = ACD_STATE_INIT;
- goto handle_post_commit;
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ nm_assert_not_reached();
+ return;
}
- 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;
+ nm_assert_not_reached();
+ return;
+
+ case ACD_STATE_CHANGE_MODE_NACD_USED:
+ nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+ nm_assert(acd_data->nacd_probe);
+
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ acd_data->last_conflict_addr = *sender_addr;
+ _l3_acd_data_state_set_full(self,
+ acd_data,
+ NM_L3_ACD_ADDR_STATE_USED,
+ TRUE,
+ "acd completed with address already in use by %s",
+ nm_ether_addr_to_string_a(sender_addr));
+
+ if (!acd_data->acd_data_timeout_source)
+ _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC);
+
+ if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) {
+ _LOGI("IPv4 address %s is used on network connected to interface %d%s%s%s from "
+ "host %s",
+ _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+ self->priv.ifindex,
+ NM_PRINT_FMT_QUOTED(self->priv.plobj_next,
+ " (",
+ NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name,
+ ")",
+ ""),
+ nm_ether_addr_to_string_a(sender_addr));
}
+ return;
+ case ACD_STATE_CHANGE_MODE_NACD_DEFENDED:
+ nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+ _LOGT_acd(acd_data,
+ "address %s defended from %s",
+ _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+ nm_ether_addr_to_string_a(sender_addr));
+ /* we just log an info message. Nothing else to do. */
return;
- }
- case ACD_STATE_CHANGE_MODE_NACD_READY:
- if (acd_data->acd_state == ACD_STATE_PROBING) {
- log_reason = "acd indicates ready";
- goto handle_probing_acd_good;
+ case ACD_STATE_CHANGE_MODE_NACD_CONFLICT:
+ nm_assert(acd_data->info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+
+ _LOGT_acd(acd_data,
+ "address conflict for %s detected with %s",
+ _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+ nm_ether_addr_to_string_a(sender_addr));
+
+ if (!_l3_acd_data_defendconflict_warning_ratelimited(acd_data, p_now_msec)) {
+ _LOGW("IPv4 address collision detection sees conflict on interface %d%s%s%s for "
+ "address %s from host %s",
+ self->priv.ifindex,
+ NM_PRINT_FMT_QUOTED(self->priv.plobj_next,
+ " (",
+ NMP_OBJECT_CAST_LINK(self->priv.plobj_next)->name,
+ ")",
+ ""),
+ _nm_utils_inet4_ntop(acd_data->info.addr, sbuf_addr),
+ nm_ether_addr_to_string_a(sender_addr));
}
- 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)
+
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ acd_data->last_conflict_addr = *sender_addr;
+ _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_CONFLICT, TRUE);
+ if (!acd_data->acd_data_timeout_source)
+ _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_CONFLICT_RESTART_MSEC);
+ return;
+
+ case ACD_STATE_CHANGE_MODE_NACD_READY:
+
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ nm_assert(acd_data->nacd_probe);
+ /* we theoretically could re-use this probe for defending. But as we
+ * may not start defending right away, it makes it more complicated. */
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ log_reason = "acd indicates ready";
+ goto handle_probing_done;
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ nm_assert(!acd_data->acd_defend_type_is_active);
+ acd_data->acd_defend_type_is_active = TRUE;
+ _LOGT_acd(acd_data,
+ "start announcing (defend=%s) (after new probe ready)",
+ _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+ sbuf256,
+ sizeof(sbuf256)));
+ if (n_acd_probe_announce(acd_data->nacd_probe,
+ _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current))
+ != 0)
nm_assert_not_reached();
return;
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ nm_assert_not_reached();
+ return;
}
- /* 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;
+ case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED:
- 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;
- }
+ if (self->priv.p->commit_reentrant_count > 0)
+ return;
- case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED:
- /* the address is configured on the link. This means, ACD passed */
- log_reason = "address configured on link";
- goto handle_probing_acd_good;
+ _LOGT_acd(acd_data, "address was externally added");
- 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;
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ nm_assert_not_reached();
+ return;
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ goto handle_start_defending;
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ log_reason = "address configured on link";
+ goto handle_probing_done;
+ }
- 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_not_reached();
+ return;
- nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
+ case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED:
- 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;
- }
+ if (self->priv.p->commit_reentrant_count > 0)
+ 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);
+ if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+ /* this can happen, because there might still be the same address with different
+ * plen or peer_address. */
return;
}
- /* 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. */
+ _LOGT_acd(acd_data, "address was externally removed");
+
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ _l3_acd_data_state_set(self, acd_data, NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, FALSE);
return;
+ case ACD_STATE_CHANGE_MODE_NACD_DOWN:
case ACD_STATE_CHANGE_MODE_LINK_NOW_UP:
- /* The interface just came up. */
-
- if (acd_data->acd_state <= ACD_STATE_PROBING) {
- nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL;
- const char * failure_reason;
- gboolean acd_not_supported;
-
- /* the interface was probing. We will restart the probe. */
- nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
-
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ nm_assert_not_reached();
+ return;
+ case NM_L3_ACD_ADDR_STATE_PROBING:
if (!acd_data->nacd_probe) {
- /* We currently are waiting to restart probing. We don't handle the link-up
- * event here, we only trigger a timeout right away. */
+ /* we failed starting to probe before and have a timer running to
+ * restart. We don't do anything now, but let the timer handle it.
+ * This also implements some rate limiting for us. */
_LOGT_acd(acd_data,
- "state: ignore link up event while we are waiting to start probing");
- _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+ "ignore link %s event while we are waiting to start probing",
+ state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN ? "down" : "up");
return;
}
- if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_RESTART_TIME_MSEC >= now_msec) {
- /* This probe was already started quite a while ago. We ignore the link-up event
- * and let it complete regularly. This is to avoid restarting to probing indefinitely. */
- _LOGT_acd(acd_data, "state: ignore link up event for a probe started long ago");
- return;
- }
+ nm_utils_get_monotonic_timestamp_msec_cached(p_now_msec);
- 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);
+ if (acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec
+ + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC
+ >= (*p_now_msec)) {
+ /* The probing already started quite a while ago. We ignore the link event
+ * and let the probe come to it's natural end. */
+ _LOGT_acd(acd_data, "ignore link up event for a probe started long ago");
return;
}
- 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);
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ if (state_change_mode == ACD_STATE_CHANGE_MODE_NACD_DOWN)
+ log_reason = "restart probing after down event";
+ else
+ log_reason = "restart probing after link up";
+ goto handle_start_probing;
+
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ /* if the link was down/came up, it's no clear what we should do about these
+ * cases. Ignore the event. */
return;
}
-
- /* we are already done with the ACD state. Bringing up an interface has
- * no further consequence w.r.t. the ACD state. */
+ nm_assert_not_reached();
return;
case ACD_STATE_CHANGE_MODE_INSTANCE_RESET:
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- if (acd_data->acd_state <= ACD_STATE_PROBING) {
- nm_assert(acd_data->acd_state == ACD_STATE_PROBING);
-
- _LOGT_acd(
- acd_data,
- "state: n-acd instance reset. Trigger a restart of the probing (was %sprobing)",
- acd_data->nacd_probe ? "" : "not");
- /* Just destroy the current probe (if any) and retrigger a restart right away. */
- acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
- _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ nm_assert_not_reached();
return;
- }
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+
+ if (!acd_data->nacd_probe) {
+ /* we failed starting to probe before and have a timer running to
+ * restart. We don't do anything now, but let the timer handle it.
+ * This also implements some rate limiting for us. */
+ _LOGT_acd(acd_data,
+ "n-acd instance reset. Ignore event while restarting %s",
+ (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing"
+ : "defending");
+ return;
+ }
- 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");
+ "n-acd instance reset. Trigger a restart of the %s",
+ (acd_data->info.state == NM_L3_ACD_ADDR_STATE_PROBING) ? "probing"
+ : "defending");
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
+ _l3_acd_data_timeout_schedule(acd_data, 0);
+ return;
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_USED:
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ nm_assert(!acd_data->nacd_probe);
+ return;
}
- acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
+ nm_assert_not_reached();
return;
}
nm_assert_not_reached();
return;
-handle_post_commit:
- /* we just did a commit of the IP configuration and now visit all ACD states
- * and kick off the necessary actions... */
- if (_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->addr)) {
- log_reason = "address already configured";
- goto handle_probing_acd_good;
- }
- if (_acd_data_collect_tracks_data(acd_data, TRUE, NM_TERNARY_DEFAULT, &acd_timeout_msec) <= 0)
- nm_assert_not_reached();
- if (acd_timeout_msec <= 0) {
- log_reason = "ACD disabled by configuration";
- goto handle_probing_acd_good;
- }
-
- switch (acd_data->acd_state) {
- case ACD_STATE_INIT:
- {
- const char *failure_reason;
- gboolean acd_not_supported;
- NAcdProbe * probe;
-
- nm_assert(!acd_data->nacd_probe);
-
- probe = _l3_acd_nacd_instance_create_probe(self,
- acd_data->addr,
- acd_timeout_msec,
- acd_data,
- &acd_not_supported,
- &failure_reason);
- if (acd_not_supported) {
- nm_assert(!probe);
- _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD)");
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
- }
-
- if (!probe) {
- _LOGT_acd(acd_data,
- "state: probing currently not possible (timeout %u msec; %s)",
- acd_timeout_msec,
- failure_reason);
- acd_data->acd_state = ACD_STATE_PROBING;
- acd_data->probing_timeout_msec = acd_timeout_msec;
- acd_data->probing_timestamp_msec =
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
- return;
- }
-
- _LOGT_acd(acd_data, "state: start probing (timeout %u msec)", acd_timeout_msec);
- acd_data->acd_state = ACD_STATE_PROBING;
- acd_data->nacd_probe = probe;
- acd_data->probing_timeout_msec = acd_timeout_msec;
- acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- return;
- }
-
- case ACD_STATE_PROBING:
- {
- nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL;
+handle_start_probing:
+ if (TRUE) {
+ const NML3AcdAddrState orig_state = acd_data->info.state;
+ nm_auto(n_acd_probe_freep) NAcdProbe *probe = NULL;
const char * failure_reason;
gboolean acd_not_supported;
- gint64 old_expiry_msec;
- gint64 new_expiry_msec;
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
+ nm_assert(NM_IN_SET(acd_data->info.state,
+ NM_L3_ACD_ADDR_STATE_INIT,
+ NM_L3_ACD_ADDR_STATE_PROBING,
+ NM_L3_ACD_ADDR_STATE_USED,
+ NM_L3_ACD_ADDR_STATE_CONFLICT));
- new_expiry_msec = now_msec + acd_timeout_msec;
- old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec;
-
- 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. */
+ /* note that we reach this line also during a ACD_STATE_CHANGE_MODE_TIMEOUT, when
+ * or when we restart the probing (with a new timeout). In all cases, we still
+ * give the original timeout (acd_data->probing_timeout_msec), and not the remaining
+ * time. That means, the probing step might take longer then originally planned
+ * (e.g. if we initially cannot start probing right away). */
- if (new_expiry_msec >= old_expiry_msec) {
- /* the running timeout expires before the new timeout. We don't update the timestamp/timerout,
- * because we don't want to prolong the overall probing time. */
- return;
- }
- /* update the timers after out timeout got reduced. Also, reschedule the timeout
- * so that it expires immediately. */
- acd_data->probing_timestamp_msec = now_msec;
- acd_data->probing_timeout_msec = acd_timeout_msec;
- _l3_acd_data_timeout_schedule(acd_data, now_msec, now_msec, TRUE);
- return;
- }
-
- if (new_expiry_msec >= old_expiry_msec) {
- /* we already have ACD running with a timeout that expires before the requested one. There
- * is nothing to do at this time. */
- return;
- }
-
- /* the timeout got reduced. We try to restart the probe. */
probe = _l3_acd_nacd_instance_create_probe(self,
- acd_data->addr,
- acd_timeout_msec,
+ acd_data->info.addr,
+ acd_data->probing_timeout_msec,
acd_data,
&acd_not_supported,
&failure_reason);
@@ -2058,143 +2341,144 @@ handle_post_commit:
if (acd_not_supported) {
nm_assert(!acd_data->nacd_probe);
- _LOGT_acd(acd_data, "state: probe-good (interface does not support ACD anymore)");
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
+ _LOGT_acd(acd_data,
+ "probe-good (interface does not support acd%s, %s)",
+ orig_state == NM_L3_ACD_ADDR_STATE_INIT ? ""
+ : (state_change_mode != ACD_STATE_CHANGE_MODE_TIMEOUT)
+ ? " anymore"
+ : " anymore after timeout",
+ log_reason);
+ goto handle_start_defending;
}
+ _l3_acd_data_state_set(self,
+ acd_data,
+ NM_L3_ACD_ADDR_STATE_PROBING,
+ !NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_INIT,
+ ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT));
+
if (!acd_data->nacd_probe) {
_LOGT_acd(acd_data,
- "state: probing currently still not possible (timeout %u msec; %s)",
- acd_timeout_msec,
- failure_reason);
- acd_data->acd_state = ACD_STATE_PROBING;
- acd_data->probing_timeout_msec = acd_timeout_msec;
- acd_data->probing_timestamp_msec = now_msec;
- _l3_acd_data_timeout_schedule_probing_restart(acd_data, now_msec);
+ "probing currently %snot possible (timeout %u msec; %s, %s)",
+ orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : " still",
+ acd_data->probing_timeout_msec,
+ failure_reason,
+ log_reason);
+ _l3_acd_data_timeout_schedule_probing_restart(acd_data, (*p_now_msec));
return;
}
- /* We update the timestamps (after also restarting the probe).
- *
- * Note that we only reduced the overall expiry. */
- acd_data->probing_timestamp_msec = now_msec;
- acd_data->probing_timeout_msec = acd_timeout_msec;
- _LOGT_acd(acd_data, "state: restart probing (timeout %u msec)", acd_timeout_msec);
+ _LOGT_acd(acd_data,
+ "%sstart probing (timeout %u msec, %s)",
+ orig_state == NM_L3_ACD_ADDR_STATE_INIT ? "" : "re",
+ acd_data->probing_timeout_msec,
+ log_reason);
return;
}
- case ACD_STATE_PROBE_DONE:
- case ACD_STATE_ANNOUNCING:
- goto handle_probe_done;
- }
- nm_assert_not_reached();
- return;
-
-handle_probing_acd_good:
- switch (acd_data->acd_state) {
- case ACD_STATE_INIT:
- _LOGT_acd(acd_data, "state: probe-done good (%s, initializing)", log_reason);
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
- case ACD_STATE_PROBING:
- _LOGT_acd(acd_data, "state: probe-done good (%s, probing done)", log_reason);
+handle_probing_done:
+ switch (acd_data->info.state) {
+ case NM_L3_ACD_ADDR_STATE_INIT:
+ _LOGT_acd(acd_data, "probe-done good (%s, initializing)", log_reason);
+ goto handle_start_defending;
+ case NM_L3_ACD_ADDR_STATE_PROBING:
+ _LOGT_acd(acd_data, "probe-done good (%s, probing done)", log_reason);
if (state_change_mode != ACD_STATE_CHANGE_MODE_NACD_READY)
acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
- acd_data->acd_state = ACD_STATE_PROBE_DONE;
- acd_data->probe_result = TRUE;
- goto handle_probe_done;
- case ACD_STATE_PROBE_DONE:
- if (!acd_data->probe_result) {
- nm_assert(!acd_data->nacd_probe);
- _LOGT_acd(acd_data, "state: probe-done good (%s, after probe failed)", log_reason);
- acd_data->probe_result = TRUE;
- }
- goto handle_probe_done;
- case ACD_STATE_ANNOUNCING:
- nm_assert(acd_data->probe_result);
- goto handle_probe_done;
- }
- nm_assert_not_reached();
- return;
-
-handle_probe_done:
- nm_assert(NM_IN_SET(acd_data->acd_state, ACD_STATE_PROBE_DONE, ACD_STATE_ANNOUNCING));
-
- if (acd_data->initializing)
+ goto handle_start_defending;
+ case NM_L3_ACD_ADDR_STATE_USED:
+ _LOGT_acd(acd_data, "probe-done good (%s, after probe failed)", log_reason);
+ goto handle_start_defending;
+ case NM_L3_ACD_ADDR_STATE_READY:
+ case NM_L3_ACD_ADDR_STATE_DEFENDING:
+ goto handle_start_defending;
+ case NM_L3_ACD_ADDR_STATE_CONFLICT:
return;
-
- if (acd_data->acd_state >= ACD_STATE_ANNOUNCING) {
- nm_assert(acd_data->nacd_probe);
- nm_assert(acd_data->probe_result);
+ case NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED:
+ nm_assert_not_reached();
return;
}
+ nm_assert_not_reached();
+ return;
- if (!acd_data->probe_result) {
- nm_assert(acd_data->acd_state == ACD_STATE_PROBE_DONE);
- nm_assert(!acd_data->nacd_probe);
- /* we just completed probing with negative result.
- * Emit a signal, but also reschedule a timer to restart. */
- if (was_probing) {
- _LOGT_acd(acd_data, "state: acd probe failed; signal failure");
- acd_data->probing_timestamp_msec =
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- _l3_acd_data_timeout_schedule_probing_full_restart(acd_data, now_msec);
+handle_start_defending:
+ if (!_l3_acd_ipv4_addresses_on_link_contains(self, acd_data->info.addr)) {
+ if (acd_data->info.state != NM_L3_ACD_ADDR_STATE_READY) {
+ _l3_acd_data_state_set_full(self,
+ acd_data,
+ NM_L3_ACD_ADDR_STATE_READY,
+ !NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_INIT,
+ ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT),
+ "probe is ready, waiting for address to be configured");
}
- _l3_acd_data_notify_acd_completed_queue(self, acd_data);
return;
}
- if (was_probing && acd_data->probe_result) {
- /* probing just completed. Schedule handling the change. */
- _LOGT_acd(acd_data, "state: acd probe succeed");
- _l3_acd_data_notify_acd_completed_queue(self, acd_data);
- if (state_change_mode != ACD_STATE_CHANGE_MODE_POST_COMMIT)
- _l3_changed_configs_set_dirty(self);
- nm_l3cfg_commit_on_idle_schedule(self);
+ _l3_acd_data_state_set(self,
+ acd_data,
+ NM_L3_ACD_ADDR_STATE_DEFENDING,
+ !NM_IN_SET(state_change_mode,
+ ACD_STATE_CHANGE_MODE_INIT,
+ ACD_STATE_CHANGE_MODE_INIT_REAPPLY,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT));
+
+ nm_assert(acd_data->acd_defend_type_desired > NM_L3_ACD_DEFEND_TYPE_NONE);
+ nm_assert(acd_data->acd_defend_type_desired <= NM_L3_ACD_DEFEND_TYPE_ALWAYS);
+
+ if (acd_data->acd_defend_type_desired != acd_data->acd_defend_type_current) {
+ acd_data->acd_defend_type_current = acd_data->acd_defend_type_desired;
+ acd_data->nacd_probe = n_acd_probe_free(acd_data->nacd_probe);
}
if (!acd_data->nacd_probe) {
const char *failure_reason;
NAcdProbe * probe;
- if (acd_data->announcing_failed_is_retrying) {
+ if (acd_data->acd_data_timeout_source) {
/* we already failed to create a probe. We are ratelimited to retry, but
* we have a timer pending... */
return;
}
probe = _l3_acd_nacd_instance_create_probe(self,
- acd_data->addr,
+ acd_data->info.addr,
0,
acd_data,
NULL,
&failure_reason);
if (!probe) {
- /* we failed to create a probe for announcing the address. We log a (rate limited)
- * warning and start a timer to retry. */
- _LOGT_acd(acd_data,
- "state: start announcing failed to create probe (%s)",
- failure_reason);
- acd_data->announcing_failed_is_retrying = TRUE;
- acd_data->probing_timestamp_msec =
- nm_utils_get_monotonic_timestamp_msec_cached(&now_msec);
- _l3_acd_data_timeout_schedule_announce_restart(acd_data, now_msec);
+ /* we failed to create a probe for announcing the address. We log a
+ * warning and start a timer to retry. This way (of having a timer pending)
+ * we also back off and are rate limited from retrying too frequently. */
+ _LOGT_acd(acd_data, "start announcing failed to create probe (%s)", failure_reason);
+ _l3_acd_data_timeout_schedule(acd_data, ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC);
return;
}
- _LOGT_acd(acd_data, "state: start announcing (with new probe)");
- acd_data->nacd_probe = probe;
- acd_data->acd_state = ACD_STATE_ANNOUNCING;
+ _LOGT_acd(acd_data,
+ "start announcing (defend=%s) (probe created)",
+ _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+ sbuf256,
+ sizeof(sbuf256)));
+ acd_data->acd_defend_type_is_active = FALSE;
+ acd_data->nacd_probe = probe;
return;
}
- if (acd_data->acd_state == ACD_STATE_PROBE_DONE) {
- _LOGT_acd(acd_data, "state: start announcing (with existing probe)");
- acd_data->acd_state = ACD_STATE_ANNOUNCING;
- if (n_acd_probe_announce(acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0)
+ if (!acd_data->acd_defend_type_is_active) {
+ acd_data->acd_defend_type_is_active = TRUE;
+ _LOGT_acd(acd_data,
+ "start announcing (defend=%s) (with existing probe)",
+ _l3_acd_defend_type_to_string(acd_data->acd_defend_type_current,
+ sbuf256,
+ sizeof(sbuf256)));
+ if (n_acd_probe_announce(acd_data->nacd_probe,
+ _l3_acd_defend_type_to_nacd(acd_data->acd_defend_type_current))
+ != 0)
nm_assert_not_reached();
return;
}
@@ -2203,28 +2487,47 @@ handle_probe_done:
static void
_l3_acd_data_process_changes(NML3Cfg *self)
{
- gboolean acd_is_announcing = FALSE;
- gboolean acd_is_pending = FALSE;
+ gboolean acd_is_pending = FALSE;
+ gboolean acd_busy = FALSE;
AcdData *acd_data;
+ gint64 now_msec = 0;
_l3_acd_data_prune(self, FALSE);
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
- _l3_acd_data_state_change(self, acd_data, ACD_STATE_CHANGE_MODE_POST_COMMIT, NULL);
- if (acd_data->acd_state < ACD_STATE_PROBE_DONE)
+ _l3_acd_data_state_change(self,
+ acd_data,
+ ACD_STATE_CHANGE_MODE_POST_COMMIT,
+ NULL,
+ &now_msec);
+ if (acd_data->info.state <= NM_L3_ACD_ADDR_STATE_PROBING)
acd_is_pending = TRUE;
- else if (acd_data->acd_state >= ACD_STATE_ANNOUNCING
- || (acd_data->acd_state >= ACD_STATE_PROBE_DONE && acd_data->probe_result))
- acd_is_announcing = TRUE;
+ if (acd_data->nacd_probe)
+ acd_busy = TRUE;
}
- self->priv.p->acd_is_pending = acd_is_pending;
- self->priv.p->acd_is_announcing = acd_is_announcing;
+ self->priv.p->acd_is_pending = acd_is_pending;
- if (!acd_is_pending && !acd_is_announcing)
+ if (!acd_busy)
_l3_acd_nacd_instance_reset(self, NM_TERNARY_DEFAULT, FALSE);
- _l3_acd_data_notify_acd_completed_all(self);
+ _nm_l3cfg_emit_signal_notify_acd_event_all(self);
+}
+
+/*****************************************************************************/
+
+const NML3AcdAddrInfo *
+nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr)
+{
+ AcdData *acd_data;
+
+ nm_assert(NM_IS_L3CFG(self));
+
+ acd_data = _l3_acd_data_find(self, addr);
+ if (!acd_data)
+ return NULL;
+
+ return &acd_data->info;
}
/*****************************************************************************/
@@ -2236,7 +2539,7 @@ _l3_commit_on_idle_cb(gpointer user_data)
nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
- _LOGT("platform commit on idle");
+ _LOGT("commit on idle");
_l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE);
return G_SOURCE_REMOVE;
}
@@ -2249,6 +2552,7 @@ nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self)
if (self->priv.p->commit_on_idle_source)
return;
+ _LOGT("commit on idle (scheduled)");
self->priv.p->commit_on_idle_source =
nm_g_idle_source_new(G_PRIORITY_DEFAULT, _l3_commit_on_idle_cb, self, NULL);
g_source_attach(self->priv.p->commit_on_idle_source, NULL);
@@ -2273,7 +2577,7 @@ _l3_config_datas_find_next(GArray * l3_config_datas,
for (i = start_idx; i < l3_config_datas->len; i++) {
const L3ConfigData *l3_config_data = _l3_config_datas_at(l3_config_datas, i);
- if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag)
+ if (NM_IN_SET(needle_tag, NULL, l3_config_data->tag_confdata)
&& NM_IN_SET(needle_l3cd, NULL, l3_config_data->l3cd))
return i;
}
@@ -2292,54 +2596,15 @@ _l3_config_datas_get_sorted_cmp(gconstpointer p_a, gconstpointer p_b, gpointer u
/* we sort the entries with higher priority (more important, lower numerical value)
* first. */
- NM_CMP_FIELD(a, b, priority);
+ NM_CMP_FIELD(a, b, priority_confdata);
/* if the priority is not unique, we sort them in the order they were added,
* with the oldest first (lower numerical value). */
- NM_CMP_FIELD(a, b, pseudo_timestamp);
+ NM_CMP_FIELD(a, b, pseudo_timestamp_confdata);
return nm_assert_unreachable_val(0);
}
-#define _l3_config_datas_get_sorted_a(l3_config_datas, out_infos, out_infos_len, out_infos_free) \
- G_STMT_START \
- { \
- GArray *const _l3_config_datas = (l3_config_datas); \
- const L3ConfigData *const **const _out_infos = (out_infos); \
- guint *const _out_infos_len = (out_infos_len); \
- const L3ConfigData ***const _out_infos_free = (out_infos_free); \
- gs_free const L3ConfigData **_infos_free = NULL; \
- const L3ConfigData ** _infos; \
- guint _l3_config_datas_len; \
- guint _i; \
- \
- _l3_config_datas_len = nm_g_array_len(_l3_config_datas); \
- \
- if (_l3_config_datas_len == 0) \
- _infos = NULL; \
- else if (_l3_config_datas_len < 300 / sizeof(_infos[0])) \
- _infos = g_alloca(_l3_config_datas_len * sizeof(_infos[0])); \
- else { \
- _infos_free = g_new(const L3ConfigData *, _l3_config_datas_len); \
- _infos = _infos_free; \
- } \
- for (_i = 0; _i < _l3_config_datas_len; _i++) \
- _infos[_i] = _l3_config_datas_at(_l3_config_datas, _i); \
- \
- if (_l3_config_datas_len > 1) { \
- g_qsort_with_data(_infos, \
- _l3_config_datas_len, \
- sizeof(_infos[0]), \
- _l3_config_datas_get_sorted_cmp, \
- NULL); \
- } \
- \
- *_out_infos = _infos; \
- *_out_infos_len = _l3_config_datas_len; \
- *_out_infos_free = g_steal_pointer(&_infos_free); \
- } \
- G_STMT_END
-
static void
_l3_config_datas_remove_index_fast(GArray *arr, guint idx)
{
@@ -2374,7 +2639,7 @@ nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty)
if (idx < 0)
return;
- _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty = dirty;
+ _l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata = dirty;
idx++;
}
}
@@ -2391,6 +2656,7 @@ nm_l3cfg_add_config(NML3Cfg * self,
guint32 default_route_metric_6,
guint32 default_route_penalty_4,
guint32 default_route_penalty_6,
+ NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec,
NML3ConfigMergeFlags merge_flags)
{
@@ -2402,6 +2668,11 @@ nm_l3cfg_add_config(NML3Cfg * self,
nm_assert(tag);
nm_assert(l3cd);
nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex);
+ nm_assert(acd_timeout_msec < ACD_MAX_TIMEOUT_MSEC);
+ nm_assert(NM_IN_SET(acd_defend_type,
+ NM_L3_ACD_DEFEND_TYPE_NEVER,
+ NM_L3_ACD_DEFEND_TYPE_ONCE,
+ NM_L3_ACD_DEFEND_TYPE_ALWAYS));
nm_assert(default_route_metric_6 != 0u); /* IPv6 default route metric cannot be zero. */
@@ -2447,29 +2718,30 @@ nm_l3cfg_add_config(NML3Cfg * self,
if (idx < 0) {
l3_config_data = nm_g_array_append_new(self->priv.p->l3_config_datas, L3ConfigData);
*l3_config_data = (L3ConfigData){
- .tag = tag,
- .l3cd = nm_l3_config_data_ref_and_seal(l3cd),
- .merge_flags = merge_flags,
- .default_route_table_4 = default_route_table_4,
- .default_route_table_6 = default_route_table_6,
- .default_route_metric_4 = default_route_metric_4,
- .default_route_metric_6 = default_route_metric_6,
- .default_route_penalty_4 = default_route_penalty_4,
- .default_route_penalty_6 = default_route_penalty_6,
- .acd_timeout_msec = acd_timeout_msec,
- .priority = priority,
- .pseudo_timestamp = ++self->priv.p->pseudo_timestamp_counter,
- .dirty = FALSE,
+ .tag_confdata = tag,
+ .l3cd = nm_l3_config_data_ref_and_seal(l3cd),
+ .merge_flags = merge_flags,
+ .default_route_table_4 = default_route_table_4,
+ .default_route_table_6 = default_route_table_6,
+ .default_route_metric_4 = default_route_metric_4,
+ .default_route_metric_6 = default_route_metric_6,
+ .default_route_penalty_4 = default_route_penalty_4,
+ .default_route_penalty_6 = default_route_penalty_6,
+ .acd_defend_type_confdata = acd_defend_type,
+ .acd_timeout_msec_confdata = acd_timeout_msec,
+ .priority_confdata = priority,
+ .pseudo_timestamp_confdata = ++self->priv.p->pseudo_timestamp_counter,
+ .dirty_confdata = FALSE,
};
changed = TRUE;
} else {
- l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx);
- l3_config_data->dirty = FALSE;
- nm_assert(l3_config_data->tag == tag);
+ l3_config_data = _l3_config_datas_at(self->priv.p->l3_config_datas, idx);
+ l3_config_data->dirty_confdata = FALSE;
+ nm_assert(l3_config_data->tag_confdata == tag);
nm_assert(l3_config_data->l3cd == l3cd);
- if (l3_config_data->priority != priority) {
- l3_config_data->priority = priority;
- changed = TRUE;
+ if (l3_config_data->priority_confdata != priority) {
+ l3_config_data->priority_confdata = priority;
+ changed = TRUE;
}
if (l3_config_data->merge_flags != merge_flags) {
l3_config_data->merge_flags = merge_flags;
@@ -2499,12 +2771,18 @@ nm_l3cfg_add_config(NML3Cfg * self,
l3_config_data->default_route_penalty_6 = default_route_penalty_6;
changed = TRUE;
}
- if (l3_config_data->acd_timeout_msec != acd_timeout_msec) {
- l3_config_data->acd_timeout_msec = acd_timeout_msec;
- changed = TRUE;
+ if (l3_config_data->acd_defend_type_confdata != acd_defend_type) {
+ l3_config_data->acd_defend_type_confdata = acd_defend_type;
+ changed = TRUE;
+ }
+ if (l3_config_data->acd_timeout_msec_confdata != acd_timeout_msec) {
+ l3_config_data->acd_timeout_msec_confdata = acd_timeout_msec;
+ changed = TRUE;
}
}
+ nm_assert(l3_config_data->acd_defend_type_confdata == acd_defend_type);
+
if (changed)
_l3_changed_configs_set_dirty(self);
@@ -2535,7 +2813,8 @@ _l3cfg_remove_config(NML3Cfg * self,
if (idx < 0)
break;
- if (only_dirty && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty) {
+ if (only_dirty
+ && !_l3_config_datas_at(self->priv.p->l3_config_datas, idx)->dirty_confdata) {
idx++;
continue;
}
@@ -2580,12 +2859,18 @@ typedef struct {
} L3ConfigMergeHookAddObjData;
static gboolean
-_l3_hook_add_addr_cb(const NML3ConfigData *l3cd, const NMPObject *obj, gpointer user_data)
+_l3_hook_add_addr_cb(const NML3ConfigData *l3cd,
+ const NMPObject * obj,
+ NMTernary * out_ip4acd_not_ready,
+ gpointer user_data)
{
const L3ConfigMergeHookAddObjData *hook_data = user_data;
NML3Cfg * self = hook_data->self;
AcdData * acd_data;
in_addr_t addr;
+ gboolean acd_bad = FALSE;
+
+ nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT);
if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS)
return TRUE;
@@ -2593,46 +2878,91 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, const NMPObject *obj, gpointer
addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address;
if (ACD_ADDR_SKIP(addr))
- return TRUE;
+ goto out;
acd_data = _l3_acd_data_find(self, addr);
- nm_assert(acd_data);
+
+ if (!acd_data) {
+ /* we don't yet track an ACD state for this address. That can only
+ * happend during _l3cfg_update_combined_config() with !to_commit,
+ * where we didn't update the ACD state.
+ *
+ * This means, unless you actually commit, nm_l3cfg_get_combined_l3cd(self, get_commited = FALSE)
+ * won't consider IPv4 addresses ready, that have no known ACD state yet. */
+ nm_assert(self->priv.p->changed_configs_acd_state);
+ acd_bad = TRUE;
+ goto out;
+ }
+
nm_assert(
_acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag)));
- return _acd_data_probe_result_is_good(acd_data);
+ if (!NM_IN_SET(acd_data->info.state,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING))
+ acd_bad = TRUE;
+
+out:
+ *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE;
+ return TRUE;
}
static void
_l3cfg_update_combined_config(NML3Cfg * self,
gboolean to_commit,
+ gboolean reapply,
const NML3ConfigData **out_old /* transfer reference */,
- gboolean * out_changed_configs,
gboolean * out_changed_combined_l3cd)
{
- nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
- nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
- gs_free const L3ConfigData **l3_config_datas_free = NULL;
- const L3ConfigData *const * l3_config_datas_sorted;
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL;
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
+ nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
+ gs_free const L3ConfigData **l3_config_datas_free = NULL;
+ const L3ConfigData ** l3_config_datas_arr;
guint l3_config_datas_len;
guint i;
+ gboolean merged_changed = FALSE;
+ gboolean commited_changed = FALSE;
nm_assert(NM_IS_L3CFG(self));
-
nm_assert(!out_old || !*out_old);
- NM_SET_OUT(out_changed_configs, self->priv.changed_configs);
+
NM_SET_OUT(out_changed_combined_l3cd, FALSE);
- if (!self->priv.changed_configs)
- goto out;
+ if (!self->priv.p->changed_configs_configs) {
+ if (!self->priv.p->changed_configs_acd_state)
+ goto out;
+ if (!to_commit) {
+ /* since we are not going to commit, we don't care about the
+ * ACD state. */
+ goto out;
+ }
+ }
- self->priv.changed_configs = FALSE;
+ self->priv.p->changed_configs_configs = FALSE;
- _l3_config_datas_get_sorted_a(self->priv.p->l3_config_datas,
- &l3_config_datas_sorted,
- &l3_config_datas_len,
- &l3_config_datas_free);
+ l3_config_datas_len = nm_g_array_len(self->priv.p->l3_config_datas);
+ l3_config_datas_arr = nm_malloc_maybe_a(300,
+ l3_config_datas_len * sizeof(l3_config_datas_arr[0]),
+ &l3_config_datas_free);
+ for (i = 0; i < l3_config_datas_len; i++)
+ l3_config_datas_arr[i] = _l3_config_datas_at(self->priv.p->l3_config_datas, i);
- _l3_acd_data_add_all(self, l3_config_datas_sorted, l3_config_datas_len);
+ if (l3_config_datas_len > 1) {
+ g_qsort_with_data(l3_config_datas_arr,
+ l3_config_datas_len,
+ sizeof(l3_config_datas_arr[0]),
+ _l3_config_datas_get_sorted_cmp,
+ NULL);
+ }
+
+ if (!to_commit) {
+ /* we are not going to commit these changes. Hence, we don't update the
+ * ACD states, but we need to remember that we have to on the next commit. */
+ self->priv.p->changed_configs_acd_state = TRUE;
+ } else {
+ _l3_acd_data_add_all(self, l3_config_datas_arr, l3_config_datas_len, reapply);
+ self->priv.p->changed_configs_acd_state = FALSE;
+ }
if (l3_config_datas_len > 0) {
L3ConfigMergeHookAddObjData hook_data = {
@@ -2643,12 +2973,12 @@ _l3cfg_update_combined_config(NML3Cfg * self,
self->priv.ifindex);
for (i = 0; i < l3_config_datas_len; i++) {
- const L3ConfigData *l3cd_data = l3_config_datas_sorted[i];
+ const L3ConfigData *l3cd_data = l3_config_datas_arr[i];
if (NM_FLAGS_HAS(l3cd_data->merge_flags, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD))
continue;
- hook_data.tag = l3cd_data->tag;
+ hook_data.tag = l3cd_data->tag_confdata;
nm_l3_config_data_merge(l3cd,
l3cd_data->l3cd,
l3cd_data->merge_flags,
@@ -2670,24 +3000,43 @@ _l3cfg_update_combined_config(NML3Cfg * self,
l3cd_old = g_steal_pointer(&self->priv.p->combined_l3cd_merged);
self->priv.p->combined_l3cd_merged = nm_l3_config_data_seal(g_steal_pointer(&l3cd));
-
+ merged_changed = TRUE;
if (!to_commit) {
NM_SET_OUT(out_old, g_steal_pointer(&l3cd_old));
NM_SET_OUT(out_changed_combined_l3cd, TRUE);
- _LOGT("desired IP configuration changed");
}
out:
if (to_commit && self->priv.p->combined_l3cd_commited != self->priv.p->combined_l3cd_merged) {
- nm_auto_unref_l3cd const NML3ConfigData *l3cd_commited_old = NULL;
-
l3cd_commited_old = g_steal_pointer(&self->priv.p->combined_l3cd_commited);
self->priv.p->combined_l3cd_commited =
nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged);
-
+ commited_changed = TRUE;
NM_SET_OUT(out_old, g_steal_pointer(&l3cd_commited_old));
NM_SET_OUT(out_changed_combined_l3cd, TRUE);
- _LOGT("desired IP configuration changed for commit");
+ }
+
+ if ((merged_changed || commited_changed) && _LOGT_ENABLED()) {
+ char sbuf256[256];
+ char sbuf30[30];
+
+ _LOGT("IP configuration changed (merged=%c%s, commited=%c%s)",
+ merged_changed ? '>' : '=',
+ NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_merged, sbuf256),
+ commited_changed ? '>' : '=',
+ NM_HASH_OBFUSCATE_PTR_STR(self->priv.p->combined_l3cd_commited, sbuf30));
+
+ if (merged_changed) {
+ nm_l3_config_data_log(self->priv.p->combined_l3cd_merged,
+ NULL,
+ nm_sprintf_buf(sbuf256,
+ "l3cfg[" NM_HASH_OBFUSCATE_PTR_FMT
+ ",ifindex=%d]: ",
+ NM_HASH_OBFUSCATE_PTR(self),
+ nm_l3cfg_get_ifindex(self)),
+ LOGL_TRACE,
+ _NMLOG_DOMAIN);
+ }
}
}
@@ -2872,10 +3221,13 @@ out_prune:
/*****************************************************************************/
static gboolean
-_l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
+_l3_commit_one(NML3Cfg * self,
+ int addr_family,
+ NML3CfgCommitType commit_type,
+ gboolean changed_combined_l3cd,
+ const NML3ConfigData *l3cd_old)
{
- const gboolean IS_IPv4 = NM_IS_IPv4(addr_family);
- nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
+ const gboolean IS_IPv4 = NM_IS_IPv4(addr_family);
gs_unref_ptrarray GPtrArray *addresses = NULL;
gs_unref_ptrarray GPtrArray *routes = NULL;
gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
@@ -2883,8 +3235,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
gs_unref_ptrarray GPtrArray *routes_temporary_not_available_arr = NULL;
NMIPRouteTableSyncMode route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_NONE;
gboolean final_failure_for_temporary_not_available = FALSE;
- gboolean changed_combined_l3cd;
- gboolean changed_configs;
char sbuf_commit_type[50];
gboolean success = TRUE;
@@ -2900,8 +3250,6 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
nm_utils_addr_family_to_char(addr_family),
_l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type)));
- _l3cfg_update_combined_config(self, TRUE, &l3cd_old, &changed_configs, &changed_combined_l3cd);
-
if (changed_combined_l3cd) {
/* our combined configuration changed. We may track entries in externally_removed_objs_hash,
* which are not longer to be considered by our configuration. We need to forget about them. */
@@ -2914,29 +3262,40 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
}
if (self->priv.p->combined_l3cd_commited) {
+ GHashTable * externally_removed_objs_hash;
NMDedupMultiFcnSelectPredicate predicate;
+ const NMDedupMultiHeadEntry * head_entry;
if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
- && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0)
- predicate = _l3cfg_externally_removed_objs_filter;
- else
- predicate = NULL;
- addresses = nm_dedup_multi_objs_to_ptr_array_head(
- nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
- NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)),
- predicate,
- self->priv.p->externally_removed_objs_hash);
+ && self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] > 0) {
+ predicate = _l3cfg_externally_removed_objs_filter;
+ externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
+ } else {
+ if (IS_IPv4)
+ predicate = _l3cfg_externally_removed_objs_filter;
+ else
+ predicate = NULL;
+ externally_removed_objs_hash = NULL;
+ }
+ head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
+ NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
+ addresses = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
+ predicate,
+ externally_removed_objs_hash);
if (commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY
- && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0)
- predicate = _l3cfg_externally_removed_objs_filter;
- else
- predicate = NULL;
- routes = nm_dedup_multi_objs_to_ptr_array_head(
- nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
- NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)),
- predicate,
- self->priv.p->externally_removed_objs_hash);
+ && self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] > 0) {
+ predicate = _l3cfg_externally_removed_objs_filter;
+ externally_removed_objs_hash = self->priv.p->externally_removed_objs_hash;
+ } else {
+ predicate = NULL;
+ externally_removed_objs_hash = NULL;
+ }
+ head_entry = nm_l3_config_data_lookup_objs(self->priv.p->combined_l3cd_commited,
+ NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
+ routes = nm_dedup_multi_objs_to_ptr_array_head(head_entry,
+ predicate,
+ externally_removed_objs_hash);
route_table_sync =
nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited,
@@ -2956,22 +3315,13 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
self->priv.ifindex,
route_table_sync);
} else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) {
- /* during update, we do a cross with the previous configuration.
- *
- * Of course, if an entry is both to be pruned and to be added, then
- * the latter wins. So, this works just nicely. */
- if (l3cd_old) {
- const NMDedupMultiHeadEntry *head_entry;
-
- head_entry =
- nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4));
- addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL);
-
- head_entry = nm_l3_config_data_lookup_objs(l3cd_old, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4));
- addresses_prune = nm_dedup_multi_objs_to_ptr_array_head(head_entry, NULL, NULL);
- }
+ addresses_prune = nm_g_ptr_array_ref(self->priv.p->last_addresses_x[IS_IPv4]);
+ routes_prune = nm_g_ptr_array_ref(self->priv.p->last_routes_x[IS_IPv4]);
}
+ nm_g_ptr_array_set(&self->priv.p->last_addresses_x[IS_IPv4], addresses);
+ nm_g_ptr_array_set(&self->priv.p->last_routes_x[IS_IPv4], routes);
+
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_privacy(). */
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ndisc_*(). */
/* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_ip6_mtu(). */
@@ -3006,8 +3356,10 @@ _l3_commit_one(NML3Cfg *self, int addr_family, NML3CfgCommitType commit_type)
static void
_l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
{
- gboolean commit_type_detected = FALSE;
- char sbuf_ct[30];
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
+ gboolean commit_type_detected = FALSE;
+ char sbuf_ct[30];
+ gboolean changed_combined_l3cd;
g_return_if_fail(NM_IS_L3CFG(self));
nm_assert(NM_IN_SET(commit_type,
@@ -3016,6 +3368,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
NM_L3_CFG_COMMIT_TYPE_ASSUME,
NM_L3_CFG_COMMIT_TYPE_UPDATE,
NM_L3_CFG_COMMIT_TYPE_REAPPLY));
+ nm_assert(self->priv.p->commit_reentrant_count == 0);
switch (commit_type) {
case NM_L3_CFG_COMMIT_TYPE_AUTO:
@@ -3041,7 +3394,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
break;
}
- _LOGT("platform-commit %s%s%s",
+ _LOGT("commit %s%s%s",
_l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)),
commit_type_detected ? " (auto)" : "",
is_idle ? " (idle handler)" : "");
@@ -3049,18 +3402,29 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle)
if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
return;
+ self->priv.p->commit_reentrant_count++;
+
nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source);
if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
_l3cfg_externally_removed_objs_drop(self);
+ _l3cfg_update_combined_config(self,
+ TRUE,
+ commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY,
+ &l3cd_old,
+ &changed_combined_l3cd);
+
/* FIXME(l3cfg): handle items currently not configurable in kernel. */
- _l3_commit_one(self, AF_INET, commit_type);
- _l3_commit_one(self, AF_INET6, commit_type);
+ _l3_commit_one(self, AF_INET, commit_type, changed_combined_l3cd, l3cd_old);
+ _l3_commit_one(self, AF_INET6, commit_type, changed_combined_l3cd, l3cd_old);
_l3_acd_data_process_changes(self);
+ nm_assert(self->priv.p->commit_reentrant_count == 1);
+ self->priv.p->commit_reentrant_count--;
+
_nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT);
}
@@ -3176,7 +3540,7 @@ nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited)
if (get_commited)
return self->priv.p->combined_l3cd_commited;
- _l3cfg_update_combined_config(self, FALSE, NULL, NULL, NULL);
+ _l3cfg_update_combined_config(self, FALSE, FALSE, NULL, NULL);
return self->priv.p->combined_l3cd_merged;
}
@@ -3270,7 +3634,7 @@ nm_l3cfg_init(NML3Cfg *self)
self->priv.p = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_L3CFG, NML3CfgPrivate);
c_list_init(&self->priv.p->acd_lst_head);
- c_list_init(&self->priv.p->acd_notify_complete_lst_head);
+ c_list_init(&self->priv.p->acd_event_notify_lst_head);
c_list_init(&self->priv.p->commit_type_lst_head);
}
@@ -3317,7 +3681,7 @@ finalize(GObject *object)
_l3_acd_data_prune(self, TRUE);
nm_assert(c_list_is_empty(&self->priv.p->acd_lst_head));
- nm_assert(c_list_is_empty(&self->priv.p->acd_notify_complete_lst_head));
+ nm_assert(c_list_is_empty(&self->priv.p->acd_event_notify_lst_head));
nm_assert(nm_g_hash_table_size(self->priv.p->acd_lst_hash) == 0);
nm_clear_pointer(&self->priv.p->acd_lst_hash, g_hash_table_unref);
@@ -3325,6 +3689,11 @@ finalize(GObject *object)
nm_clear_g_source_inst(&self->priv.p->nacd_source);
nm_clear_g_source_inst(&self->priv.p->nacd_instance_ensure_retry);
+ nm_clear_pointer(&self->priv.p->last_addresses_4, g_ptr_array_unref);
+ nm_clear_pointer(&self->priv.p->last_addresses_6, g_ptr_array_unref);
+ nm_clear_pointer(&self->priv.p->last_routes_4, g_ptr_array_unref);
+ nm_clear_pointer(&self->priv.p->last_routes_6, g_ptr_array_unref);
+
nm_clear_g_source(&self->priv.p->routes_temporary_not_available_id);
nm_clear_pointer(&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
diff --git a/src/nm-l3cfg.h b/src/nm-l3cfg.h
index 9f957ae10c..d89e2fa280 100644
--- a/src/nm-l3cfg.h
+++ b/src/nm-l3cfg.h
@@ -18,10 +18,47 @@
#define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify"
+typedef enum _nm_packed {
+ NM_L3_ACD_DEFEND_TYPE_NONE,
+ NM_L3_ACD_DEFEND_TYPE_NEVER,
+ NM_L3_ACD_DEFEND_TYPE_ONCE,
+ NM_L3_ACD_DEFEND_TYPE_ALWAYS,
+} NML3AcdDefendType;
+
+typedef enum _nm_packed {
+ NM_L3_ACD_ADDR_STATE_INIT,
+ NM_L3_ACD_ADDR_STATE_PROBING,
+ NM_L3_ACD_ADDR_STATE_USED,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING,
+ NM_L3_ACD_ADDR_STATE_CONFLICT,
+ NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED,
+} NML3AcdAddrState;
+
+typedef struct {
+ const NMPObject * obj;
+ const NML3ConfigData *l3cd;
+ gconstpointer tag;
+
+ char _padding[sizeof(struct {
+ guint32 a;
+ NML3AcdDefendType b;
+ guint8 c;
+ })];
+} NML3AcdAddrTrackInfo;
+
+typedef struct {
+ in_addr_t addr;
+ guint n_track_infos;
+ NML3AcdAddrState state;
+ NML3Cfg * l3cfg;
+ const NML3AcdAddrTrackInfo *track_infos;
+} NML3AcdAddrInfo;
+
typedef enum {
NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED,
- NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED,
+ NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT,
/* emitted at the end of nm_l3cfg_platform_commit(). */
NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT,
@@ -42,20 +79,11 @@ typedef enum {
} NML3ConfigNotifyType;
typedef struct {
- const NMPObject * obj;
- const NML3ConfigData *l3cd;
- gconstpointer tag;
-} NML3ConfigNotifyPayloadAcdFailedSource;
-
-typedef struct {
NML3ConfigNotifyType notify_type;
union {
struct {
- in_addr_t addr;
- guint sources_len;
- bool probe_result : 1;
- const NML3ConfigNotifyPayloadAcdFailedSource *sources;
- } acd_completed;
+ NML3AcdAddrInfo info;
+ } acd_event;
struct {
const NMPObject * obj;
@@ -79,7 +107,6 @@ struct _NML3Cfg {
const NMPObject * plobj;
const NMPObject * plobj_next;
int ifindex;
- bool changed_configs : 1;
} priv;
};
@@ -205,6 +232,7 @@ gboolean nm_l3cfg_add_config(NML3Cfg * self,
guint32 default_route_metric_6,
guint32 default_route_penalty_4,
guint32 default_route_penalty_6,
+ NML3AcdDefendType acd_defend_type,
guint32 acd_timeout_msec,
NML3ConfigMergeFlags merge_flags);
@@ -250,6 +278,10 @@ void nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self);
/*****************************************************************************/
+const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr);
+
+/*****************************************************************************/
+
NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self);
typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle;
diff --git a/src/platform/nm-platform.c b/src/platform/nm-platform.c
index 2823d73c94..eca201b087 100644
--- a/src/platform/nm-platform.c
+++ b/src/platform/nm-platform.c
@@ -6266,11 +6266,16 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
"%s" /* label */
" src %s"
"%s" /* external */
+ "%s" /* ip4acd_not_ready */
"",
s_address,
address->plen,
- broadcast_address ? " brd " : "",
- broadcast_address ? _nm_utils_inet4_ntop(broadcast_address, str_broadcast) : "",
+ broadcast_address != 0u || address->use_ip4_broadcast_address
+ ? (address->use_ip4_broadcast_address ? " brd " : " brd* ")
+ : "",
+ broadcast_address != 0u || address->use_ip4_broadcast_address
+ ? _nm_utils_inet4_ntop(broadcast_address, str_broadcast)
+ : "",
str_lft_p,
str_pref_p,
str_time_p,
@@ -6279,7 +6284,8 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf
_to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)),
str_label,
nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
- address->external ? " ext" : "");
+ address->external ? " ext" : "",
+ address->ip4acd_not_ready ? " ip4acd-not-ready" : "");
g_free(str_peer);
return buf;
}
@@ -7835,7 +7841,7 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
nm_hash_update_vals(h,
obj->ifindex,
obj->addr_source,
- nm_platform_ip4_broadcast_address_from_addr(obj),
+ obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u),
obj->timestamp,
obj->lifetime,
obj->preferred,
@@ -7843,7 +7849,10 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState
obj->plen,
obj->address,
obj->peer_address,
- NM_HASH_COMBINE_BOOLS(guint8, obj->external));
+ NM_HASH_COMBINE_BOOLS(guint8,
+ obj->external,
+ obj->use_ip4_broadcast_address,
+ obj->ip4acd_not_ready));
nm_hash_update_strarr(h, obj->label);
}
@@ -7855,8 +7864,9 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad
NM_CMP_FIELD(a, b, address);
NM_CMP_FIELD(a, b, plen);
NM_CMP_FIELD(a, b, peer_address);
- NM_CMP_DIRECT(nm_platform_ip4_broadcast_address_from_addr(a),
- nm_platform_ip4_broadcast_address_from_addr(b));
+ NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address);
+ if (a->use_ip4_broadcast_address)
+ NM_CMP_FIELD(a, b, broadcast_address);
NM_CMP_FIELD(a, b, addr_source);
NM_CMP_FIELD(a, b, timestamp);
NM_CMP_FIELD(a, b, lifetime);
@@ -7864,6 +7874,7 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad
NM_CMP_FIELD(a, b, n_ifa_flags);
NM_CMP_FIELD_STR(a, b, label);
NM_CMP_FIELD_UNSAFE(a, b, external);
+ NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready);
return 0;
}
diff --git a/src/platform/nm-platform.h b/src/platform/nm-platform.h
index c7b8017a40..17f3a2fd5a 100644
--- a/src/platform/nm-platform.h
+++ b/src/platform/nm-platform.h
@@ -322,10 +322,15 @@ typedef enum {
guint8 plen; \
\
/* FIXME(l3cfg): the external marker won't be necessary anymore, because we only
- * merge addresses we care about, and ignore (don't remove) external addresses. */ \
+ * merge addresses we care about, and ignore (don't remove) external addresses. */ \
bool external : 1; \
\
bool use_ip4_broadcast_address : 1; \
+ \
+ /* Whether the address is ready to be configured. By default, an address is, but this
+ * flag may indicate that the address is just for tracking purpose only, but the ACD
+ * state is not yet ready for the address to be configured. */ \
+ bool ip4acd_not_ready : 1; \
;
/**
diff --git a/src/tests/test-l3cfg.c b/src/tests/test-l3cfg.c
index d55dcfbcaa..49e45d3881 100644
--- a/src/tests/test-l3cfg.c
+++ b/src/tests/test-l3cfg.c
@@ -93,15 +93,24 @@ typedef enum {
typedef struct {
const TestFixture1 *f;
+ bool has_addr4_101 : 1;
+ bool add_addr4_101 : 1;
+
+ guint32 acd_timeout_msec_a;
+ NML3AcdDefendType acd_defend_type_a;
+
TestL3cfgNotifyType notify_type;
guint post_commit_event_count;
guint general_event_count;
+ guint general_event_flags;
union {
struct {
int cb_count;
bool expected_probe_result : 1;
+ bool acd_event_ready_45 : 1;
+ bool acd_event_ready_101 : 1;
} wait_for_acd_ready_1;
- } notify_data;
+ } notify_result;
} TestL3cfgData;
static void
@@ -112,7 +121,8 @@ _test_l3cfg_data_set_notify_type(TestL3cfgData *tdata, TestL3cfgNotifyType notif
tdata->notify_type = notify_type;
tdata->post_commit_event_count = 0;
tdata->general_event_count = 0;
- memset(&tdata->notify_data, 0, sizeof(tdata->notify_data));
+ tdata->general_event_flags = 0;
+ memset(&tdata->notify_result, 0, sizeof(tdata->notify_result));
}
static void
@@ -120,6 +130,8 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg,
const NML3ConfigNotifyData *notify_data,
TestL3cfgData * tdata)
{
+ guint i;
+
g_assert(NM_IS_L3CFG(l3cfg));
g_assert(tdata);
g_assert(notify_data);
@@ -131,6 +143,18 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg,
else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE) {
g_assert(NMP_OBJECT_IS_VALID(notify_data->platform_change.obj));
g_assert(notify_data->platform_change.change_type != 0);
+ } else if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+ g_assert_cmpint(notify_data->acd_event.info.n_track_infos, >=, 1);
+ g_assert(notify_data->acd_event.info.track_infos);
+ for (i = 0; i < notify_data->acd_event.info.n_track_infos; i++) {
+ const NML3AcdAddrTrackInfo *ti = &notify_data->acd_event.info.track_infos[i];
+
+ nm_assert(NMP_OBJECT_GET_TYPE(ti->obj) == NMP_OBJECT_TYPE_IP4_ADDRESS);
+ nm_assert(NMP_OBJECT_CAST_IP4_ADDRESS(ti->obj)->address
+ == notify_data->acd_event.info.addr);
+ nm_assert(NM_IS_L3_CONFIG_DATA(ti->l3cd));
+ nm_assert(ti->tag);
+ }
}
switch (tdata->notify_type) {
@@ -150,14 +174,33 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg,
case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
tdata->post_commit_event_count++;
return;
- case NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED:
+ case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
switch (tdata->f->test_idx) {
case 2:
- nmtst_assert_ip4_address(notify_data->acd_completed.addr, "192.167.133.45");
- g_assert(notify_data->acd_completed.probe_result);
+ case 3:
+ nmtst_assert_ip4_address(notify_data->acd_event.info.addr, "192.168.133.45");
+ if (tdata->f->test_idx == 2)
+ g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_DEFENDING);
+ else
+ g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
g_assert(tdata->general_event_count == 0);
tdata->general_event_count++;
return;
+ case 4:
+ if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) {
+ g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x1u));
+ tdata->general_event_flags |= 0x1u;
+ g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+ tdata->general_event_count++;
+ } else if (notify_data->acd_event.info.addr
+ == nmtst_inet4_from_string("192.168.133.101")) {
+ g_assert(!NM_FLAGS_HAS(tdata->general_event_flags, 0x4u));
+ tdata->general_event_flags |= 0x4u;
+ g_assert(notify_data->acd_event.info.state == NM_L3_ACD_ADDR_STATE_PROBING);
+ tdata->general_event_count++;
+ } else
+ g_assert_not_reached();
+ return;
default:
g_assert_not_reached();
return;
@@ -169,30 +212,63 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg,
return;
}
case TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1:
+ {
+ int num_acd_completed_events =
+ 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0);
+
if (NM_IN_SET(notify_data->notify_type,
NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE,
NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE))
return;
- if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED) {
- g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 0);
- tdata->notify_data.wait_for_acd_ready_1.cb_count++;
+ if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT) {
+ if (notify_data->acd_event.info.addr == nmtst_inet4_from_string("192.168.133.45")) {
+ g_assert(NM_IN_SET(notify_data->acd_event.info.state,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING));
+ tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 = TRUE;
+ } else if (notify_data->acd_event.info.addr
+ == nmtst_inet4_from_string("192.168.133.101")) {
+ if (tdata->has_addr4_101) {
+ g_assert(
+ NM_IN_SET(notify_data->acd_event.info.state, NM_L3_ACD_ADDR_STATE_USED));
+ } else {
+ g_assert(NM_IN_SET(notify_data->acd_event.info.state,
+ NM_L3_ACD_ADDR_STATE_READY,
+ NM_L3_ACD_ADDR_STATE_DEFENDING));
+ tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 = TRUE;
+ }
+ } else
+ g_assert_not_reached();
+
+ g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+ <,
+ num_acd_completed_events);
+ tdata->notify_result.wait_for_acd_ready_1.cb_count++;
return;
}
if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT) {
- g_assert(tdata->notify_data.wait_for_acd_ready_1.cb_count == 1);
- tdata->notify_data.wait_for_acd_ready_1.cb_count++;
- nmtstp_platform_ip_addresses_assert(tdata->f->platform,
- tdata->f->ifindex0,
- TRUE,
- TRUE,
- TRUE,
- "192.167.133.45",
- "1:2:3:4::45");
+ g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count, >, 0);
+ g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+ <,
+ num_acd_completed_events);
+ tdata->notify_result.wait_for_acd_ready_1.cb_count++;
+ nmtstp_platform_ip_addresses_assert(
+ tdata->f->platform,
+ tdata->f->ifindex0,
+ TRUE,
+ TRUE,
+ TRUE,
+ tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_45 ? "192.168.133.45"
+ : NULL,
+ tdata->notify_result.wait_for_acd_ready_1.acd_event_ready_101 ? "192.168.133.101"
+ : NULL,
+ "1:2:3:4::45");
return;
}
g_assert_not_reached();
return;
}
+ }
g_assert_not_reached();
}
@@ -200,19 +276,20 @@ _test_l3cfg_signal_notify(NML3Cfg * l3cfg,
static void
test_l3cfg(gconstpointer test_data)
{
- nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
+ const int TEST_IDX = GPOINTER_TO_INT(test_data);
+ const guint32 ACD_TIMEOUT_BASE_MSEC = 1000;
+ nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {};
const TestFixture1 * f;
NML3CfgCommitTypeHandle * commit_type_1;
NML3CfgCommitTypeHandle * commit_type_2;
- gs_unref_object NML3Cfg *l3cfg0 = NULL;
- nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL;
- guint32 acd_timeout_msec = 0;
- TestL3cfgData tdata_stack = {
+ gs_unref_object NML3Cfg *l3cfg0 = NULL;
+ nm_auto_unref_l3cd const NML3ConfigData *l3cd_a = NULL;
+ TestL3cfgData tdata_stack = {
.f = NULL,
};
TestL3cfgData *const tdata = &tdata_stack;
- _LOGD("test start (/l3cfg/%d)", GPOINTER_TO_INT(test_data));
+ _LOGD("test start (/l3cfg/%d)", TEST_IDX);
if (nmtst_test_quick()) {
gs_free char *msg =
@@ -223,9 +300,31 @@ test_l3cfg(gconstpointer test_data)
return;
}
- f = _test_fixture_1_setup(&test_fixture, GPOINTER_TO_INT(test_data));
-
- tdata->f = f;
+ f = _test_fixture_1_setup(&test_fixture, TEST_IDX);
+
+ tdata->f = f;
+ tdata->has_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool());
+ tdata->add_addr4_101 = (f->test_idx == 4 && nmtst_get_rand_bool());
+
+ tdata->acd_timeout_msec_a = NM_IN_SET(f->test_idx, 3, 4) ? ACD_TIMEOUT_BASE_MSEC : 0u;
+ tdata->acd_defend_type_a = NM_IN_SET(f->test_idx, 4)
+ ? nmtst_rand_select(NM_L3_ACD_DEFEND_TYPE_NEVER,
+ NM_L3_ACD_DEFEND_TYPE_ONCE,
+ NM_L3_ACD_DEFEND_TYPE_ALWAYS)
+ : NM_L3_ACD_DEFEND_TYPE_NEVER;
+
+ if (tdata->has_addr4_101) {
+ nmtstp_ip4_address_add(f->platform,
+ -1,
+ f->ifindex1,
+ nmtst_inet4_from_string("192.168.133.101"),
+ 24,
+ nmtst_inet4_from_string("192.168.133.101"),
+ 100000,
+ 0,
+ 0,
+ NULL);
+ }
l3cfg0 = nm_netns_access_l3cfg(f->netns, f->ifindex0);
g_assert(NM_IS_L3CFG(l3cfg0));
@@ -249,6 +348,7 @@ test_l3cfg(gconstpointer test_data)
break;
case 2:
case 3:
+ case 4:
{
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
@@ -256,10 +356,19 @@ test_l3cfg(gconstpointer test_data)
nm_l3_config_data_add_address_4(
l3cd,
- NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.167.133.45"),
- .peer_address = nmtst_inet4_from_string("192.167.133.45"),
+ NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.45"),
+ .peer_address = nmtst_inet4_from_string("192.168.133.45"),
.plen = 24, ));
+ if (tdata->add_addr4_101) {
+ nm_l3_config_data_add_address_4(
+ l3cd,
+ NM_PLATFORM_IP4_ADDRESS_INIT(.address = nmtst_inet4_from_string("192.168.133.101"),
+ .peer_address =
+ nmtst_inet4_from_string("192.168.133.101"),
+ .plen = 24, ));
+ }
+
nm_l3_config_data_add_address_6(
l3cd,
NM_PLATFORM_IP6_ADDRESS_INIT(.address = *nmtst_inet6_from_string("1:2:3:4::45"),
@@ -272,7 +381,7 @@ test_l3cfg(gconstpointer test_data)
}
}
- acd_timeout_msec = (f->test_idx == 3) ? 2000u : 0u;
+ nm_l3_config_data_log(l3cd_a, "l3cd_a", "platform-test: l3cd_a: ", LOGL_DEBUG, LOGD_PLATFORM);
if (l3cd_a) {
nm_l3cfg_add_config(l3cfg0,
@@ -286,7 +395,8 @@ test_l3cfg(gconstpointer test_data)
NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6,
0,
0,
- acd_timeout_msec,
+ tdata->acd_defend_type_a,
+ tdata->acd_timeout_msec_a,
NM_L3_CONFIG_MERGE_FLAGS_NONE);
}
@@ -306,24 +416,32 @@ test_l3cfg(gconstpointer test_data)
TRUE,
TRUE,
TRUE,
- NM_IN_SET(f->test_idx, 2) ? "192.167.133.45" : NULL,
- NM_IN_SET(f->test_idx, 2, 3) ? "1:2:3:4::45" : NULL);
+ NM_IN_SET(f->test_idx, 2) ? "192.168.133.45" : NULL,
+ NM_IN_SET(f->test_idx, 2, 3, 4) ? "1:2:3:4::45" : NULL);
if (NM_IN_SET(f->test_idx, 1, 2)) {
_test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_IDLE_ASSERT_NO_SIGNAL);
_LOGT("poll 1 start");
- nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 5000u, FALSE);
+ nmtst_main_context_iterate_until(NULL,
+ nmtst_get_rand_uint32() % (ACD_TIMEOUT_BASE_MSEC * 5u),
+ FALSE);
_LOGT("poll 1 end");
_test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
}
- if (NM_IN_SET(f->test_idx, 3)) {
+ if (NM_IN_SET(f->test_idx, 3, 4)) {
_test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_WAIT_FOR_ACD_READY_1);
- tdata->notify_data.wait_for_acd_ready_1.expected_probe_result = TRUE;
+ tdata->notify_result.wait_for_acd_ready_1.expected_probe_result = TRUE;
_LOGT("poll 2 start");
- nmtst_main_context_iterate_until(NULL, 2500u + (nmtst_get_rand_uint32() % 4000u), FALSE);
+ nmtst_main_context_iterate_until(
+ NULL,
+ ACD_TIMEOUT_BASE_MSEC * 3u / 2u
+ + (nmtst_get_rand_uint32() % (2u * ACD_TIMEOUT_BASE_MSEC)),
+ FALSE);
_LOGT("poll 2 end");
- g_assert_cmpint(tdata->notify_data.wait_for_acd_ready_1.cb_count, ==, 2);
+ g_assert_cmpint(tdata->notify_result.wait_for_acd_ready_1.cb_count,
+ ==,
+ 1 + 2 + (tdata->add_addr4_101 ? (tdata->has_addr4_101 ? 1 : 3) : 0));
_test_l3cfg_data_set_notify_type(tdata, TEST_L3CFG_NOTIFY_TYPE_NONE);
}
@@ -340,7 +458,7 @@ test_l3cfg(gconstpointer test_data)
if ((nmtst_get_rand_uint32() % 3) == 0)
_test_fixture_1_teardown(&test_fixture);
- _LOGD("test end (/l3cfg/%d)", f->test_idx);
+ _LOGD("test end (/l3cfg/%d)", TEST_IDX);
}
/*****************************************************************************/
@@ -359,4 +477,5 @@ _nmtstp_setup_tests(void)
g_test_add_data_func("/l3cfg/1", GINT_TO_POINTER(1), test_l3cfg);
g_test_add_data_func("/l3cfg/2", GINT_TO_POINTER(2), test_l3cfg);
g_test_add_data_func("/l3cfg/3", GINT_TO_POINTER(3), test_l3cfg);
+ g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg);
}