// SPDX-License-Identifier: LGPL-2.1+
#include "nm-default.h"
#include "nm-l3cfg.h"
#include <net/if.h>
#include "platform/nm-platform.h"
#include "platform/nmp-object.h"
#include "nm-netns.h"
#include "n-acd/src/n-acd.h"
/*****************************************************************************/
#define ACD_SUPPORTED_ETH_ALEN ETH_ALEN
#define ACD_ENSURE_RATELIMIT_MSEC ((guint32) 4000u)
#define ACD_WAIT_PROBING_EXTRA_TIME_MSEC ((guint32) (1000u + ACD_ENSURE_RATELIMIT_MSEC))
#define ACD_WAIT_PROBING_EXTRA_TIME2_MSEC ((guint32) 1000u)
#define ACD_WAIT_PROBING_RESTART_TIME_MSEC ((guint32) 8000u)
#define ACD_MAX_TIMEOUT_MSEC ((guint32) 30000u)
#define ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC ((guint32) 30000u)
#define ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC ((guint32) 20000u)
static gboolean
ACD_ADDR_SKIP (in_addr_t addr)
{
return addr == 0u;
}
#define ACD_TRACK_FMT "["NM_HASH_OBFUSCATE_PTR_FMT","NM_HASH_OBFUSCATE_PTR_FMT","NM_HASH_OBFUSCATE_PTR_FMT"]"
#define ACD_TRACK_PTR2(l3cd, obj, tag) NM_HASH_OBFUSCATE_PTR (l3cd), NM_HASH_OBFUSCATE_PTR (obj), NM_HASH_OBFUSCATE_PTR (tag)
#define ACD_TRACK_PTR(acd_track) ACD_TRACK_PTR2 ((acd_track)->l3cd, (acd_track)->obj, (acd_track)->tag)
typedef enum {
ACD_STATE_CHANGE_MODE_INIT,
ACD_STATE_CHANGE_MODE_POST_COMMIT,
ACD_STATE_CHANGE_MODE_NACD_READY,
ACD_STATE_CHANGE_MODE_NACD_USED,
ACD_STATE_CHANGE_MODE_NACD_DOWN,
ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED,
ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED,
ACD_STATE_CHANGE_MODE_LINK_NOW_UP,
ACD_STATE_CHANGE_MODE_INSTANCE_RESET,
ACD_STATE_CHANGE_MODE_TIMEOUT,
} AcdStateChangeMode;
typedef struct {
CList acd_track_lst;
const NMPObject *obj;
const NML3ConfigData *l3cd;
gconstpointer tag;
guint32 acd_timeout_msec;
bool acd_dirty:1;
bool acd_failed_notified:1;
} AcdTrackData;
typedef enum _nm_packed {
ACD_STATE_INIT,
ACD_STATE_PROBING,
ACD_STATE_PROBE_DONE,
ACD_STATE_ANNOUNCING,
} AcdState;
typedef struct {
in_addr_t addr;
/* This is only relevant while in state ACD_STATE_PROBING. It's the
* duration for how long we probe, and @probing_timestamp_msec is the
* timestamp when we start probing. */
guint32 probing_timeout_msec;
CList acd_lst;
CList acd_track_lst_head;
NML3Cfg *self;
NAcdProbe *nacd_probe;
GSource *acd_timeout_source;
gint64 acd_timeout_expiry_msec;
/* see probing_timeout_msec. */
gint64 probing_timestamp_msec;
/* the ACD state for this address. */
AcdState acd_state;
/* The probe result. This is only relevant if @acd_state is ACD_STATE_PROBE_DONE.
* In state ACD_STATE_ANNOUNCING the @probe_result must be TRUE. */
bool probe_result:1;
bool announcing_failed_is_retrying:1;
} AcdData;
struct _NML3CfgCommitTypeHandle {
CList commit_type_lst;
NML3CfgCommitType commit_type;
};
typedef struct {
const NML3ConfigData *l3cd;
NML3ConfigMergeFlags merge_flags;
union {
struct {
guint32 default_route_penalty_6;
guint32 default_route_penalty_4;
};
guint32 default_route_penalty_x[2];
};
gconstpointer tag;
guint64 pseudo_timestamp;
int priority;
guint32 acd_timeout_msec;
bool dirty:1;
} L3ConfigData;
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NML3Cfg,
PROP_NETNS,
PROP_IFINDEX,
);
enum {
SIGNAL_NOTIFY,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = { 0 };
typedef struct _NML3CfgPrivate {
GArray *property_emit_list;
GArray *l3_config_datas;
const NML3ConfigData *combined_l3cd;
CList commit_type_lst_head;
GHashTable *routes_temporary_not_available_hash;
GHashTable *externally_removed_objs_hash;
GHashTable *acd_ipv4_addresses_on_link;
GHashTable *acd_lst_hash;
CList acd_lst_head;
NAcd *nacd;
GSource *nacd_source;
/* This is for rate-limiting the creation of nacd instance. */
GSource *nacd_instance_ensure_retry;
GSource *acd_ready_on_idle_source;
guint64 pseudo_timestamp_counter;
union {
struct {
guint externally_removed_objs_cnt_addresses_6;
guint externally_removed_objs_cnt_addresses_4;
};
guint externally_removed_objs_cnt_addresses_x[2];
};
union {
struct {
guint externally_removed_objs_cnt_routes_6;
guint externally_removed_objs_cnt_routes_4;
};
guint externally_removed_objs_cnt_routes_x[2];
};
guint routes_temporary_not_available_id;
bool acd_is_pending:1;
bool acd_is_announcing:1;
bool nacd_acd_not_supported:1;
bool acd_ipv4_addresses_on_link_has:1;
} NML3CfgPrivate;
struct _NML3CfgClass {
GObjectClass parent;
};
G_DEFINE_TYPE (NML3Cfg, nm_l3cfg, G_TYPE_OBJECT)
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_CORE
#define _NMLOG_PREFIX_NAME "l3cfg"
#define _NMLOG(level, ...) \
G_STMT_START { \
nm_log ((level), (_NMLOG_DOMAIN), NULL, NULL, \
"l3cfg["NM_HASH_OBFUSCATE_PTR_FMT",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
NM_HASH_OBFUSCATE_PTR (self), \
nm_l3cfg_get_ifindex (self) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} G_STMT_END
#define _LOGT_acd(acd_data, ...) \
G_STMT_START { \
char _sbuf_acd[NM_UTILS_INET_ADDRSTRLEN]; \
\
_LOGT ("acd[%s]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
_nm_utils_inet4_ntop ((acd_data)->addr, _sbuf_acd) \
_NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} G_STMT_END
/*****************************************************************************/
static void _property_emit_notify (NML3Cfg *self, NML3CfgPropertyEmitType emit_type);
static gboolean _acd_has_valid_link (const NMPObject *obj,
const guint8 **out_addr_bin,
gboolean *out_acd_not_supported);
static void _l3_acd_nacd_instance_reset (NML3Cfg *self,
NMTernary start_timer,
gboolean acd_data_notify);
static void _l3_acd_data_prune (NML3Cfg *self,
gboolean all);
static void _l3_acd_data_state_change (NML3Cfg *self,
AcdData *acd_data,
AcdStateChangeMode mode,
NAcdEvent *event);
static AcdData *_l3_acd_data_find (NML3Cfg *self,
in_addr_t addr);
/*****************************************************************************/
static
NM_UTILS_ENUM2STR_DEFINE (_l3_cfg_commit_type_to_string, NML3CfgCommitType,
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_AUTO, "auto"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_NONE, "none"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_ASSUME, "assume"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_UPDATE, "update"),
NM_UTILS_ENUM2STR (NM_L3_CFG_COMMIT_TYPE_REAPPLY, "reapply"),
);
/*****************************************************************************/
static void
_l3cfg_emit_signal_notify (NML3Cfg *self,
NML3ConfigNotifyType notify_type,
const NML3ConfigNotifyPayload *pay_load)
{
nm_assert (_NM_INT_NOT_NEGATIVE (notify_type));
nm_assert (notify_type < _NM_L3_CONFIG_NOTIFY_TYPE_NUM);
g_signal_emit (self,
signals[SIGNAL_NOTIFY],
0,
(int) notify_type,
pay_load);
}
/*****************************************************************************/
static void
_l3_acd_ipv4_addresses_on_link_update (NML3Cfg *self,
in_addr_t addr,
gboolean add /* or else remove */)
{
AcdData *acd_data;
acd_data = _l3_acd_data_find (self, addr);
if (add) {
if (self->priv.p->acd_ipv4_addresses_on_link)
g_hash_table_add (self->priv.p->acd_ipv4_addresses_on_link, GUINT_TO_POINTER (addr));
else
self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
if (acd_data)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED, NULL);
return;
}
/* when we remove an IPv4 address from kernel, we cannot know whether the same address is still
* present (with a different prefix length or peer). So we cannot be sure whether we removed
* the only address, or whether more are still present. All we can do is forget about the
* cached addresses, and fetch them new the next time we need the information. */
nm_clear_pointer (&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref);
self->priv.p->acd_ipv4_addresses_on_link_has = FALSE;
if (acd_data)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED, NULL);
}
static gboolean
_l3_acd_ipv4_addresses_on_link_contains (NML3Cfg *self,
in_addr_t addr)
{
if (!self->priv.p->acd_ipv4_addresses_on_link) {
if (self->priv.p->acd_ipv4_addresses_on_link_has)
return FALSE;
self->priv.p->acd_ipv4_addresses_on_link_has = TRUE;
self->priv.p->acd_ipv4_addresses_on_link = nm_platform_ip4_address_addr_to_hash (self->priv.platform,
self->priv.ifindex);
if (!self->priv.p->acd_ipv4_addresses_on_link)
return FALSE;
}
return g_hash_table_contains (self->priv.p->acd_ipv4_addresses_on_link,
GUINT_TO_POINTER (addr));
}
/*****************************************************************************/
static NAcdProbe *
_nm_n_acd_data_probe_new (NML3Cfg *self,
in_addr_t addr,
guint32 timeout_msec,
gpointer user_data)
{
nm_auto (n_acd_probe_config_freep) NAcdProbeConfig *probe_config = NULL;
NAcdProbe *probe;
int r;
nm_assert (self);
if (!self->priv.p->nacd)
return NULL;
if (addr == 0)
return nm_assert_unreachable_val (NULL);
r = n_acd_probe_config_new (&probe_config);
if (r)
return NULL;
n_acd_probe_config_set_ip (probe_config, (struct in_addr) { addr });
n_acd_probe_config_set_timeout (probe_config, timeout_msec);
r = n_acd_probe (self->priv.p->nacd, &probe, probe_config);
if (r)
return NULL;
n_acd_probe_set_userdata (probe, user_data);
return probe;
}
/*****************************************************************************/
static guint *
_l3cfg_externally_removed_objs_counter (NML3Cfg *self,
NMPObjectType obj_type)
{
switch (obj_type) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
return &self->priv.p->externally_removed_objs_cnt_addresses_4;
case NMP_OBJECT_TYPE_IP6_ADDRESS:
return &self->priv.p->externally_removed_objs_cnt_addresses_6;
case NMP_OBJECT_TYPE_IP4_ROUTE:
return &self->priv.p->externally_removed_objs_cnt_routes_4;
case NMP_OBJECT_TYPE_IP6_ROUTE:
return &self->priv.p->externally_removed_objs_cnt_routes_6;
default:
return nm_assert_unreachable_val (NULL);
}
}
static void
_l3cfg_externally_removed_objs_drop (NML3Cfg *self,
int addr_family)
{
const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family);
GHashTableIter iter;
const NMPObject *obj;
nm_assert (NM_IS_L3CFG (self));
nm_assert (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET, AF_INET6));
if (addr_family == AF_UNSPEC) {
self->priv.p->externally_removed_objs_cnt_addresses_4 = 0;
self->priv.p->externally_removed_objs_cnt_addresses_6 = 0;
self->priv.p->externally_removed_objs_cnt_routes_4 = 0;
self->priv.p->externally_removed_objs_cnt_routes_6 = 0;
if (g_hash_table_size (self->priv.p->externally_removed_objs_hash) > 0)
_LOGD ("externally-removed: untrack all");
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
return;
}
if ( self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] == 0
&& self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] == 0)
return;
_LOGD ("externally-removed: untrack IPv%c",
nm_utils_addr_family_to_char (addr_family));
g_hash_table_iter_init (&iter, self->priv.p->externally_removed_objs_hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &obj, NULL)) {
nm_assert (NM_IN_SET (NMP_OBJECT_GET_TYPE (obj), NMP_OBJECT_TYPE_IP4_ADDRESS,
NMP_OBJECT_TYPE_IP6_ADDRESS,
NMP_OBJECT_TYPE_IP4_ROUTE,
NMP_OBJECT_TYPE_IP6_ROUTE));
if (NMP_OBJECT_GET_ADDR_FAMILY (obj) != addr_family)
g_hash_table_iter_remove (&iter);
}
self->priv.p->externally_removed_objs_cnt_addresses_x[IS_IPv4] = 0;
self->priv.p->externally_removed_objs_cnt_routes_x[IS_IPv4] = 0;
if ( self->priv.p->externally_removed_objs_cnt_addresses_x[!IS_IPv4] == 0
&& self->priv.p->externally_removed_objs_cnt_routes_x[!IS_IPv4] == 0)
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
}
static void
_l3cfg_externally_removed_objs_drop_unused (NML3Cfg *self)
{
GHashTableIter h_iter;
const NMPObject *obj;
char sbuf[sizeof (_nm_utils_to_string_buffer)];
nm_assert (NM_IS_L3CFG (self));
if (!self->priv.p->externally_removed_objs_hash)
return;
if (!self->priv.p->combined_l3cd) {
_l3cfg_externally_removed_objs_drop (self, AF_UNSPEC);
return;
}
g_hash_table_iter_init (&h_iter, self->priv.p->externally_removed_objs_hash);
while (g_hash_table_iter_next (&h_iter, (gpointer *) &obj, NULL)) {
if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cd,
obj)) {
/* The object is no longer tracked in the configuration.
* The externally_removed_objs_hash is to prevent adding entires that were
* removed externally, so if we don't plan to add the entry, we no longer need to track
* it. */
(*(_l3cfg_externally_removed_objs_counter (self, NMP_OBJECT_GET_TYPE (obj))))--;
g_hash_table_iter_remove (&h_iter);
_LOGD ("externally-removed: untrack %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
}
static void
_l3cfg_externally_removed_objs_track (NML3Cfg *self,
const NMPObject *obj,
gboolean is_removed)
{
char sbuf[1000];
nm_assert (NM_IS_L3CFG (self));
if (!self->priv.p->combined_l3cd)
return;
if (!is_removed) {
/* the object is still (or again) present. It no longer gets hidden. */
if (self->priv.p->externally_removed_objs_hash) {
if (g_hash_table_remove (self->priv.p->externally_removed_objs_hash,
obj)) {
(*(_l3cfg_externally_removed_objs_counter (self,
NMP_OBJECT_GET_TYPE (obj))))--;
_LOGD ("externally-removed: untrack %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
return;
}
if (!nm_l3_config_data_lookup_route_obj (self->priv.p->combined_l3cd,
obj)) {
/* we don't care about this object, so there is nothing to hide hide */
return;
}
if (G_UNLIKELY (!self->priv.p->externally_removed_objs_hash)) {
self->priv.p->externally_removed_objs_hash = g_hash_table_new_full ((GHashFunc) nmp_object_id_hash,
(GEqualFunc) nmp_object_id_equal,
(GDestroyNotify) nmp_object_unref,
NULL);
}
if (g_hash_table_add (self->priv.p->externally_removed_objs_hash,
(gpointer) nmp_object_ref (obj))) {
(*(_l3cfg_externally_removed_objs_counter (self,
NMP_OBJECT_GET_TYPE (obj))))++;
_LOGD ("externally-removed: track %s",
nmp_object_to_string (obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
}
}
static void
_l3cfg_externally_removed_objs_pickup (NML3Cfg *self,
int addr_family)
{
const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family);
NMDedupMultiIter iter;
const NMPObject *obj;
if (!self->priv.p->combined_l3cd)
return;
nm_l3_config_data_iter_obj_for_each (&iter, self->priv.p->combined_l3cd, &obj, NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)) {
if (!nm_platform_lookup_entry (self->priv.platform,
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
obj))
_l3cfg_externally_removed_objs_track (self, obj, TRUE);
}
nm_l3_config_data_iter_obj_for_each (&iter, self->priv.p->combined_l3cd, &obj, NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)) {
if (!nm_platform_lookup_entry (self->priv.platform,
NMP_CACHE_ID_TYPE_OBJECT_TYPE,
obj))
_l3cfg_externally_removed_objs_track (self, obj, TRUE);
}
}
static gboolean
_l3cfg_externally_removed_objs_filter (/* const NMDedupMultiObj * */ gconstpointer o,
gpointer user_data)
{
const NMPObject *obj = o;
GHashTable *externally_removed_objs_hash = user_data;
return !g_hash_table_contains (externally_removed_objs_hash, obj);
}
/*****************************************************************************/
static void
_load_link (NML3Cfg *self, gboolean initial)
{
nm_auto_nmpobj const NMPObject *obj_old = NULL;
const NMPObject *obj;
const char *ifname;
const char *ifname_old;
gboolean nacd_changed;
gboolean nacd_new_valid;
gboolean nacd_old_valid;
const guint8 *nacd_old_addr;
const guint8 *nacd_new_addr;
gboolean nacd_link_now_up;
AcdData *acd_data;
obj = nm_platform_link_get_obj (self->priv.platform, self->priv.ifindex, TRUE);
if ( initial
&& obj == self->priv.pllink)
return;
obj_old = g_steal_pointer (&self->priv.pllink);
self->priv.pllink = nmp_object_ref (obj);
if ( obj
&& NM_FLAGS_HAS (NMP_OBJECT_CAST_LINK (obj)->n_ifi_flags, IFF_UP)
&& ( !obj_old
|| !NM_FLAGS_HAS (NMP_OBJECT_CAST_LINK (obj_old)->n_ifi_flags, IFF_UP)))
nacd_link_now_up = TRUE;
else
nacd_link_now_up = FALSE;
nacd_changed = FALSE;
nacd_old_valid = _acd_has_valid_link (obj_old, &nacd_old_addr, NULL);
nacd_new_valid = _acd_has_valid_link (obj, &nacd_new_addr, NULL);
if (self->priv.p->nacd_instance_ensure_retry) {
if ( nacd_new_valid
&& ( !nacd_old_valid
|| memcmp (nacd_new_addr, nacd_old_addr, ACD_SUPPORTED_ETH_ALEN) == 0))
nacd_changed = TRUE;
} else if (self->priv.p->nacd) {
if (!nacd_new_valid)
nacd_changed = TRUE;
else if (!nacd_old_valid)
nacd_changed = nm_assert_unreachable_val (TRUE);
else if (memcmp (nacd_old_addr, nacd_new_addr, ACD_SUPPORTED_ETH_ALEN) != 0)
nacd_changed = TRUE;
} else if (nacd_new_valid)
nacd_changed = TRUE;
ifname_old = nmp_object_link_get_ifname (obj_old);
ifname = nmp_object_link_get_ifname (self->priv.pllink);
if (initial) {
_LOGT ("link ifname changed: %s%s%s (initial)",
NM_PRINT_FMT_QUOTE_STRING (ifname));
} else if (!nm_streq0 (ifname, ifname_old)) {
_LOGT ("link ifname changed: %s%s%s (was %s%s%s)",
NM_PRINT_FMT_QUOTE_STRING (ifname),
NM_PRINT_FMT_QUOTE_STRING (ifname_old));
}
if (nacd_changed) {
if (!c_list_is_empty (&self->priv.p->acd_lst_head))
_LOGT ("acd: link change causes restart of ACD");
_l3_acd_nacd_instance_reset (self, NM_TERNARY_FALSE, TRUE);
} else if (nacd_link_now_up) {
if (!c_list_is_empty (&self->priv.p->acd_lst_head)) {
_LOGT ("acd: link up requires are re-initialize of ACD probes");
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_LINK_NOW_UP, NULL);
}
}
}
/*****************************************************************************/
void
_nm_l3cfg_notify_platform_change_on_idle (NML3Cfg *self, guint32 obj_type_flags)
{
if (NM_FLAGS_ANY (obj_type_flags, nmp_object_type_to_flags (NMP_OBJECT_TYPE_LINK)))
_load_link (self, FALSE);
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);
if (NM_FLAGS_ANY (obj_type_flags, nmp_object_type_to_flags (NMP_OBJECT_TYPE_IP6_ROUTE)))
_property_emit_notify (self, NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE);
}
void
_nm_l3cfg_notify_platform_change (NML3Cfg *self,
NMPlatformSignalChangeType change_type,
const NMPObject *obj)
{
nm_assert (NMP_OBJECT_IS_VALID (obj));
switch (NMP_OBJECT_GET_TYPE (obj)) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
_l3_acd_ipv4_addresses_on_link_update (self,
NMP_OBJECT_CAST_IP4_ADDRESS (obj)->address,
change_type != NM_PLATFORM_SIGNAL_REMOVED);
/* fall-through */
case NMP_OBJECT_TYPE_IP6_ADDRESS:
case NMP_OBJECT_TYPE_IP4_ROUTE:
case NMP_OBJECT_TYPE_IP6_ROUTE:
_l3cfg_externally_removed_objs_track (self,
obj,
change_type == NM_PLATFORM_SIGNAL_REMOVED);
default:
break;
}
}
/*****************************************************************************/
typedef struct {
GObject *target_obj;
const GParamSpec *target_property;
NML3CfgPropertyEmitType emit_type;
} PropertyEmitData;
static void
_property_emit_notify (NML3Cfg *self, NML3CfgPropertyEmitType emit_type)
{
gs_free PropertyEmitData *collected_heap = NULL;
PropertyEmitData *collected = NULL;
PropertyEmitData *emit_data;
guint num;
guint i;
guint j;
if (!self->priv.p->property_emit_list)
return;
num = 0;
emit_data = &g_array_index (self->priv.p->property_emit_list, PropertyEmitData, 0);
for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
if (emit_data->emit_type == emit_type) {
collected = emit_data;
num++;
}
}
if (num == 0)
return;
if (num == 1) {
g_object_notify_by_pspec (collected->target_obj, (GParamSpec *) collected->target_property);
return;
}
if (num < 300u / sizeof (*collected))
collected = g_alloca (sizeof (PropertyEmitData) * num);
else {
collected_heap = g_new (PropertyEmitData, num);
collected = collected_heap;
}
emit_data = &g_array_index (self->priv.p->property_emit_list, PropertyEmitData, 0);
for (i = 0, j = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
if (emit_data->emit_type == emit_type) {
collected[j++] = *emit_data;
g_object_ref (collected->target_obj);
}
}
nm_assert (j == num);
for (i = 0; i < num; i++) {
g_object_notify_by_pspec (collected[i].target_obj, (GParamSpec *) collected[i].target_property);
if (i > 0)
g_object_unref (collected[i].target_obj);
}
}
void
nm_l3cfg_property_emit_register (NML3Cfg *self,
GObject *target_obj,
const GParamSpec *target_property,
NML3CfgPropertyEmitType emit_type)
{
PropertyEmitData *emit_data;
guint i;
nm_assert (NM_IS_L3CFG (self));
nm_assert (G_IS_OBJECT (target_obj));
nm_assert (target_property);
nm_assert (NM_IN_SET (emit_type, NM_L3CFG_PROPERTY_EMIT_TYPE_IP4_ROUTE,
NM_L3CFG_PROPERTY_EMIT_TYPE_IP6_ROUTE));
nm_assert (target_property == nm_g_object_class_find_property_from_gtype (G_OBJECT_TYPE (target_obj),
target_property->name));
if (!self->priv.p->property_emit_list)
self->priv.p->property_emit_list = g_array_new (FALSE, FALSE, sizeof (PropertyEmitData));
else {
emit_data = &g_array_index (self->priv.p->property_emit_list, PropertyEmitData, 0);
for (i = 0; i < self->priv.p->property_emit_list->len; i++, emit_data++) {
if ( emit_data->target_obj != target_obj
|| emit_data->target_property != target_property)
continue;
nm_assert (emit_data->emit_type == emit_type);
emit_data->emit_type = emit_type;
return;
}
}
emit_data = nm_g_array_append_new (self->priv.p->property_emit_list, PropertyEmitData);
*emit_data = (PropertyEmitData) {
.target_obj = target_obj,
.target_property = target_property,
.emit_type = emit_type,
};
}
void
nm_l3cfg_property_emit_unregister (NML3Cfg *self,
GObject *target_obj,
const GParamSpec *target_property)
{
PropertyEmitData *emit_data;
guint i;
nm_assert (NM_IS_L3CFG (self));
nm_assert (G_IS_OBJECT (target_obj));
nm_assert ( !target_property
|| target_property == nm_g_object_class_find_property_from_gtype (G_OBJECT_TYPE (target_obj),
target_property->name));
if (!self->priv.p->property_emit_list)
return;
for (i = self->priv.p->property_emit_list->len; i > 0; i--) {
emit_data = &g_array_index (self->priv.p->property_emit_list, PropertyEmitData, i);
if (emit_data->target_obj != target_obj)
continue;
if ( target_property
&& emit_data->target_property != target_property)
continue;
g_array_remove_index_fast (self->priv.p->property_emit_list, i);
if (target_property) {
/* if a target-property is given, we don't have another entry in
* the list. */
return;
}
}
}
/*****************************************************************************/
gboolean
nm_l3cfg_get_acd_is_pending (NML3Cfg *self)
{
g_return_val_if_fail (NM_IS_L3CFG (self), FALSE);
return self->priv.p->acd_is_pending;
}
static gboolean
_acd_track_data_is_not_dirty (const AcdTrackData *acd_track)
{
return acd_track
&& !acd_track->acd_dirty;
}
static void
_acd_track_data_free (AcdTrackData *acd_track)
{
c_list_unlink_stale (&acd_track->acd_track_lst);
nm_l3_config_data_unref (acd_track->l3cd);
nmp_object_unref (acd_track->obj);
nm_g_slice_free (acd_track);
}
static void
_acd_data_free (AcdData *acd_data)
{
nm_assert (c_list_is_empty (&acd_data->acd_track_lst_head));
n_acd_probe_free (acd_data->nacd_probe);
nm_clear_g_source_inst (&acd_data->acd_timeout_source);
c_list_unlink_stale (&acd_data->acd_lst);
nm_g_slice_free (acd_data);
}
static gboolean
_acd_data_probe_result_is_good (const AcdData *acd_data)
{
nm_assert (acd_data);
if (acd_data->acd_state < ACD_STATE_PROBE_DONE) {
/* we are currently probing. Wait. */
return FALSE;
}
/* Probing is already completed. Use the probe result. */
return acd_data->probe_result;
}
static guint
_acd_data_collect_tracks_data (const AcdData *acd_data,
NMTernary dirty_selector,
NMTernary acd_failed_notified_selector,
guint32 *out_best_acd_timeout_msec)
{
guint32 best_acd_timeout_msec = G_MAXUINT32;
AcdTrackData *acd_track;
guint n = 0;
c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
if (dirty_selector != NM_TERNARY_DEFAULT) {
if ((!!dirty_selector) != (!!acd_track->acd_dirty))
continue;
}
if (acd_failed_notified_selector != NM_TERNARY_DEFAULT) {
if ((!!acd_failed_notified_selector) != (!!acd_track->acd_failed_notified))
continue;
}
n++;
if (best_acd_timeout_msec > acd_track->acd_timeout_msec)
best_acd_timeout_msec = acd_track->acd_timeout_msec;
}
NM_SET_OUT (out_best_acd_timeout_msec, n > 0 ? best_acd_timeout_msec : 0u);
return n;
}
static AcdTrackData *
_acd_data_find_track (const AcdData *acd_data,
const NML3ConfigData *l3cd,
const NMPObject *obj,
gconstpointer tag)
{
AcdTrackData *acd_track;
c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
if ( acd_track->obj == obj
&& acd_track->l3cd == l3cd
&& acd_track->tag == tag)
return acd_track;
}
return NULL;
}
/*****************************************************************************/
static void
_l3_acd_platform_commit_acd_update (NML3Cfg *self)
{
/* The idea with NML3Cfg is that multiple users (NMDevice/NMVpnConnection) share one layer 3 configuration
* and push their (portion of) IP configuration to it. That implies, that any user may issue nm_l3cfg_platform_commit()
* at any time, in order to say that a new configuration is ready.
*
* This makes the mechanism also suitable for internally triggering a commit when ACD completes. */
_LOGT ("acd: acd update now");
self->priv.changed_configs = TRUE;
nm_l3cfg_platform_commit (self,
NM_L3_CFG_COMMIT_TYPE_AUTO,
AF_INET,
NULL);
}
static gboolean
_acd_has_valid_link (const NMPObject *obj,
const guint8 **out_addr_bin,
gboolean *out_acd_not_supported)
{
const NMPlatformLink *link;
const guint8 *addr_bin;
gsize addr_len;
if (!obj) {
NM_SET_OUT (out_acd_not_supported, FALSE);
return FALSE;
}
link = NMP_OBJECT_CAST_LINK (obj);
addr_bin = nmp_link_address_get (&link->l_address, &addr_len);
if ( !addr_bin
|| addr_len != ACD_SUPPORTED_ETH_ALEN) {
NM_SET_OUT (out_acd_not_supported, TRUE);
return FALSE;
}
NM_SET_OUT (out_acd_not_supported, FALSE);
NM_SET_OUT (out_addr_bin, addr_bin);
return TRUE;
}
static gboolean
_l3_acd_nacd_event (int fd,
GIOCondition condition,
gpointer user_data)
{
NML3Cfg *self = user_data;
int r;
nm_assert (NM_IS_L3CFG (self));
nm_assert (self->priv.p->nacd);
r = n_acd_dispatch (self->priv.p->nacd);
if (!NM_IN_SET (r, 0, N_ACD_E_PREEMPTED)) {
_LOGT ("acd: dispatch failed with error %d", r);
goto handle_failure;
}
while (TRUE) {
AcdData *acd_data;
NAcdEvent *event;
r = n_acd_pop_event (self->priv.p->nacd, &event);
if (r) {
_LOGT ("acd: pop-event failed with error %d", r);
goto handle_failure;
}
if (!event)
return G_SOURCE_CONTINUE;
#define _acd_event_payload used
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NAcdEvent, _acd_event_payload) == G_STRUCT_OFFSET (NAcdEvent, defended));
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NAcdEvent, _acd_event_payload) == G_STRUCT_OFFSET (NAcdEvent, conflict));
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NAcdEvent, _acd_event_payload) == G_STRUCT_OFFSET (NAcdEvent, used));
nm_assert (&event->_acd_event_payload == &event->defended);
nm_assert (&event->_acd_event_payload == &event->conflict);
nm_assert (&event->_acd_event_payload == &event->used);
switch (event->event) {
case N_ACD_EVENT_READY:
n_acd_probe_get_userdata (event->_acd_event_payload.probe, (void **) &acd_data);
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_NACD_READY, event);
break;
case N_ACD_EVENT_USED:
n_acd_probe_get_userdata (event->_acd_event_payload.probe, (void **) &acd_data);
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_NACD_USED, event);
break;
case N_ACD_EVENT_DEFENDED:
case N_ACD_EVENT_CONFLICT: {
gs_free char *sender_str = NULL;
const char *addr_str = NULL;
char sbuf_addr[NM_UTILS_INET_ADDRSTRLEN];
/* since we announce with N_ACD_DEFEND_ALWAYS, we don't actually expect any
* conflict reported and don't handle it. It would be complicated to de-configure
* the address. */
nm_assert (event->event == N_ACD_EVENT_DEFENDED);
n_acd_probe_get_userdata (event->_acd_event_payload.probe, (void **) &acd_data);
_LOGT_acd (acd_data,
"address %s %s from %s",
(addr_str = nm_utils_inet4_ntop (acd_data->addr, sbuf_addr)),
event->event == N_ACD_EVENT_DEFENDED
? "defended"
: "conflict detected",
(sender_str = nm_utils_bin2hexstr_full (event->_acd_event_payload.sender,
event->_acd_event_payload.n_sender,
':',
FALSE,
NULL)));
if (event->event == N_ACD_EVENT_CONFLICT) {
_LOGW ("IPv4 address collision detection sees conflict on interface %i%s%s%s for address %s from host %s",
self->priv.ifindex,
NM_PRINT_FMT_QUOTED (self->priv.pllink, " (", NMP_OBJECT_CAST_LINK (self->priv.pllink)->name, ")", ""),
addr_str ?: nm_utils_inet4_ntop (acd_data->addr, sbuf_addr),
sender_str
?: (sender_str = nm_utils_bin2hexstr_full (event->_acd_event_payload.sender,
event->_acd_event_payload.n_sender,
':',
FALSE,
NULL)));
}
break;
}
case N_ACD_EVENT_DOWN:
_LOGT ("acd: message possibly dropped due to device down.");
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_NACD_DOWN, NULL);
break;
default:
_LOGT ("acd: unexpected event %u. Ignore", event->event);
break;
}
}
nm_assert_not_reached ();
handle_failure:
/* Something is seriously wrong with our nacd instance. We handle that by resetting the
* ACD instance. */
_l3_acd_nacd_instance_reset (self, NM_TERNARY_TRUE, TRUE);
return G_SOURCE_CONTINUE;
}
static gboolean
_l3_acd_nacd_instance_ensure_retry_cb (gpointer user_data)
{
NML3Cfg *self = user_data;
nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry);
_l3_acd_platform_commit_acd_update (self);
return G_SOURCE_REMOVE;
}
static void
_l3_acd_nacd_instance_reset (NML3Cfg *self,
NMTernary start_timer,
gboolean acd_data_notify)
{
nm_assert (NM_IS_L3CFG (self));
if (self->priv.p->nacd) {
_LOGT ("acd: clear nacd instance");
self->priv.p->nacd = n_acd_unref (self->priv.p->nacd);
}
nm_clear_g_source_inst (&self->priv.p->nacd_source);
nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry);
if (c_list_is_empty (&self->priv.p->acd_lst_head))
start_timer = NM_TERNARY_DEFAULT;
switch (start_timer) {
case NM_TERNARY_FALSE:
self->priv.p->nacd_instance_ensure_retry = nm_g_idle_source_new (G_PRIORITY_DEFAULT,
_l3_acd_nacd_instance_ensure_retry_cb,
self,
NULL);
g_source_attach (self->priv.p->nacd_instance_ensure_retry, NULL);
break;
case NM_TERNARY_TRUE:
self->priv.p->nacd_instance_ensure_retry = nm_g_timeout_source_new_seconds (ACD_ENSURE_RATELIMIT_MSEC / 1000u,
G_PRIORITY_DEFAULT,
_l3_acd_nacd_instance_ensure_retry_cb,
self,
NULL);
g_source_attach (self->priv.p->nacd_instance_ensure_retry, NULL);
break;
case NM_TERNARY_DEFAULT:
break;
}
if (acd_data_notify) {
AcdData *acd_data;
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_INSTANCE_RESET, NULL);
}
}
static NAcd *
_l3_acd_nacd_instance_ensure (NML3Cfg *self,
gboolean *out_acd_not_supported)
{
nm_auto (n_acd_config_freep) NAcdConfig *config = NULL;
nm_auto (n_acd_unrefp) NAcd *nacd = NULL;
const guint8 *addr_bin;
gboolean acd_not_supported;
gboolean valid;
int fd;
int r;
nm_assert (NM_IS_L3CFG (self));
nm_assert (self->priv.ifindex > 0);
again:
if (G_LIKELY (self->priv.p->nacd)) {
NM_SET_OUT (out_acd_not_supported, FALSE);
return self->priv.p->nacd;
}
if (self->priv.p->nacd_instance_ensure_retry) {
/* we just tried to create an instance and failed. We are rate-limited,
* don't yet try again. */
NM_SET_OUT (out_acd_not_supported, self->priv.p->nacd_acd_not_supported);
return NULL;
}
valid = _acd_has_valid_link (self->priv.pllink, &addr_bin, &acd_not_supported);
if (!valid)
goto failed_create_acd;
nm_assert (!acd_not_supported);
r = n_acd_config_new (&config);
if (r)
goto failed_create_acd;
n_acd_config_set_ifindex (config, self->priv.ifindex);
n_acd_config_set_transport (config, N_ACD_TRANSPORT_ETHERNET);
n_acd_config_set_mac (config, addr_bin, ACD_SUPPORTED_ETH_ALEN);
r = n_acd_new (&nacd, config);
if (r)
goto failed_create_acd;
self->priv.p->nacd = g_steal_pointer (&nacd);
n_acd_get_fd (self->priv.p->nacd, &fd);
self->priv.p->nacd_source = nm_g_unix_fd_source_new (fd,
G_IO_IN,
G_PRIORITY_DEFAULT,
_l3_acd_nacd_event,
self,
NULL);
nm_g_source_attach (self->priv.p->nacd_source, NULL);
NM_SET_OUT (out_acd_not_supported, FALSE);
return self->priv.p->nacd;
failed_create_acd:
/* is-internal-error means that we failed to create the NAcd instance. Most likely due
* to being unable to create a file descriptor. Anyway, something is seriously wrong here.
*
* Otherwise, the MAC address might just not be suitable (ETH_ALEN) or we might have
* not NMPlatformLink. In that case, it means the interface is currently not ready to
* do acd. */
self->priv.p->nacd_acd_not_supported = acd_not_supported;
_l3_acd_nacd_instance_reset (self, NM_TERNARY_TRUE, FALSE);
goto again;
}
static NAcdProbe *
_l3_acd_nacd_instance_create_probe (NML3Cfg *self,
in_addr_t addr,
guint32 timeout_msec,
gpointer user_data,
gboolean *out_acd_not_supported,
const char **out_failure_reason)
{
gboolean acd_not_supported;
NAcdProbe *probe;
if (!_l3_acd_nacd_instance_ensure (self, &acd_not_supported)) {
NM_SET_OUT (out_acd_not_supported, acd_not_supported);
if (acd_not_supported)
NM_SET_OUT (out_failure_reason, "interface not suitable for ACD");
else
NM_SET_OUT (out_failure_reason, "failure to create nacd instance");
return NULL;
}
nm_assert (!acd_not_supported);
NM_SET_OUT (out_acd_not_supported, FALSE);
probe = _nm_n_acd_data_probe_new (self, addr, timeout_msec, user_data);
if (!probe) {
NM_SET_OUT (out_failure_reason, "failure to create nacd probe");
return NULL;
}
NM_SET_OUT (out_failure_reason, NULL);
return probe;
}
static void
_l3_acd_data_free_trackers (NML3Cfg *self,
AcdData *acd_data,
gboolean all /* or only dirty */)
{
AcdTrackData *acd_track;
AcdTrackData *acd_track_safe;
c_list_for_each_entry_safe (acd_track, acd_track_safe, &acd_data->acd_track_lst_head, acd_track_lst) {
/* If not "all" is requested, we only delete the dirty ones
* (and mark the survivors as dirty right away). */
if ( !all
&& !acd_track->acd_dirty) {
acd_track->acd_dirty = TRUE;
continue;
}
_LOGT_acd (acd_data,
"untrack "ACD_TRACK_FMT"",
ACD_TRACK_PTR (acd_track));
_acd_track_data_free (acd_track);
}
if (!c_list_is_empty (&acd_data->acd_track_lst_head))
return;
if (!g_hash_table_remove (self->priv.p->acd_lst_hash, acd_data))
nm_assert_not_reached ();
_acd_data_free (acd_data);
}
static void
_l3_acd_data_prune (NML3Cfg *self,
gboolean all /* or only dirty */)
{
AcdData *acd_data_safe;
AcdData *acd_data;
c_list_for_each_entry_safe (acd_data, acd_data_safe, &self->priv.p->acd_lst_head, acd_lst)
_l3_acd_data_free_trackers (self, acd_data, all);
}
static AcdData *
_l3_acd_data_find (NML3Cfg *self,
in_addr_t addr)
{
return nm_g_hash_table_lookup (self->priv.p->acd_lst_hash, &addr);
}
static void
_l3_acd_data_add (NML3Cfg *self,
const NML3ConfigData *l3cd,
const NMPObject *obj,
gconstpointer tag,
guint32 acd_timeout_msec)
{
in_addr_t addr = NMP_OBJECT_CAST_IP4_ADDRESS (obj)->address;
AcdTrackData *acd_track;
AcdData *acd_data;
const char *track_mode;
if (ACD_ADDR_SKIP (addr))
return;
acd_data = _l3_acd_data_find (self, addr);
if (acd_timeout_msec > ACD_MAX_TIMEOUT_MSEC) {
/* we limit the maximum timeout. Otherwise we have to handle integer overflow
* when adding timeouts. */
acd_timeout_msec = ACD_MAX_TIMEOUT_MSEC;
}
if (!acd_data) {
if (G_UNLIKELY (!self->priv.p->acd_lst_hash)) {
G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET(AcdData, addr) == 0);
self->priv.p->acd_lst_hash = g_hash_table_new (nm_puint32_hash,
nm_puint32_equals);
}
acd_data = g_slice_new (AcdData);
*acd_data = (AcdData) {
.self = self,
.addr = addr,
.acd_track_lst_head = C_LIST_INIT (acd_data->acd_track_lst_head),
.acd_state = ACD_STATE_INIT,
.probing_timestamp_msec = 0,
.probe_result = FALSE,
};
c_list_link_tail (&self->priv.p->acd_lst_head, &acd_data->acd_lst);
if (!g_hash_table_add (self->priv.p->acd_lst_hash, acd_data))
nm_assert_not_reached ();
acd_track = NULL;
} else {
acd_track = _acd_data_find_track (acd_data,
l3cd,
obj,
tag);
}
if (!acd_track) {
acd_track = g_slice_new (AcdTrackData);
*acd_track = (AcdTrackData) {
.l3cd = nm_l3_config_data_ref (l3cd),
.obj = nmp_object_ref (obj),
.tag = tag,
.acd_dirty = FALSE,
.acd_timeout_msec = acd_timeout_msec,
};
c_list_link_tail (&acd_data->acd_track_lst_head, &acd_track->acd_track_lst);
track_mode = "new";
} else {
nm_assert (acd_track->acd_dirty);
acd_track->acd_dirty = FALSE;
if (acd_track->acd_timeout_msec != acd_timeout_msec) {
acd_track->acd_timeout_msec = acd_timeout_msec;
track_mode = "update";
} else
track_mode = NULL;
}
if (track_mode) {
_LOGT_acd (acd_data,
"track "ACD_TRACK_FMT" with timeout %u msec (%s)",
ACD_TRACK_PTR (acd_track),
acd_timeout_msec,
track_mode);
}
}
static void
_l3_acd_data_add_all (NML3Cfg *self,
const L3ConfigData *const*infos,
guint infos_len)
{
AcdData *acd_data;
guint i_info;
/* First we add/track all the relevant addresses for ACD. */
for (i_info = 0; i_info < infos_len; i_info++) {
const L3ConfigData *info = infos[i_info];
NMDedupMultiIter iter;
const NMPObject *obj;
nm_l3_config_data_iter_obj_for_each (&iter, info->l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS)
_l3_acd_data_add (self, info->l3cd, obj, info->tag, info->acd_timeout_msec);
}
/* Then we do a pre-flight check, whether some of the acd_data entries can already
* move forward to automatically pass ACD. That is the case if acd_timeout_msec
* is zero (to disable ACD) or if the address is already configured on the
* interface. */
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst)
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_INIT, NULL);
}
static gboolean
_l3_acd_ready_on_idle_cb (gpointer user_data)
{
NML3Cfg *self = user_data;
nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source);
_LOGT ("acd: handle ACD changes on idle");
_l3_acd_platform_commit_acd_update (self);
return G_SOURCE_REMOVE;
}
static gboolean
_l3_acd_data_timeout_cb (gpointer user_data)
{
AcdData *acd_data = user_data;
NML3Cfg *self = acd_data->self;
nm_assert (NM_IS_L3CFG (self));
nm_clear_g_source_inst (&acd_data->acd_timeout_source);
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_TIMEOUT, NULL);
return G_SOURCE_REMOVE;
}
static void
_l3_acd_data_timeout_schedule (AcdData *acd_data,
gint64 now_msec,
gint64 expiry_msec,
gboolean msec_granularity)
{
nm_assert (expiry_msec > 0);
nm_assert (now_msec > 0);
if ( acd_data->acd_timeout_source
&& acd_data->acd_timeout_expiry_msec == expiry_msec)
return;
nm_clear_g_source_inst (&acd_data->acd_timeout_source);
acd_data->acd_timeout_expiry_msec = expiry_msec;
if (msec_granularity) {
acd_data->acd_timeout_source = nm_g_timeout_source_new (NM_MAX (0, expiry_msec - now_msec),
G_PRIORITY_DEFAULT,
_l3_acd_data_timeout_cb,
acd_data,
NULL);
} else {
acd_data->acd_timeout_source = nm_g_timeout_source_new_seconds ((NM_MAX (0, expiry_msec - now_msec) + 999) / 1000,
G_PRIORITY_DEFAULT,
_l3_acd_data_timeout_cb,
acd_data,
NULL);
}
g_source_attach (acd_data->acd_timeout_source, NULL);
}
static void
_l3_acd_data_timeout_schedule_probing_restart (AcdData *acd_data,
gint64 now_msec)
{
gint64 expiry_msec;
gint64 timeout_msec;
nm_assert (acd_data);
nm_assert (now_msec > 0);
nm_assert (acd_data->acd_state == ACD_STATE_PROBING);
nm_assert (!acd_data->nacd_probe);
nm_assert (acd_data->probing_timeout_msec > 0);
nm_assert (acd_data->probing_timestamp_msec > 0);
expiry_msec = acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC;
timeout_msec = NM_MAX (0, expiry_msec - now_msec);
if (timeout_msec > 1000) {
/* we poll at least once per second to re-check the state. */
timeout_msec = 1000;
}
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec + timeout_msec, TRUE);
}
static void
_l3_acd_data_timeout_schedule_probing_full_restart (AcdData *acd_data,
gint64 now_msec)
{
nm_assert (acd_data);
nm_assert (now_msec > 0);
nm_assert (acd_data->acd_state == ACD_STATE_PROBE_DONE);
nm_assert (!acd_data->probe_result);
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec + ACD_WAIT_TIME_PROBING_FULL_RESTART_MSEC, FALSE);
}
static void
_l3_acd_data_timeout_schedule_announce_restart (AcdData *acd_data,
gint64 now_msec)
{
nm_assert (acd_data);
nm_assert (now_msec > 0);
nm_assert (acd_data->acd_state == ACD_STATE_PROBE_DONE);
nm_assert (acd_data->probe_result);
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec + ACD_WAIT_TIME_ANNOUNCE_RESTART_MSEC, FALSE);
}
static void
_l3_acd_data_notify_acd_failed (NML3Cfg *self,
AcdData *acd_data,
gboolean force_all)
{
gs_free NML3ConfigNotifyPayloadAcdFailedSource *sources_free = NULL;
NML3ConfigNotifyPayloadAcdFailedSource *sources = NULL;
NML3ConfigNotifyPayload payload;
AcdTrackData *acd_track;
guint i, n;
NMTernary acd_failed_notified_selector;
nm_assert (NM_IS_L3CFG (self));
nm_assert (acd_data);
nm_assert (_acd_data_collect_tracks_data (acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) == 0);
acd_failed_notified_selector = force_all
? NM_TERNARY_DEFAULT
: FALSE;
n = _acd_data_collect_tracks_data (acd_data, NM_TERNARY_DEFAULT, acd_failed_notified_selector, NULL);
if (n == 0)
return;
if (!force_all) {
_LOGT_acd (acd_data,
"state: acd probe failed earlier. Emit notification for new trackers");
}
if (n * sizeof (sources[0]) > 300) {
sources_free = g_new (NML3ConfigNotifyPayloadAcdFailedSource, n);
sources = sources_free;
} else
sources = g_newa (NML3ConfigNotifyPayloadAcdFailedSource, n);
i = 0;
c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
if ( !force_all
&& acd_track->acd_failed_notified) {
/* already notified before. Skip. */
continue;
}
nm_assert (i < n);
acd_track->acd_failed_notified = TRUE;
sources[i++] = (NML3ConfigNotifyPayloadAcdFailedSource) {
.obj = nmp_object_ref (acd_track->obj),
.l3cd = nm_l3_config_data_ref (acd_track->l3cd),
.tag = acd_track->tag,
};
}
nm_assert (i == n);
payload = (NML3ConfigNotifyPayload) {
.acd_failed = {
.addr = acd_data->addr,
.sources_len = n,
.sources = sources,
},
};
_l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ACD_FAILED, &payload);
for (i = 0; i < n; i++) {
nmp_object_unref (sources[i].obj);
nm_l3_config_data_unref (sources[i].l3cd);
}
}
static void
_l3_acd_data_state_change (NML3Cfg *self,
AcdData *acd_data,
AcdStateChangeMode state_change_mode,
NAcdEvent *event)
{
guint32 acd_timeout_msec;
gint64 now_msec = 0;
const char *log_reason;
gboolean was_probing;
/* Keeping track of ACD inevitably requires keeping (and mutating) state. Then a multitude of
* things can happen, and depending on the state, we need to do something.
*
* Here, all the state for one address that we probe/announce is tracked in AcdData/acd_data.
*
* The acd_data has a list of AcdTrackData/acd_track_lst_head, which are configuration items
* that are interested in configuring this address. The "owners" of the ACD check for a certain
* address.
*
* We try to do all the state changes in this _l3_acd_data_state_change() function, where --
* depending on the @state_change_mode -- we progress the state.
*
* It is complicated, but I think this is not really avoidable if you want to handle all
* the special things (state-changes) that can happen.
*/
nm_assert (NM_IS_L3CFG (self));
nm_assert (acd_data);
nm_assert (!c_list_is_empty (&acd_data->acd_track_lst_head));
was_probing = acd_data->acd_state < ACD_STATE_PROBE_DONE;
switch (state_change_mode) {
case ACD_STATE_CHANGE_MODE_INIT: {
AcdTrackData *acd_track;
gboolean any_no_timeout;
/* we are called from _l3_acd_data_add_all(), and we do a fast check whether
* newly tracked entries already passed ACD so that we can use the address
* right away. */
if (_l3_acd_ipv4_addresses_on_link_contains (self, acd_data->addr)) {
/* the address is already configured on the link. It is an automatic pass. */
if (_acd_data_collect_tracks_data (acd_data, FALSE, NM_TERNARY_DEFAULT, NULL) <= 0) {
/* The entry has no non-dirty trackers, that means, it's no longer referenced
* and will be removed during the next _l3_acd_data_prune(). We can ignore
* this entry. */
return;
}
log_reason = "address initially already configured";
goto handle_probing_acd_good;
}
/* we are called at the end of _l3_acd_data_add_all(). We updated the list of a
* all tracked IP addresses before we actually collect the addresses that are
* ready. We don't do regular handling of ACD states at this point, however,
* we check whether ACD for new elements is disabled entirely, so we can signal
* the address are ready right away (without going through another hop). */
if (acd_data->acd_state != ACD_STATE_INIT) {
/* this element is not new and we don't perform the quick-check. */
return;
}
any_no_timeout = FALSE;
c_list_for_each_entry (acd_track, &acd_data->acd_track_lst_head, acd_track_lst) {
/* There should be no dirty trackers, because the element is in init-state. */
nm_assert (!acd_track->acd_dirty);
if (acd_track->acd_timeout_msec <= 0) {
/* ACD for this element is disabled. We can process is right away. */
any_no_timeout = TRUE;
break;
}
}
if (!any_no_timeout) {
/* there are elements that request the address, but they all specify
* an ACD timeout. We cannot progress the state. */
return;
}
/* ACD is disabled, we can artificially moving the state further to
* ACD_STATE_PROBE_DONE and configure the address right away. This avoids
* that we go through another hop.
*/
_LOGT_acd (acd_data,
"state: probe-done good (ACD disabled by configuration from the start)");
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
return;
}
case ACD_STATE_CHANGE_MODE_POST_COMMIT:
goto handle_post_commit;
case ACD_STATE_CHANGE_MODE_TIMEOUT: {
if ( acd_data->acd_state == ACD_STATE_PROBING
&& !acd_data->nacd_probe) {
const char *failure_reason;
gboolean acd_not_supported;
nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_EXTRA_TIME_MSEC + ACD_WAIT_PROBING_EXTRA_TIME2_MSEC >= now_msec) {
_LOGT_acd (acd_data,
"state: probe-good (waiting for creating probe timed out. Assume good)");
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
}
/* try create a new probe. The timeout is always as originally requested. */
acd_data->nacd_probe = _l3_acd_nacd_instance_create_probe (self,
acd_data->addr,
acd_data->probing_timeout_msec,
acd_data,
&acd_not_supported,
&failure_reason);
if (acd_not_supported) {
nm_assert (!acd_data->nacd_probe);
_LOGT_acd (acd_data,
"state: probe-good (interface does not support ACD anymore after timeout)");
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
}
if (!acd_data->nacd_probe) {
_LOGT_acd (acd_data,
"state: probing not possible at this time (%s). Wait longer",
failure_reason);
_l3_acd_data_timeout_schedule_probing_restart (acd_data, now_msec);
return;
}
/* probing started (with the original timeout. Note that acd_data->probing_time*_msec
* no longer corresponds to the actual timeout of the nacd_probe. This is not a problem
* because at this point we only trust the internal timer from nacd_probe to get
* it right. Instead, we keep acd_data->probing_time*_msec unchanged, to remember when
* we originally wanted to start. */
_LOGT_acd (acd_data,
"state: probing started (after retry, timeout %u msec)",
acd_data->probing_timeout_msec);
return;
}
if ( acd_data->acd_state == ACD_STATE_PROBE_DONE
&& !acd_data->probe_result) {
/* Probing is done, but previously we detected a conflict. After a restart, we retry to
* probe. */
nm_assert (!acd_data->nacd_probe);
nm_assert (!acd_data->announcing_failed_is_retrying);
_LOGT_acd (acd_data,
"state: restart a new probe after previous conflict");
acd_data->acd_state = ACD_STATE_INIT;
goto handle_post_commit;
}
if ( acd_data->acd_state == ACD_STATE_PROBE_DONE
&& acd_data->probe_result
&& !acd_data->nacd_probe
&& acd_data->announcing_failed_is_retrying) {
/* Probing is done, but previously we failed to start announcing. Retry now. */
nm_assert (!was_probing);
_LOGT_acd (acd_data,
"state: retry announcing address");
acd_data->announcing_failed_is_retrying = FALSE;
goto handle_probe_done;
}
return;
}
case ACD_STATE_CHANGE_MODE_NACD_READY:
if (acd_data->acd_state == ACD_STATE_PROBING) {
log_reason = "acd indicates ready";
goto handle_probing_acd_good;
}
if (acd_data->acd_state == ACD_STATE_ANNOUNCING) {
_LOGT_acd (acd_data,
"state: ready to start announcing");
if (n_acd_probe_announce (acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0)
nm_assert_not_reached ();
return;
}
/* nacd really shouldn't call us in this state. There is a bug somewhere. */
nm_assert_not_reached ();
return;
case ACD_STATE_CHANGE_MODE_NACD_USED: {
gs_free char *str_to_free = NULL;
nm_assert (acd_data->acd_state == ACD_STATE_PROBING);
_LOGT_acd (acd_data,
"state: probe-done bad (address already in use by %s)",
nm_utils_bin2hexstr_a (event->_acd_event_payload.sender,
event->_acd_event_payload.n_sender,
':',
FALSE,
&str_to_free));
acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe);
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = FALSE;
goto handle_probe_done;
}
case ACD_STATE_CHANGE_MODE_EXTERNAL_ADDED:
/* the address is configured on the link. This means, ACD passed */
log_reason = "address configured on link";
goto handle_probing_acd_good;
case ACD_STATE_CHANGE_MODE_EXTERNAL_REMOVED:
/* The address got removed. Either we ourself removed it or it was removed externally.
* In either case, it's not clear what we should do about that, regardless in which
* ACD state we are, so ignore it. */
_LOGT_acd (acd_data,
"state: address was externally removed. Ignore");
return;
case ACD_STATE_CHANGE_MODE_NACD_DOWN:
if (acd_data->acd_state < ACD_STATE_PROBE_DONE) {
/* we are probing, but the probe has a problem that the link went down. Maybe
* we need to restart. */
nm_assert (acd_data->acd_state == ACD_STATE_PROBING);
if (!acd_data->nacd_probe) {
/* we are in probing state, but currently not really probing. A timer is
* running, and we will handle this situation that way. */
return;
}
/* We abort the probing, but we also schedule a timer to restart it. Let
* the regular re-start handling handle this. */
_LOGT_acd (acd_data,
"state: interface-down. Probing aborted but we keep waiting to retry");
acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe);
_l3_acd_data_timeout_schedule_probing_restart (acd_data, now_msec);
return;
}
/* We already completed a probe and acted accordingly (by either configuring the address
* already or by rejecting it). We cannot (easily) re-evaluate what to do now. Should
* we later restart probing? But what about the decisions we already made??
* Ignore the situation. */
return;
case ACD_STATE_CHANGE_MODE_LINK_NOW_UP:
/* The interface just came up. */
if (acd_data->acd_state <= ACD_STATE_PROBING) {
nm_auto (n_acd_probe_freep) NAcdProbe *probe = NULL;
const char *failure_reason;
gboolean acd_not_supported;
/* the interface was probing. We will restart the probe. */
nm_assert (acd_data->acd_state == ACD_STATE_PROBING);
nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
if (!acd_data->nacd_probe) {
/* We currently are waiting to restart probing. We don't handle the link-up
* event here, we only trigger a timeout right away. */
_LOGT_acd (acd_data,
"state: ignore link up event while we are waiting to start probing");
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec, TRUE);
return;
}
if (acd_data->probing_timestamp_msec + ACD_WAIT_PROBING_RESTART_TIME_MSEC >= now_msec) {
/* This probe was already started quite a while ago. We ignore the link-up event
* and let it complete regularly. This is to avoid restarting to probing indefinitely. */
_LOGT_acd (acd_data,
"state: ignore link up event for a probe started long ago");
return;
}
probe = _l3_acd_nacd_instance_create_probe (self,
acd_data->addr,
acd_data->probing_timeout_msec,
acd_data,
&acd_not_supported,
&failure_reason);
if (!probe) {
_LOGT_acd (acd_data,
"state: link up event would cause to retry probing, but creating a probe failed (%s). Ignore and keep previous probe",
failure_reason);
return;
}
NM_SWAP (&probe, &acd_data->nacd_probe);
/* We just restarted probing. Note that we don't touch the original acd_data->probing_time*_msec
* times, otherwise a repeated link up/down cycle could extend the probing indefinitely.
*
* This is despite the new probe just started counting now. So, at this point, the
* timestamp/timeout of acd_data no longer corresponds to the internal timestamp of
* acd_data->nacd_probe. But since we don't run our own timer against the internal timer of
* acd_data->nacd_probe, that is not a problem.
*/
_LOGT_acd (acd_data,
"state: probing restarted (after link up, new timeout %u msec)",
acd_data->probing_timeout_msec);
return;
}
/* we are already done with the ACD state. Bringing up an interface has
* no further consequence w.r.t. the ACD state. */
return;
case ACD_STATE_CHANGE_MODE_INSTANCE_RESET:
if (acd_data->acd_state <= ACD_STATE_PROBING) {
nm_assert (acd_data->acd_state == ACD_STATE_PROBING);
_LOGT_acd (acd_data,
"state: n-acd instance reset. Trigger a restart of the probing (was %sprobing)",
acd_data->nacd_probe
? ""
: "not");
/* Just destroy the current probe (if any) and retrigger a restart right away. */
acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe);
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec, TRUE);
return;
}
if (acd_data->probe_result) {
_LOGT_acd (acd_data,
"state: n-acd instance reset. Restart announcing");
} else {
_LOGT_acd (acd_data,
"state: n-acd instance reset. Reprobe the address that conflicted before");
}
acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe);
acd_data->acd_state = ACD_STATE_PROBE_DONE;
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec, TRUE);
break;
}
nm_assert_not_reached ();
return;
handle_post_commit:
/* we just did a commit of the IP configuration and now visit all ACD states
* and kick off the necessary actions... */
if (_l3_acd_ipv4_addresses_on_link_contains (self, acd_data->addr)) {
log_reason = "address already configured";
goto handle_probing_acd_good;
}
if (_acd_data_collect_tracks_data (acd_data, TRUE, NM_TERNARY_DEFAULT, &acd_timeout_msec) <= 0)
nm_assert_not_reached ();
if (acd_timeout_msec <= 0) {
log_reason = "ACD disabled by configuration";
goto handle_probing_acd_good;
}
switch (acd_data->acd_state) {
case ACD_STATE_INIT: {
const char *failure_reason;
gboolean acd_not_supported;
NAcdProbe *probe;
nm_assert (!acd_data->nacd_probe);
probe = _l3_acd_nacd_instance_create_probe (self,
acd_data->addr,
acd_timeout_msec,
acd_data,
&acd_not_supported,
&failure_reason);
if (acd_not_supported) {
nm_assert (!probe);
_LOGT_acd (acd_data,
"state: probe-good (interface does not support ACD)");
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
}
if (!probe) {
_LOGT_acd (acd_data,
"state: probing currently not possible (timeout %u msec; %s)",
acd_timeout_msec,
failure_reason);
acd_data->acd_state = ACD_STATE_PROBING;
acd_data->probing_timeout_msec = acd_timeout_msec;
acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
_l3_acd_data_timeout_schedule_probing_restart (acd_data, now_msec);
return;
}
_LOGT_acd (acd_data,
"state: start probing (timeout %u msec)",
acd_timeout_msec);
acd_data->acd_state = ACD_STATE_PROBING;
acd_data->nacd_probe = probe;
acd_data->probing_timeout_msec = acd_timeout_msec;
acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
return;
}
case ACD_STATE_PROBING: {
nm_auto (n_acd_probe_freep) NAcdProbe *probe = NULL;
const char *failure_reason;
gboolean acd_not_supported;
gint64 old_expiry_msec;
gint64 new_expiry_msec;
nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
new_expiry_msec = now_msec + acd_timeout_msec;
old_expiry_msec = acd_data->probing_timestamp_msec + acd_data->probing_timeout_msec;
if (!acd_data->nacd_probe) {
/* we are currently waiting for restarting a probe. At this point, at most we have
* to adjust the timeout/timestamp and let the regular timeouts handle this. */
if (new_expiry_msec >= old_expiry_msec) {
/* the running timeout expires before the new timeout. We don't update the timestamp/timerout,
* because we don't want to prolong the overall probing time. */
return;
}
/* update the timers after out timeout got reduced. Also, reschedule the timeout
* so that it expires immediately. */
acd_data->probing_timestamp_msec = now_msec;
acd_data->probing_timeout_msec = acd_timeout_msec;
_l3_acd_data_timeout_schedule (acd_data, now_msec, now_msec, TRUE);
return;
}
if (new_expiry_msec >= old_expiry_msec) {
/* we already have ACD running with a timeout that expires before the requested one. There
* is nothing to do at this time. */
return;
}
/* the timeout got reduced. We try to restart the probe. */
probe = _l3_acd_nacd_instance_create_probe (self,
acd_data->addr,
acd_timeout_msec,
acd_data,
&acd_not_supported,
&failure_reason);
NM_SWAP (&probe, &acd_data->nacd_probe);
if (acd_not_supported) {
nm_assert (!acd_data->nacd_probe);
_LOGT_acd (acd_data,
"state: probe-good (interface does not support ACD anymore)");
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
}
if (!acd_data->nacd_probe) {
_LOGT_acd (acd_data,
"state: probing currently still not possible (timeout %u msec; %s)",
acd_timeout_msec,
failure_reason);
acd_data->acd_state = ACD_STATE_PROBING;
acd_data->probing_timeout_msec = acd_timeout_msec;
acd_data->probing_timestamp_msec = now_msec;
_l3_acd_data_timeout_schedule_probing_restart (acd_data, now_msec);
return;
}
/* We update the timestamps (after also restarting the probe).
*
* Note that we only reduced the overall expiry. */
acd_data->probing_timestamp_msec = now_msec;
acd_data->probing_timeout_msec = acd_timeout_msec;
_LOGT_acd (acd_data,
"state: restart probing (timeout %u msec)",
acd_timeout_msec);
return;
}
case ACD_STATE_PROBE_DONE:
case ACD_STATE_ANNOUNCING:
goto handle_probe_done;
}
nm_assert_not_reached ();
return;
handle_probing_acd_good:
switch (acd_data->acd_state) {
case ACD_STATE_INIT:
_LOGT_acd (acd_data,
"state: probe-done good (%s, inializingbcwin)",
log_reason);
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
case ACD_STATE_PROBING:
_LOGT_acd (acd_data,
"state: probe-done good (%s, probing done)",
log_reason);
if (state_change_mode != ACD_STATE_CHANGE_MODE_NACD_READY)
acd_data->nacd_probe = n_acd_probe_free (acd_data->nacd_probe);
acd_data->acd_state = ACD_STATE_PROBE_DONE;
acd_data->probe_result = TRUE;
goto handle_probe_done;
case ACD_STATE_PROBE_DONE:
if (!acd_data->probe_result) {
nm_assert (!acd_data->nacd_probe);
_LOGT_acd (acd_data,
"state: probe-done good (%s, after probe failed)",
log_reason);
acd_data->probe_result = TRUE;
}
goto handle_probe_done;
case ACD_STATE_ANNOUNCING:
nm_assert (acd_data->probe_result);
goto handle_probe_done;
}
nm_assert_not_reached ();
return;
handle_probe_done:
nm_assert (NM_IN_SET (acd_data->acd_state, ACD_STATE_PROBE_DONE,
ACD_STATE_ANNOUNCING));
if (state_change_mode == ACD_STATE_CHANGE_MODE_INIT)
return;
if (acd_data->acd_state >= ACD_STATE_ANNOUNCING) {
nm_assert (acd_data->nacd_probe);
nm_assert (acd_data->probe_result);
return;
}
if (!acd_data->probe_result) {
nm_assert (acd_data->acd_state == ACD_STATE_PROBE_DONE);
nm_assert (!acd_data->nacd_probe);
/* we just completed probing with negative result.
* Emit a signal, but also reschedule a timer to restart. */
if (was_probing) {
_LOGT_acd (acd_data,
"state: acd probe failed; signal failure");
acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
_l3_acd_data_timeout_schedule_probing_full_restart (acd_data, now_msec);
}
_l3_acd_data_notify_acd_failed (self, acd_data, was_probing);
return;
}
if ( was_probing
&& acd_data->probe_result) {
/* probing just completed. Schedule handling the change. */
_LOGT_acd (acd_data,
"state: acd probe succeed");
if (!self->priv.p->acd_ready_on_idle_source) {
self->priv.p->acd_ready_on_idle_source = nm_g_idle_source_new (G_PRIORITY_DEFAULT,
_l3_acd_ready_on_idle_cb,
self,
NULL);
g_source_attach (self->priv.p->acd_ready_on_idle_source, NULL);
}
}
if (!acd_data->nacd_probe) {
const char *failure_reason;
NAcdProbe *probe;
if (acd_data->announcing_failed_is_retrying) {
/* we already failed to create a probe. We are ratelimited to retry, but
* we have a timer pending... */
return;
}
probe = _l3_acd_nacd_instance_create_probe (self,
acd_data->addr,
0,
acd_data,
NULL,
&failure_reason);
if (!probe) {
/* we failed to create a probe for announcing the address. We log a (rate limited)
* warning and start a timer to retry. */
_LOGT_acd (acd_data,
"state: start announcing failed to create probe (%s)",
failure_reason);
acd_data->announcing_failed_is_retrying = TRUE;
acd_data->probing_timestamp_msec = nm_utils_get_monotonic_timestamp_msec_cached (&now_msec);
_l3_acd_data_timeout_schedule_announce_restart (acd_data, now_msec);
return;
}
_LOGT_acd (acd_data, "state: start announcing (with new probe)");
acd_data->nacd_probe = probe;
acd_data->acd_state = ACD_STATE_ANNOUNCING;
return;
}
if (acd_data->acd_state == ACD_STATE_PROBE_DONE) {
_LOGT_acd (acd_data, "state: start announcing (with existing probe)");
acd_data->acd_state = ACD_STATE_ANNOUNCING;
if (n_acd_probe_announce (acd_data->nacd_probe, N_ACD_DEFEND_ALWAYS) != 0)
nm_assert_not_reached ();
return;
}
}
static void
_l3_acd_data_process_changes (NML3Cfg *self)
{
gboolean acd_is_announcing = FALSE;
gboolean acd_is_pending = FALSE;
AcdData *acd_data;
_l3_acd_data_prune (self, FALSE);
c_list_for_each_entry (acd_data, &self->priv.p->acd_lst_head, acd_lst) {
_l3_acd_data_state_change (self, acd_data, ACD_STATE_CHANGE_MODE_POST_COMMIT, NULL);
if (acd_data->acd_state < ACD_STATE_PROBE_DONE)
acd_is_pending = TRUE;
else if ( acd_data->acd_state >= ACD_STATE_ANNOUNCING
|| ( acd_data->acd_state >= ACD_STATE_PROBE_DONE
&& acd_data->probe_result))
acd_is_announcing = TRUE;
}
self->priv.p->acd_is_pending = acd_is_pending;
self->priv.p->acd_is_announcing = acd_is_announcing;
if ( !acd_is_pending
&& !acd_is_announcing)
_l3_acd_nacd_instance_reset (self, NM_TERNARY_DEFAULT, FALSE);
}
/*****************************************************************************/
static GArray *
_l3_config_datas_ensure (GArray **p_arr)
{
if (!*p_arr)
*p_arr = g_array_new (FALSE, FALSE, sizeof (L3ConfigData));
return *p_arr;
}
#define _l3_config_datas_at(l3_config_datas, idx) \
(&g_array_index ((l3_config_datas), L3ConfigData, (idx)))
static gssize
_l3_config_datas_find_next (GArray *l3_config_datas,
guint start_idx,
gconstpointer needle_tag,
const NML3ConfigData *needle_l3cd)
{
guint i;
nm_assert (l3_config_datas);
nm_assert (start_idx <= l3_config_datas->len);
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)
&& NM_IN_SET (needle_l3cd, NULL, l3_config_data->l3cd))
return i;
}
return -1;
}
static int
_l3_config_datas_get_sorted_cmp (gconstpointer p_a,
gconstpointer p_b,
gpointer user_data)
{
const L3ConfigData *a = *((L3ConfigData **) p_a);
const L3ConfigData *b = *((L3ConfigData **) p_b);
nm_assert (a);
nm_assert (b);
nm_assert (nm_l3_config_data_get_ifindex (a->l3cd) == nm_l3_config_data_get_ifindex (b->l3cd));
/* we sort the entries with higher priority (more important, lower numerical value)
* first. */
NM_CMP_FIELD (a, b, priority);
/* if the priority is not unique, we sort them in the order they were added,
* with the oldest first (lower numerical value). */
NM_CMP_FIELD (a, b, pseudo_timestamp);
return nm_assert_unreachable_val (0);
}
#define _l3_config_datas_get_sorted_a(l3_config_datas, \
out_infos, \
out_infos_len, \
out_infos_free) \
G_STMT_START { \
GArray *const _l3_config_datas = (l3_config_datas); \
const L3ConfigData *const**const _out_infos = (out_infos); \
guint *const _out_infos_len = (out_infos_len); \
const L3ConfigData ***const _out_infos_free = (out_infos_free); \
gs_free const L3ConfigData **_infos_free = NULL; \
const L3ConfigData **_infos; \
guint _l3_config_datas_len; \
guint _i; \
\
_l3_config_datas_len = nm_g_array_len (_l3_config_datas); \
\
if (_l3_config_datas_len == 0) \
_infos = NULL; \
else if (_l3_config_datas_len < 300 / sizeof (_infos[0])) \
_infos = g_alloca (_l3_config_datas_len * sizeof (_infos[0])); \
else { \
_infos_free = g_new (const L3ConfigData *, _l3_config_datas_len); \
_infos = _infos_free; \
} \
for (_i = 0; _i < _l3_config_datas_len; _i++) \
_infos[_i] = _l3_config_datas_at (_l3_config_datas, _i); \
\
if (_l3_config_datas_len > 1) { \
g_qsort_with_data (_infos, \
_l3_config_datas_len, \
sizeof (_infos[0]), \
_l3_config_datas_get_sorted_cmp, \
NULL); \
} \
\
*_out_infos = _infos; \
*_out_infos_len = _l3_config_datas_len; \
*_out_infos_free = g_steal_pointer (&_infos_free); \
} G_STMT_END
static void
_l3_config_datas_remove_index_fast (GArray *arr,
guint idx)
{
L3ConfigData *l3_config_data;
nm_assert (arr);
nm_assert (idx < arr->len);
l3_config_data = _l3_config_datas_at (arr, idx);
nm_l3_config_data_unref (l3_config_data->l3cd);
g_array_remove_index_fast (arr, idx);
}
void
nm_l3cfg_mark_config_dirty (NML3Cfg *self,
gconstpointer tag,
gboolean dirty)
{
GArray *l3_config_datas;
gssize idx;
nm_assert (NM_IS_L3CFG (self));
nm_assert (tag);
l3_config_datas = self->priv.p->l3_config_datas;
if (!l3_config_datas)
return;
idx = 0;
while (TRUE) {
idx = _l3_config_datas_find_next (l3_config_datas,
idx,
tag,
NULL);
if (idx < 0)
return;
_l3_config_datas_at (l3_config_datas, idx)->dirty = dirty;
idx++;
}
}
gboolean
nm_l3cfg_add_config (NML3Cfg *self,
gconstpointer tag,
gboolean replace_same_tag,
const NML3ConfigData *l3cd,
int priority,
guint32 default_route_penalty_4,
guint32 default_route_penalty_6,
guint32 acd_timeout_msec,
NML3ConfigMergeFlags merge_flags)
{
GArray *l3_config_datas;
L3ConfigData *l3_config_data;
gssize idx;
gboolean changed = FALSE;
nm_assert (NM_IS_L3CFG (self));
nm_assert (tag);
nm_assert (l3cd);
nm_assert (nm_l3_config_data_get_ifindex (l3cd) == self->priv.ifindex);
l3_config_datas = _l3_config_datas_ensure (&self->priv.p->l3_config_datas);
idx = _l3_config_datas_find_next (l3_config_datas,
0,
tag,
replace_same_tag ? NULL : l3cd);
if (replace_same_tag) {
gssize idx2;
idx2 = idx;
idx = -1;
while (TRUE) {
l3_config_data = _l3_config_datas_at (l3_config_datas, idx2);
if (l3_config_data->l3cd == l3cd) {
nm_assert (idx == -1);
idx = idx2;
continue;
}
changed = TRUE;
_l3_config_datas_remove_index_fast (l3_config_datas, idx2);
idx2 = _l3_config_datas_find_next (l3_config_datas, idx2, tag, NULL);
if (idx2 < 0)
break;
}
}
if (idx < 0) {
l3_config_data = nm_g_array_append_new (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_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,
};
changed = TRUE;
} else {
l3_config_data = _l3_config_datas_at (l3_config_datas, idx);
l3_config_data->dirty = FALSE;
nm_assert (l3_config_data->tag == 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->merge_flags != merge_flags) {
l3_config_data->merge_flags = merge_flags;
changed = TRUE;
}
if (l3_config_data->default_route_penalty_4 != default_route_penalty_4) {
l3_config_data->default_route_penalty_4 = default_route_penalty_4;
changed = TRUE;
}
if (l3_config_data->default_route_penalty_6 != default_route_penalty_6) {
l3_config_data->default_route_penalty_6 = default_route_penalty_6;
changed = TRUE;
}
if (l3_config_data->acd_timeout_msec != acd_timeout_msec) {
l3_config_data->acd_timeout_msec = acd_timeout_msec;
changed = TRUE;
}
}
if (changed)
self->priv.changed_configs = TRUE;
return changed;
}
static gboolean
_l3cfg_remove_config (NML3Cfg *self,
gconstpointer tag,
gboolean only_dirty,
const NML3ConfigData *l3cd)
{
GArray *l3_config_datas;
gboolean changed;
gssize idx;
nm_assert (NM_IS_L3CFG (self));
nm_assert (tag);
l3_config_datas = self->priv.p->l3_config_datas;
if (!l3_config_datas)
return FALSE;
idx = 0;
changed = FALSE;
while (TRUE) {
idx = _l3_config_datas_find_next (l3_config_datas,
idx,
tag,
l3cd);
if (idx < 0)
return changed;
if ( only_dirty
&& !_l3_config_datas_at (l3_config_datas, idx)->dirty) {
idx++;
continue;
}
self->priv.changed_configs = TRUE;
_l3_config_datas_remove_index_fast (l3_config_datas, idx);
if (l3cd) {
/* only one was requested to be removed. We are done. */
return TRUE;
}
changed = TRUE;
}
}
gboolean
nm_l3cfg_remove_config (NML3Cfg *self,
gconstpointer tag,
const NML3ConfigData *ifcfg)
{
nm_assert (ifcfg);
return _l3cfg_remove_config (self, tag, FALSE, ifcfg);
}
gboolean
nm_l3cfg_remove_config_all (NML3Cfg *self,
gconstpointer tag,
gboolean only_dirty)
{
return _l3cfg_remove_config (self, tag, only_dirty, NULL);
}
/*****************************************************************************/
typedef struct {
NML3Cfg *self;
gconstpointer tag;
} L3ConfigMergeHookAddObjData;
static gboolean
_l3_hook_add_addr_cb (const NML3ConfigData *l3cd,
const NMPObject *obj,
gpointer user_data)
{
const L3ConfigMergeHookAddObjData *hook_data = user_data;
NML3Cfg *self = hook_data->self;
AcdData *acd_data;
in_addr_t addr;
if (NMP_OBJECT_GET_TYPE (obj) != NMP_OBJECT_TYPE_IP4_ADDRESS)
return TRUE;
addr = NMP_OBJECT_CAST_IP4_ADDRESS (obj)->address;
if (ACD_ADDR_SKIP (addr))
return TRUE;
acd_data = _l3_acd_data_find (self, addr);
nm_assert (acd_data);
nm_assert (_acd_track_data_is_not_dirty (_acd_data_find_track (acd_data, l3cd, obj, hook_data->tag)));
return _acd_data_probe_result_is_good (acd_data);
}
static void
_l3cfg_update_combined_config (NML3Cfg *self,
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;
guint l3_config_datas_len;
guint i;
nm_assert (NM_IS_L3CFG (self));
nm_assert (!out_old || !*out_old);
NM_SET_OUT (out_changed_configs, self->priv.changed_configs);
NM_SET_OUT (out_changed_combined_l3cd, FALSE);
if (!self->priv.changed_configs)
return;
self->priv.changed_configs = FALSE;
_l3_config_datas_get_sorted_a (self->priv.p->l3_config_datas,
&l3_config_datas,
&l3_config_datas_len,
&l3_config_datas_free);
_l3_acd_data_add_all (self,
l3_config_datas,
l3_config_datas_len);
if (l3_config_datas_len > 0) {
L3ConfigMergeHookAddObjData hook_data = {
.self = self,
};
l3cd = nm_l3_config_data_new (nm_platform_get_multi_idx (self->priv.platform),
self->priv.ifindex);
for (i = 0; i < l3_config_datas_len; i++) {
hook_data.tag = l3_config_datas[i]->tag;
nm_l3_config_data_merge (l3cd,
l3_config_datas[i]->l3cd,
l3_config_datas[i]->merge_flags,
l3_config_datas[i]->default_route_penalty_x,
_l3_hook_add_addr_cb,
&hook_data);
}
nm_assert (l3cd);
nm_assert (nm_l3_config_data_get_ifindex (l3cd) == self->priv.ifindex);
nm_l3_config_data_seal (l3cd);
}
if (nm_l3_config_data_equal (l3cd, self->priv.p->combined_l3cd))
return;
_LOGT ("desired IP configuration changed");
l3cd_old = g_steal_pointer (&self->priv.p->combined_l3cd);
self->priv.p->combined_l3cd = nm_l3_config_data_seal (g_steal_pointer (&l3cd));
NM_SET_OUT (out_old, nm_l3_config_data_ref (self->priv.p->combined_l3cd));
NM_SET_OUT (out_changed_combined_l3cd, TRUE);
}
/*****************************************************************************/
typedef struct {
const NMPObject *obj;
gint64 timestamp_msec;
bool dirty;
} RoutesTemporaryNotAvailableData;
static void
_routes_temporary_not_available_data_free (gpointer user_data)
{
RoutesTemporaryNotAvailableData *data = user_data;
nmp_object_unref (data->obj);
nm_g_slice_free (data);
}
#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000)
static gboolean
_routes_temporary_not_available_timeout (gpointer user_data)
{
RoutesTemporaryNotAvailableData *data;
NML3Cfg *self = NM_L3CFG (user_data);
GHashTableIter iter;
gint64 expiry_threshold_msec;
gboolean any_expired = FALSE;
gint64 now_msec;
gint64 oldest_msec;
self->priv.p->routes_temporary_not_available_id = 0;
if (!self->priv.p->routes_temporary_not_available_hash)
return G_SOURCE_REMOVE;
/* we check the timeouts again. That is, because we allow to remove
* entries from routes_temporary_not_available_hash, without rescheduling
* out timeouts. */
now_msec = nm_utils_get_monotonic_timestamp_msec ();
expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC;
oldest_msec = G_MAXINT64;
g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) {
if (data->timestamp_msec >= expiry_threshold_msec) {
any_expired = TRUE;
break;
}
if (data->timestamp_msec < oldest_msec)
oldest_msec = data->timestamp_msec;
}
if (any_expired) {
/* a route expired. We emit a signal, but we don't schedule it again. That will
* only happen if the user calls nm_l3cfg_platform_commit() again. */
_l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, NULL);
return G_SOURCE_REMOVE;
}
if (oldest_msec != G_MAXINT64) {
/* we have a timeout still. Reschedule. */
self->priv.p->routes_temporary_not_available_id = g_timeout_add (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
_routes_temporary_not_available_timeout,
self);
}
return G_SOURCE_REMOVE;
}
static gboolean
_routes_temporary_not_available_update (NML3Cfg *self,
int addr_family,
GPtrArray *routes_temporary_not_available_arr)
{
RoutesTemporaryNotAvailableData *data;
GHashTableIter iter;
gint64 oldest_msec;
gint64 now_msec;
gboolean prune_all = FALSE;
gboolean success = TRUE;
guint i;
now_msec = nm_utils_get_monotonic_timestamp_msec ();
if (nm_g_ptr_array_len (routes_temporary_not_available_arr) <= 0) {
prune_all = TRUE;
goto out_prune;
}
if (self->priv.p->routes_temporary_not_available_hash) {
g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) {
if (NMP_OBJECT_GET_ADDR_FAMILY (data->obj) == addr_family)
data->dirty = TRUE;
}
} else {
self->priv.p->routes_temporary_not_available_hash = g_hash_table_new_full (nmp_object_indirect_id_hash,
nmp_object_indirect_id_equal,
_routes_temporary_not_available_data_free,
NULL);
}
for (i = 0; i < routes_temporary_not_available_arr->len; i++) {
const NMPObject *o = routes_temporary_not_available_arr->pdata[i];
char sbuf[1024];
nm_assert (NMP_OBJECT_GET_TYPE (o) == NMP_OBJECT_TYPE_IP_ROUTE (NM_IS_IPv4 (addr_family)));
data = g_hash_table_lookup (self->priv.p->routes_temporary_not_available_hash, &o);
if (data) {
if (!data->dirty)
continue;
nm_assert ( data->timestamp_msec > 0
&& data->timestamp_msec <= now_msec);
if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) {
/* timeout. Could not add this address. */
_LOGW ("failure to add IPv%c route: %s",
nm_utils_addr_family_to_char (addr_family),
nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
success = FALSE;
continue;
}
data->dirty = FALSE;
continue;
}
_LOGT ("(temporarily) unable to add IPv%c route: %s",
nm_utils_addr_family_to_char (addr_family),
nmp_object_to_string (o, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof (sbuf)));
data = g_slice_new (RoutesTemporaryNotAvailableData);
*data = (RoutesTemporaryNotAvailableData) {
.obj = nmp_object_ref (o),
.timestamp_msec = now_msec,
.dirty = FALSE,
};
g_hash_table_add (self->priv.p->routes_temporary_not_available_hash, data);
}
out_prune:
oldest_msec = G_MAXINT64;
if (self->priv.p->routes_temporary_not_available_hash) {
g_hash_table_iter_init (&iter, self->priv.p->routes_temporary_not_available_hash);
while (g_hash_table_iter_next (&iter, (gpointer *) &data, NULL)) {
nm_assert ( NMP_OBJECT_GET_ADDR_FAMILY (data->obj) == addr_family
|| !data->dirty);
if ( !prune_all
&& !data->dirty) {
if (data->timestamp_msec < oldest_msec)
oldest_msec = data->timestamp_msec;
continue;
}
g_hash_table_iter_remove (&iter);
}
if (oldest_msec != G_MAXINT64)
nm_clear_pointer (&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
}
nm_clear_g_source (&self->priv.p->routes_temporary_not_available_id);
if (oldest_msec != G_MAXINT64) {
nm_assert (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC < now_msec);
self->priv.p->routes_temporary_not_available_id = g_timeout_add (oldest_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec,
_routes_temporary_not_available_timeout,
self);
}
return success;
}
/*****************************************************************************/
static gboolean
_platform_commit (NML3Cfg *self,
int addr_family,
NML3CfgCommitType commit_type,
gboolean *out_final_failure_for_temporary_not_available)
{
const gboolean IS_IPv4 = NM_IS_IPv4 (addr_family);
nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
gs_unref_ptrarray GPtrArray *addresses = NULL;
gs_unref_ptrarray GPtrArray *routes = NULL;
gs_unref_ptrarray GPtrArray *addresses_prune = NULL;
gs_unref_ptrarray GPtrArray *routes_prune = NULL;
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;
nm_assert (NM_IS_L3CFG (self));
nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_NONE,
NM_L3_CFG_COMMIT_TYPE_REAPPLY,
NM_L3_CFG_COMMIT_TYPE_UPDATE,
NM_L3_CFG_COMMIT_TYPE_ASSUME));
nm_assert_addr_family (addr_family);
_LOGT ("committing IPv%c configuration (%s)",
nm_utils_addr_family_to_char (addr_family),
_l3_cfg_commit_type_to_string (commit_type, sbuf_commit_type, sizeof (sbuf_commit_type)));
_l3cfg_update_combined_config (self, &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. */
_l3cfg_externally_removed_objs_drop_unused (self);
}
if (commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME) {
/* we need to artificially pre-populate the externally remove hash. */
_l3cfg_externally_removed_objs_pickup (self, addr_family);
}
if (self->priv.p->combined_l3cd) {
NMDedupMultiFcnSelectPredicate predicate;
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,
NMP_OBJECT_TYPE_IP_ADDRESS (IS_IPv4)),
predicate,
self->priv.p->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,
NMP_OBJECT_TYPE_IP_ROUTE (IS_IPv4)),
predicate,
self->priv.p->externally_removed_objs_hash);
route_table_sync = nm_l3_config_data_get_route_table_sync (self->priv.p->combined_l3cd, addr_family);
}
if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE)
route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL;
if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) {
addresses_prune = nm_platform_ip_address_get_prune_list (self->priv.platform,
addr_family,
self->priv.ifindex,
TRUE);
routes_prune = nm_platform_ip_route_get_prune_list (self->priv.platform,
addr_family,
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);
}
}
nm_platform_ip_address_sync (self->priv.platform,
addr_family,
self->priv.ifindex,
addresses,
addresses_prune);
if (!nm_platform_ip_route_sync (self->priv.platform,
addr_family,
self->priv.ifindex,
routes,
routes_prune,
&routes_temporary_not_available_arr))
success = FALSE;
final_failure_for_temporary_not_available = FALSE;
if (!_routes_temporary_not_available_update (self,
addr_family,
routes_temporary_not_available_arr))
final_failure_for_temporary_not_available = TRUE;
if (final_failure_for_temporary_not_available)
NM_SET_OUT (out_final_failure_for_temporary_not_available, TRUE);
return success;
}
gboolean
nm_l3cfg_platform_commit (NML3Cfg *self,
NML3CfgCommitType commit_type,
int addr_family,
gboolean *out_final_failure_for_temporary_not_available)
{
gboolean success = TRUE;
gboolean acd_was_pending;
g_return_val_if_fail (NM_IS_L3CFG (self), FALSE);
nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_AUTO,
NM_L3_CFG_COMMIT_TYPE_NONE,
NM_L3_CFG_COMMIT_TYPE_REAPPLY,
NM_L3_CFG_COMMIT_TYPE_UPDATE,
NM_L3_CFG_COMMIT_TYPE_ASSUME));
NM_SET_OUT (out_final_failure_for_temporary_not_available, FALSE);
acd_was_pending = self->priv.p->acd_is_pending;
if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET))
nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source);
if (commit_type == NM_L3_CFG_COMMIT_TYPE_AUTO)
commit_type = nm_l3cfg_commit_type_get (self);
if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY)
_l3cfg_externally_removed_objs_drop (self, addr_family);
if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET)) {
if (!_platform_commit (self, AF_INET, commit_type, out_final_failure_for_temporary_not_available))
success = FALSE;
}
if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET6)) {
if (!_platform_commit (self, AF_INET6, commit_type, out_final_failure_for_temporary_not_available))
success = FALSE;
}
if (NM_IN_SET (addr_family, AF_UNSPEC, AF_INET))
_l3_acd_data_process_changes (self);
if ( acd_was_pending
&& !self->priv.p->acd_is_pending)
_l3cfg_emit_signal_notify (self, NM_L3_CONFIG_NOTIFY_TYPE_ACD_COMPLETED, NULL);
return success;
}
/*****************************************************************************/
NML3CfgCommitType
nm_l3cfg_commit_type_get (NML3Cfg *self)
{
NML3CfgCommitTypeHandle *handle;
nm_assert (NM_IS_L3CFG (self));
handle = c_list_first_entry (&self->priv.p->commit_type_lst_head, NML3CfgCommitTypeHandle, commit_type_lst);
return handle
? handle->commit_type
: NM_L3_CFG_COMMIT_TYPE_NONE;
}
/**
* nm_l3cfg_commit_type_register:
* @self: the #NML3Cfg
* @commit_type: the commit type to register
* @existing_handle: instead of being a new registration, update an existing handle.
* This may be %NULL, which is like having no previous registration.
*
* NML3Cfg needs to know whether it is in charge of an interface (and how "much").
* By default, it is not in charge, but various users can register themself with
* a certain @commit_type. The "higher" commit type is the used one when calling
* nm_l3cfg_platform_commit() with %NM_L3_CFG_COMMIT_TYPE_AUTO.
*
* Returns: a handle tracking the registration, or %NULL of @commit_type
* is %NM_L3_CFG_COMMIT_TYPE_NONE.
*/
NML3CfgCommitTypeHandle *
nm_l3cfg_commit_type_register (NML3Cfg *self,
NML3CfgCommitType commit_type,
NML3CfgCommitTypeHandle *existing_handle)
{
NML3CfgCommitTypeHandle *handle;
NML3CfgCommitTypeHandle *h;
gboolean linked;
nm_assert (NM_IS_L3CFG (self));
nm_assert (NM_IN_SET (commit_type, NM_L3_CFG_COMMIT_TYPE_NONE,
NM_L3_CFG_COMMIT_TYPE_ASSUME,
NM_L3_CFG_COMMIT_TYPE_UPDATE,
NM_L3_CFG_COMMIT_TYPE_REAPPLY));
nm_assert ( !existing_handle
|| c_list_contains (&self->priv.p->commit_type_lst_head, &existing_handle->commit_type_lst));
if (existing_handle) {
if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) {
nm_l3cfg_commit_type_unregister (self, existing_handle);
return NULL;
}
if (existing_handle->commit_type == commit_type)
return existing_handle;
c_list_unlink_stale (&existing_handle->commit_type_lst);
handle = existing_handle;
} else {
if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
return NULL;
handle = g_slice_new (NML3CfgCommitTypeHandle);
handle->commit_type = commit_type;
if (c_list_is_empty (&self->priv.p->commit_type_lst_head))
g_object_ref (self);
}
linked = FALSE;
c_list_for_each_entry (h, &self->priv.p->commit_type_lst_head, commit_type_lst) {
if (handle->commit_type >= h->commit_type) {
c_list_link_before (&self->priv.p->commit_type_lst_head, &handle->commit_type_lst);
linked = TRUE;
}
}
if (!linked)
c_list_link_tail (&self->priv.p->commit_type_lst_head, &handle->commit_type_lst);
return handle;
}
void
nm_l3cfg_commit_type_unregister (NML3Cfg *self,
NML3CfgCommitTypeHandle *handle)
{
nm_assert (NM_IS_L3CFG (self));
if (!handle)
return;
nm_assert (c_list_contains (&self->priv.p->commit_type_lst_head, &handle->commit_type_lst));
c_list_unlink_stale (&handle->commit_type_lst);
if (c_list_is_empty (&self->priv.p->commit_type_lst_head))
g_object_unref (self);
nm_g_slice_free (handle);
}
/*****************************************************************************/
const NML3ConfigData *
nm_l3cfg_get_combined_l3cd (NML3Cfg *self)
{
nm_assert (NM_IS_L3CFG (self));
return self->priv.p->combined_l3cd;
}
const NMPObject *
nm_l3cfg_get_best_default_route (NML3Cfg *self,
int addr_family)
{
nm_assert (NM_IS_L3CFG (self));
/* we only consider the combined_l3cd. This is a merge of all the l3cd, and the one
* with which we called nm_l3cfg_platform_commit() the last time.
*
* In the meantime, we might have changed the tracked l3_config_datas, but we didn't
* nm_l3cfg_platform_commit() yet. These changes are ignored for this purpose, until
* the user call nm_l3cfg_platform_commit() to re-commit the changes. */
if (!self->priv.p->combined_l3cd)
return NULL;
return nm_l3_config_data_get_best_default_route (self->priv.p->combined_l3cd, addr_family);
}
/*****************************************************************************/
static void
set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
NML3Cfg *self = NM_L3CFG (object);
switch (prop_id) {
case PROP_NETNS:
/* construct-only */
self->priv.netns = g_object_ref (g_value_get_pointer (value));
nm_assert (NM_IS_NETNS (self->priv.netns));
break;
case PROP_IFINDEX:
/* construct-only */
self->priv.ifindex = g_value_get_int (value);
nm_assert (self->priv.ifindex > 0);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
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->commit_type_lst_head);
}
static void
constructed (GObject *object)
{
NML3Cfg *self = NM_L3CFG (object);
nm_assert (NM_IS_NETNS (self->priv.netns));
nm_assert (self->priv.ifindex > 0);
self->priv.platform = g_object_ref (nm_netns_get_platform (self->priv.netns));
nm_assert (NM_IS_PLATFORM (self->priv.platform));
_LOGT ("created (netns="NM_HASH_OBFUSCATE_PTR_FMT")",
NM_HASH_OBFUSCATE_PTR (self->priv.netns));
G_OBJECT_CLASS (nm_l3cfg_parent_class)->constructed (object);
_load_link (self, TRUE);
}
NML3Cfg *
nm_l3cfg_new (NMNetns *netns, int ifindex)
{
nm_assert (NM_IS_NETNS (netns));
nm_assert (ifindex > 0);
return g_object_new (NM_TYPE_L3CFG,
NM_L3CFG_NETNS, netns,
NM_L3CFG_IFINDEX, ifindex,
NULL);
}
static void
finalize (GObject *object)
{
NML3Cfg *self = NM_L3CFG (object);
nm_assert (c_list_is_empty (&self->priv.p->commit_type_lst_head));
nm_clear_g_source_inst (&self->priv.p->acd_ready_on_idle_source);
nm_assert (nm_g_array_len (self->priv.p->property_emit_list) == 0u);
_l3_acd_data_prune (self, TRUE);
nm_assert (c_list_is_empty (&self->priv.p->acd_lst_head));
nm_assert (nm_g_hash_table_size (self->priv.p->acd_lst_hash) == 0);
nm_clear_pointer (&self->priv.p->acd_lst_hash, g_hash_table_unref);
nm_clear_pointer (&self->priv.p->nacd, n_acd_unref);
nm_clear_g_source_inst (&self->priv.p->nacd_source);
nm_clear_g_source_inst (&self->priv.p->nacd_instance_ensure_retry);
nm_clear_g_source (&self->priv.p->routes_temporary_not_available_id);
nm_clear_pointer (&self->priv.p->routes_temporary_not_available_hash, g_hash_table_unref);
nm_clear_pointer (&self->priv.p->externally_removed_objs_hash, g_hash_table_unref);
g_clear_object (&self->priv.netns);
g_clear_object (&self->priv.platform);
nm_clear_l3cd (&self->priv.p->combined_l3cd);
nm_clear_pointer (&self->priv.pllink, nmp_object_unref);
nm_clear_pointer (&self->priv.p->acd_ipv4_addresses_on_link, g_hash_table_unref);
_LOGT ("finalized");
G_OBJECT_CLASS (nm_l3cfg_parent_class)->finalize (object);
}
static void
nm_l3cfg_class_init (NML3CfgClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
g_type_class_add_private (klass, sizeof (NML3CfgPrivate));
object_class->set_property = set_property;
object_class->constructed = constructed;
object_class->finalize = finalize;
obj_properties[PROP_NETNS] =
g_param_spec_pointer (NM_L3CFG_NETNS, "", "",
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IFINDEX] =
g_param_spec_int (NM_L3CFG_IFINDEX, "", "",
0,
G_MAXINT,
0,
G_PARAM_WRITABLE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
signals[SIGNAL_NOTIFY] =
g_signal_new (NM_L3CFG_SIGNAL_NOTIFY,
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_FIRST,
0, NULL, NULL, NULL,
G_TYPE_NONE,
2,
G_TYPE_INT /* NML3ConfigNotifyType */,
G_TYPE_POINTER /* (const NML3ConfigNotifyPayload *) */ );
}