diff options
| author | Michael Biebl <biebl@debian.org> | 2022-01-13 22:30:39 +0100 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2022-01-13 22:30:39 +0100 |
| commit | 88c227d90a6b7b388c5c85d72802a0ca8f05ed5c (patch) | |
| tree | 71f32df6617802270e8a78574bd8e1637dc532f4 /src/core/nm-l3cfg.c | |
| parent | e74c568b07b50b97873fb4ee1d776dedefbd54d6 (diff) | |
New upstream version 1.34.0 upstream/1.34.0
Diffstat (limited to 'src/core/nm-l3cfg.c')
| -rw-r--r-- | src/core/nm-l3cfg.c | 1762 |
1 files changed, 1176 insertions, 586 deletions
diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 93e92673..aa56c13f 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -18,6 +18,17 @@ /*****************************************************************************/ +#define ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC ((gint64) 20000) + +/* When a ObjStateData becomes a "zombie", we aim to delete it from platform + * on the next commit (until it disappears from platform). But we might have + * a bug, so that we fail to delete the platform (for example, related to + * IPv6 multicast routes). We thus rate limit how often we try to do this, + * before giving up. */ +#define ZOMBIE_COUNT_START 5 + +/*****************************************************************************/ + G_STATIC_ASSERT(NM_ACD_TIMEOUT_RFC5227_MSEC == N_ACD_TIMEOUT_RFC5227); #define ACD_SUPPORTED_ETH_ALEN ETH_ALEN @@ -97,6 +108,58 @@ typedef struct { G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0); +typedef struct { + const NMPObject *obj; + + /* Whether obj is currently in the platform cache or not. + * Since "obj" is the NMPObject from the merged NML3ConfigData, + * the object in platform has the same ID (but may otherwise not + * be identical). If this is not NULL, then currently the object + * is configured in kernel. */ + const NMPObject *os_plobj; + + CList os_lst; + + /* If we have a timeout pending, we link the instance to + * self->priv.p->obj_state_temporary_not_available_lst_head. */ + CList os_temporary_not_available_lst; + + /* If a NMPObject is no longer to be configured (but was configured + * during a previous commit), then we need to remember it so that the + * next commit can delete the address/route in kernel. It becomes a zombie. */ + CList os_zombie_lst; + + /* We might want to configure "obj" in platform, but it's currently not possible. + * For example, certain IPv6 routes can only be added after the IPv6 address + * becomes non-tentative (*sigh*). In such a case, we need to remember that, and + * retry later. If this timestamp is set to a non-zero value, then it means + * we tried to configure the obj (at that timestamp) and failed, but we are + * waiting to retry. + * + * See also self->priv.p->obj_state_temporary_not_available_lst_head + * and self->priv.p->obj_state_temporary_not_available_timeout_source. */ + gint64 os_temporary_not_available_timestamp_msec; + + /* When the obj is a zombie (that means, it was previously configured by NML3Cfg, but + * now no longer), it needs to be deleted from platform. This ratelimits the time + * how often we try that. When the counter reaches zero, we forget about it. */ + guint8 os_zombie_count; + + /* whether we ever saw the object in platform. */ + bool os_was_in_platform : 1; + + /* Indicates whether NetworkManager actively tried to configure the object + * in platform once. */ + bool os_nm_configured : 1; + + /* This flag is only used temporarily to do a bulk update and + * clear all the ones that are no longer in used. */ + bool os_dirty : 1; + bool os_tna_dirty : 1; +} ObjStateData; + +G_STATIC_ASSERT(G_STRUCT_OFFSET(ObjStateData, obj) == 0); + struct _NML3CfgCommitTypeHandle { CList commit_type_lst; NML3CfgCommitType commit_type; @@ -104,6 +167,7 @@ struct _NML3CfgCommitTypeHandle { typedef struct { const NML3ConfigData *l3cd; + NML3CfgConfigFlags config_flags; NML3ConfigMergeFlags merge_flags; union { struct { @@ -126,6 +190,13 @@ typedef struct { }; guint32 default_route_penalty_x[2]; }; + union { + struct { + int default_dns_priority_6; + int default_dns_priority_4; + }; + int default_dns_priority_x[2]; + }; gconstpointer tag_confdata; guint64 pseudo_timestamp_confdata; int priority_confdata; @@ -146,7 +217,6 @@ enum { static guint signals[LAST_SIGNAL] = {0}; typedef struct _NML3CfgPrivate { - GArray *property_emit_list; GArray *l3_config_datas; NML3IPv4LL *ipv4ll; @@ -157,9 +227,11 @@ typedef struct _NML3CfgPrivate { CList commit_type_lst_head; - GHashTable *routes_temporary_not_available_hash; + GHashTable *obj_state_hash; - GHashTable *externally_removed_objs_hash; + CList obj_state_lst_head; + CList obj_state_zombie_lst_head; + CList obj_state_temporary_not_available_lst_head; GHashTable *acd_ipv4_addresses_on_link; @@ -181,41 +253,30 @@ typedef struct _NML3CfgPrivate { 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]; - }; + GSource *obj_state_temporary_not_available_timeout_source; - union { - struct { - guint externally_removed_objs_cnt_routes_6; - guint externally_removed_objs_cnt_routes_4; - }; - guint externally_removed_objs_cnt_routes_x[2]; - }; + NML3CfgCommitType commit_on_idle_type; - union { - struct { - GPtrArray *last_addresses_6; - GPtrArray *last_addresses_4; - }; - GPtrArray *last_addresses_x[2]; - }; + gint8 commit_reentrant_count; - union { - struct { - GPtrArray *last_routes_6; - GPtrArray *last_routes_4; - }; - GPtrArray *last_routes_x[2]; - }; + /* The value that was set before we touched the sysctl (this only is + * meaningful if "ip6_privacy_set" is true. At the end, we want to restore + * this value. */ + NMSettingIP6ConfigPrivacy ip6_privacy_initial : 4; - guint routes_temporary_not_available_id; + /* The value that we set the last time. This is cached so that we don't + * repeatedly try to commit the same value. */ + NMSettingIP6ConfigPrivacy ip6_privacy_set_before : 4; - gint8 commit_reentrant_count; + guint32 ndisc_retrans_timer_msec; + guint32 ndisc_reachable_time_msec; + int ndisc_hop_limit; + + /* Whether "self" set the ip6_privacy sysctl (and whether it needs to be reset). */ + bool ip6_privacy_set : 1; + bool ndisc_reachable_time_msec_set : 1; + bool ndisc_retrans_timer_msec_set : 1; + bool ndisc_hop_limit_set : 1; bool commit_type_update_sticky : 1; @@ -268,8 +329,6 @@ G_DEFINE_TYPE(NML3Cfg, nm_l3cfg, G_TYPE_OBJECT) static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle); -static void _property_emit_notify(NML3Cfg *self, NML3CfgPropertyEmitType emit_type); - static void _nm_l3cfg_emit_signal_notify_acd_event_all(NML3Cfg *self); static gboolean _acd_has_valid_link(const NMPObject *obj, @@ -305,6 +364,7 @@ static NM_UTILS_ENUM2STR_DEFINE( NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED, "l3cd-changed"), 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_PRE_COMMIT, "pre-commit"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, "post-commit"), NM_UTILS_ENUM2STR(NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED, "routes-temporary-not-available-expired"), @@ -314,7 +374,7 @@ 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_NONE, "none"), NM_UTILS_ENUM2STR(NM_L3_ACD_DEFEND_TYPE_ONCE, "once"), ); static NM_UTILS_LOOKUP_DEFINE(_l3_acd_defend_type_to_nacd, @@ -342,6 +402,17 @@ static NM_UTILS_LOOKUP_DEFINE(_l3_acd_addr_state_to_string, /*****************************************************************************/ +gboolean +nm_l3cfg_is_vrf(const NML3Cfg *self) +{ + const NMPlatformLink *pllink; + + pllink = nm_l3cfg_get_pllink(self, TRUE); + return pllink && pllink->type == NM_LINK_TYPE_VRF; +} + +/*****************************************************************************/ + static const char * _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, char * sbuf, @@ -358,31 +429,29 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, nm_assert(sbuf_size > 0); _l3_config_notify_type_to_string(notify_data->notify_type, s, l); - nm_utils_strbuf_seek_end(&s, &l); + nm_strbuf_seek_end(&s, &l); switch (notify_data->notify_type) { case NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED: - nm_utils_strbuf_append( - &s, - &l, - ", l3cd-old=%s", - NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_old, sbufobf)); - nm_utils_strbuf_append( - &s, - &l, - ", l3cd-new=%s", - NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_new, sbufobf)); - nm_utils_strbuf_append(&s, &l, ", commited=%d", notify_data->l3cd_changed.commited); + nm_strbuf_append(&s, + &l, + ", l3cd-old=%s", + NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_old, sbufobf)); + nm_strbuf_append(&s, + &l, + ", l3cd-new=%s", + NM_HASH_OBFUSCATE_PTR_STR(notify_data->l3cd_changed.l3cd_new, sbufobf)); + nm_strbuf_append(&s, &l, ", commited=%d", notify_data->l3cd_changed.commited); break; case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT: - nm_utils_strbuf_append(&s, - &l, - ", 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)); + nm_strbuf_append(&s, + &l, + ", 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( + nm_strbuf_append( &s, &l, ", obj-type=%s, change=%s, obj=", @@ -391,15 +460,15 @@ _l3_config_notify_data_to_string(const NML3ConfigNotifyData *notify_data, nmp_object_to_string(notify_data->platform_change.obj, NMP_OBJECT_TO_STRING_PUBLIC, s, l); break; case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE: - nm_utils_strbuf_append(&s, - &l, - ", obj-type-flags=0x%x", - notify_data->platform_change_on_idle.obj_type_flags); + nm_strbuf_append(&s, + &l, + ", obj-type-flags=0x%x", + notify_data->platform_change_on_idle.obj_type_flags); break; case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT: nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll)); addr4 = nm_l3_ipv4ll_get_addr(notify_data->ipv4ll_event.ipv4ll); - nm_utils_strbuf_append( + nm_strbuf_append( &s, &l, ", ipv4ll=" NM_HASH_OBFUSCATE_PTR_FMT "%s%s, state=%s", @@ -555,157 +624,447 @@ _nm_n_acd_data_probe_new(NML3Cfg *self, in_addr_t addr, guint32 timeout_msec, gp /*****************************************************************************/ -static guint * -_l3cfg_externally_removed_objs_counter(NML3Cfg *self, NMPObjectType obj_type) +#define nm_assert_obj_state(self, obj_state) \ + G_STMT_START \ + { \ + if (NM_MORE_ASSERTS > 0) { \ + const NML3Cfg * _self = (self); \ + const ObjStateData *_obj_state = (obj_state); \ + \ + nm_assert(_obj_state); \ + nm_assert(NM_IN_SET(NMP_OBJECT_GET_TYPE(_obj_state->obj), \ + NMP_OBJECT_TYPE_IP4_ADDRESS, \ + NMP_OBJECT_TYPE_IP6_ADDRESS, \ + NMP_OBJECT_TYPE_IP4_ROUTE, \ + NMP_OBJECT_TYPE_IP6_ROUTE)); \ + nm_assert(!_obj_state->os_plobj || _obj_state->os_was_in_platform); \ + nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ + == c_list_is_empty(&_obj_state->os_temporary_not_available_lst)); \ + if (_self) { \ + if (c_list_is_empty(&_obj_state->os_zombie_lst)) { \ + nm_assert(_self->priv.p->combined_l3cd_commited); \ + \ + if (NM_MORE_ASSERTS > 5) { \ + nm_assert(c_list_contains(&_self->priv.p->obj_state_lst_head, \ + &_obj_state->os_lst)); \ + nm_assert((_obj_state->os_temporary_not_available_timestamp_msec == 0) \ + || c_list_contains( \ + &_self->priv.p->obj_state_temporary_not_available_lst_head, \ + &_obj_state->os_temporary_not_available_lst)); \ + nm_assert(_obj_state->os_plobj \ + == nm_platform_lookup_obj(_self->priv.platform, \ + NMP_CACHE_ID_TYPE_OBJECT_TYPE, \ + _obj_state->obj)); \ + nm_assert( \ + c_list_is_empty(&obj_state->os_zombie_lst) \ + ? (_obj_state->obj \ + == nm_dedup_multi_entry_get_obj(nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))) \ + : (!nm_l3_config_data_lookup_obj( \ + _self->priv.p->combined_l3cd_commited, \ + _obj_state->obj))); \ + } \ + } \ + } \ + } \ + } \ + G_STMT_END + +static gboolean +_obj_state_data_get_assume_config_once(const ObjStateData *obj_state) { - 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); - } + nm_assert_obj_state(NULL, obj_state); + + return nmp_object_get_assume_config_once(obj_state->obj); +} + +static ObjStateData * +_obj_state_data_new(const NMPObject *obj, const NMPObject *plobj) +{ + ObjStateData *obj_state; + + obj_state = g_slice_new(ObjStateData); + *obj_state = (ObjStateData){ + .obj = nmp_object_ref(obj), + .os_plobj = nmp_object_ref(plobj), + .os_was_in_platform = !!plobj, + .os_nm_configured = FALSE, + .os_dirty = FALSE, + .os_temporary_not_available_lst = C_LIST_INIT(obj_state->os_temporary_not_available_lst), + .os_zombie_lst = C_LIST_INIT(obj_state->os_zombie_lst), + }; + return obj_state; } static void -_l3cfg_externally_removed_objs_drop(NML3Cfg *self) +_obj_state_data_free(gpointer data) { - nm_assert(NM_IS_L3CFG(self)); + ObjStateData *obj_state = data; + + c_list_unlink_stale(&obj_state->os_lst); + c_list_unlink_stale(&obj_state->os_zombie_lst); + c_list_unlink_stale(&obj_state->os_temporary_not_available_lst); + nmp_object_unref(obj_state->obj); + nmp_object_unref(obj_state->os_plobj); + nm_g_slice_free(obj_state); +} + +static const char * +_obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_size) +{ + const char *buf0 = buf; + gint64 now_msec = 0; + + nm_assert(buf); + nm_assert(buf_size > 0); + nm_assert_obj_state(NULL, obj_state); + + nm_strbuf_append(&buf, + &buf_size, + "[" NM_HASH_OBFUSCATE_PTR_FMT ", %s, ", + NM_HASH_OBFUSCATE_PTR(obj_state), + NMP_OBJECT_GET_CLASS(obj_state->obj)->obj_type_name); + + nmp_object_to_string(obj_state->obj, NMP_OBJECT_TO_STRING_PUBLIC, buf, buf_size); + nm_strbuf_seek_end(&buf, &buf_size); + nm_strbuf_append_c(&buf, &buf_size, ']'); + + if (!c_list_is_empty(&obj_state->os_zombie_lst)) + nm_strbuf_append(&buf, &buf_size, ", zombie[%u]", obj_state->os_zombie_count); + + if (obj_state->os_nm_configured) + nm_strbuf_append_str(&buf, &buf_size, ", nm-configured"); + + if (obj_state->os_plobj) { + nm_assert(obj_state->os_was_in_platform); + nm_strbuf_append_str(&buf, &buf_size, ", in-platform"); + } else if (obj_state->os_was_in_platform) + nm_strbuf_append_str(&buf, &buf_size, ", was-in-platform"); + + if (obj_state->os_temporary_not_available_timestamp_msec > 0) { + nm_utils_get_monotonic_timestamp_msec_cached(&now_msec); + nm_strbuf_append( + &buf, + &buf_size, + ", temporary-not-available-since=%" G_GINT64_FORMAT ".%03d", + (now_msec - obj_state->os_temporary_not_available_timestamp_msec) / 1000, + (int) ((now_msec - obj_state->os_temporary_not_available_timestamp_msec) % 1000)); + } + + return buf0; +} + +static gboolean +_obj_state_data_update(ObjStateData *obj_state, const NMPObject *obj) +{ + gboolean changed = FALSE; + + nm_assert_obj_state(NULL, obj_state); + nm_assert(obj); + nm_assert(nmp_object_id_equal(obj_state->obj, obj)); + + obj_state->os_dirty = FALSE; - 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 (nm_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); + if (obj_state->obj != obj) { + nm_auto_nmpobj const NMPObject *obj_old = NULL; + + if (!nmp_object_equal(obj_state->obj, obj)) + changed = TRUE; + obj_old = g_steal_pointer(&obj_state->obj); + obj_state->obj = nmp_object_ref(obj); + } + + if (!c_list_is_empty(&obj_state->os_zombie_lst)) { + c_list_unlink(&obj_state->os_zombie_lst); + changed = TRUE; + } + + return changed; } +/*****************************************************************************/ + static void -_l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self) +_obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboolean in_platform) { - GHashTableIter h_iter; - const NMPObject *obj; - char sbuf[sizeof(_nm_utils_to_string_buffer)]; + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + ObjStateData *obj_state; nm_assert(NM_IS_L3CFG(self)); + nm_assert_is_bool(in_platform); - if (!self->priv.p->externally_removed_objs_hash) + nm_assert( + in_platform + ? (obj + == nm_platform_lookup_obj(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)) + : (!nm_platform_lookup_obj(self->priv.platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj))); + + obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj); + if (!obj_state) return; - if (!self->priv.p->combined_l3cd_commited) { - _l3cfg_externally_removed_objs_drop(self); + if (!in_platform) + obj = NULL; + + if (obj_state->os_plobj == obj) + goto out; + + if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) { + /* this is a zombie. We can forget about it.*/ + nm_clear_nmp_object(&obj_state->os_plobj); + c_list_unlink(&obj_state->os_zombie_lst); + _LOGD("obj-state: zombie gone (untrack): %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); 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_obj(self->priv.p->combined_l3cd_commited, 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. */ - _LOGD("externally-removed: untrack %s", - nmp_object_to_string(obj, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); - (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj))))--; - g_hash_table_iter_remove(&h_iter); - } + nm_assert(c_list_is_empty(&obj_state->os_zombie_lst)); + + if (in_platform) { + nmp_object_ref_set(&obj_state->os_plobj, obj); + obj_state->os_was_in_platform = TRUE; + _LOGD("obj-state: appeared in platform: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + goto out; } + + nm_clear_nmp_object(&obj_state->os_plobj); + _LOGD("obj-state: remove from platform: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + +out: + nm_assert_obj_state(self, obj_state); } static void -_l3cfg_externally_removed_objs_track(NML3Cfg *self, const NMPObject *obj, gboolean is_removed) +_obj_states_update_all(NML3Cfg *self) { - char sbuf[1000]; + static const NMPObjectType obj_types[] = { + NMP_OBJECT_TYPE_IP4_ADDRESS, + NMP_OBJECT_TYPE_IP6_ADDRESS, + NMP_OBJECT_TYPE_IP4_ROUTE, + NMP_OBJECT_TYPE_IP6_ROUTE, + }; + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + ObjStateData *obj_state; + int i; + gboolean any_dirty = FALSE; nm_assert(NM_IS_L3CFG(self)); - if (!self->priv.p->combined_l3cd_commited) - return; + c_list_for_each_entry (obj_state, &self->priv.p->obj_state_lst_head, os_lst) { + if (!c_list_is_empty(&obj_state->os_zombie_lst)) { + /* we can ignore zombies. */ + continue; + } + any_dirty = TRUE; + obj_state->os_dirty = TRUE; + } + + for (i = 0; i < (int) G_N_ELEMENTS(obj_types); i++) { + const NMPObjectType obj_type = obj_types[i]; + NMDedupMultiIter o_iter; + const NMPObject * obj; + + if (!self->priv.p->combined_l3cd_commited) + continue; + + nm_l3_config_data_iter_obj_for_each (&o_iter, + self->priv.p->combined_l3cd_commited, + &obj, + obj_type) { + obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj); + if (!obj_state) { + obj_state = + _obj_state_data_new(obj, + nm_platform_lookup_obj(self->priv.platform, + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + obj)); + c_list_link_tail(&self->priv.p->obj_state_lst_head, &obj_state->os_lst); + g_hash_table_add(self->priv.p->obj_state_hash, obj_state); + _LOGD("obj-state: track: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + nm_assert_obj_state(self, obj_state); + continue; + } - if (!is_removed) { - /* the object is still (or again) present. It no longer gets hidden. */ - if (self->priv.p->externally_removed_objs_hash) { - const NMPObject *obj2; - gpointer x_val; - - if (g_hash_table_steal_extended(self->priv.p->externally_removed_objs_hash, - obj, - (gpointer *) &obj2, - &x_val)) { - (*(_l3cfg_externally_removed_objs_counter(self, NMP_OBJECT_GET_TYPE(obj2))))--; - _LOGD("externally-removed: untrack %s", - nmp_object_to_string(obj2, NMP_OBJECT_TO_STRING_PUBLIC, sbuf, sizeof(sbuf))); - nmp_object_unref(obj2); + if (_obj_state_data_update(obj_state, obj)) { + _LOGD("obj-state: update: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); } + + nm_assert_obj_state(self, obj_state); } - return; } - if (!nm_l3_config_data_lookup_obj(self->priv.p->combined_l3cd_commited, obj)) { - /* we don't care about this object, so there is nothing to hide hide */ - return; + if (any_dirty) { + GHashTableIter h_iter; + + g_hash_table_iter_init(&h_iter, self->priv.p->obj_state_hash); + while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_state, NULL)) { + if (!c_list_is_empty(&obj_state->os_zombie_lst)) + continue; + if (!obj_state->os_dirty) + continue; + + if (obj_state->os_plobj && obj_state->os_nm_configured) { + c_list_link_tail(&self->priv.p->obj_state_zombie_lst_head, + &obj_state->os_zombie_lst); + obj_state->os_zombie_count = ZOMBIE_COUNT_START; + _LOGD("obj-state: now zombie: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + continue; + } + + _LOGD("obj-state: untrack: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + g_hash_table_iter_remove(&h_iter); + } } +} - 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); +typedef struct { + NML3Cfg * self; + NML3CfgCommitType commit_type; +} ObjStatesSyncFilterData; + +static gboolean +_obj_states_sync_filter(/* const NMDedupMultiObj * */ gconstpointer o, gpointer user_data) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + const NMPObject * obj = o; + const ObjStatesSyncFilterData *sync_filter_data = user_data; + NMPObjectType obj_type; + ObjStateData * obj_state; + + nm_assert(sync_filter_data); + nm_assert(NM_IS_L3CFG(sync_filter_data->self)); + + obj_type = NMP_OBJECT_GET_TYPE(obj); + + if (obj_type == NMP_OBJECT_TYPE_IP4_ADDRESS + && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->a_acd_not_ready) + return FALSE; + + obj_state = g_hash_table_lookup(sync_filter_data->self->priv.p->obj_state_hash, &obj); + + nm_assert_obj_state(sync_filter_data->self, obj_state); + nm_assert(obj_state->obj == obj); + nm_assert(c_list_is_empty(&obj_state->os_zombie_lst)); + + if (!obj_state->os_nm_configured) { + NML3Cfg *self; + + if (sync_filter_data->commit_type == NM_L3_CFG_COMMIT_TYPE_ASSUME + && !_obj_state_data_get_assume_config_once(obj_state)) + return FALSE; + + obj_state->os_nm_configured = TRUE; + + self = sync_filter_data->self; + _LOGD("obj-state: configure-first-time: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + return TRUE; } - 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))); + if (obj_state->os_temporary_not_available_timestamp_msec > 0) { + /* we currently try to configure this address (but failed earlier). + * Definitely retry. */ + return TRUE; } + + if (!obj_state->os_plobj && sync_filter_data->commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY) + return FALSE; + + return TRUE; } static void -_l3cfg_externally_removed_objs_pickup(NML3Cfg *self, int addr_family) +_obj_state_zombie_lst_get_prune_lists(NML3Cfg * self, + int addr_family, + GPtrArray **out_addresses_prune, + GPtrArray **out_routes_prune) { - const int IS_IPv4 = NM_IS_IPv4(addr_family); - NMDedupMultiIter iter; - const NMPObject *obj; + const int IS_IPv4 = NM_IS_IPv4(addr_family); + const NMPObjectType obj_type_route = NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4); + const NMPObjectType obj_type_address = NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4); + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + ObjStateData * obj_state; + ObjStateData * obj_state_safe; - if (!self->priv.p->combined_l3cd_commited) - return; + nm_assert(NM_IS_L3CFG(self)); + nm_assert(out_addresses_prune && !*out_addresses_prune); + nm_assert(out_routes_prune && !*out_routes_prune); - nm_l3_config_data_iter_obj_for_each (&iter, - self->priv.p->combined_l3cd_commited, - &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_commited, - &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); + c_list_for_each_entry_safe (obj_state, + obj_state_safe, + &self->priv.p->obj_state_zombie_lst_head, + os_zombie_lst) { + NMPObjectType obj_type; + GPtrArray ** p_a; + + nm_assert_obj_state(self, obj_state); + nm_assert(obj_state->os_zombie_count > 0); + + obj_type = NMP_OBJECT_GET_TYPE(obj_state->obj); + + if (obj_type == obj_type_route) + p_a = out_routes_prune; + else if (obj_type == obj_type_address) + p_a = out_addresses_prune; + else + continue; + + if (!*p_a) + *p_a = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref); + + g_ptr_array_add(*p_a, (gpointer) nmp_object_ref(obj_state->obj)); + + if (--obj_state->os_zombie_count == 0) { + _LOGD("obj-state: prune zombie (untrack): %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); + continue; + } + _LOGD("obj-state: prune zombie: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); } } -static gboolean -_l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointer o, - gpointer user_data) +static void +_obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family) { - const NMPObject *obj = o; - GHashTable * externally_removed_objs_hash = user_data; + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + ObjStateData *obj_state; + ObjStateData *obj_state_safe; - if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS - && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) - return FALSE; + /* we call this during reapply. Then we delete all the routes/addresses + * that are configured, and not only the zombies. + * + * Still, we need to adjust the os_zombie_count and assume that we + * are going to drop them. */ + + c_list_for_each_entry_safe (obj_state, + obj_state_safe, + &self->priv.p->obj_state_zombie_lst_head, + os_zombie_lst) { + nm_assert_obj_state(self, obj_state); + nm_assert(obj_state->os_zombie_count > 0); + + if (NMP_OBJECT_GET_ADDR_FAMILY(obj_state->obj) != addr_family) + continue; - return !nm_g_hash_table_contains(externally_removed_objs_hash, obj); + if (--obj_state->os_zombie_count == 0) { + _LOGD("obj-state: zombie pruned during reapply (untrack): %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + g_hash_table_remove(self->priv.p->obj_state_hash, obj_state); + continue; + } + _LOGD("obj-state: zombie pruned during reapply: %s", + _obj_state_data_to_string(obj_state, sbuf, sizeof(sbuf))); + } } /*****************************************************************************/ @@ -810,11 +1169,6 @@ _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint32 obj_type_flags) _nm_l3cfg_emit_signal_notify(self, ¬ify_data); _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); - 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 @@ -847,7 +1201,7 @@ _nm_l3cfg_notify_platform_change(NML3Cfg * self, 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); + _obj_states_externally_removed_track(self, obj, change_type != NM_PLATFORM_SIGNAL_REMOVED); default: break; } @@ -864,146 +1218,6 @@ _nm_l3cfg_notify_platform_change(NML3Cfg * self, /*****************************************************************************/ -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) { @@ -1012,12 +1226,6 @@ nm_l3cfg_get_acd_is_pending(NML3Cfg *self) return self->priv.p->acd_is_pending; } -static gboolean -_acd_track_data_is_not_dirty(const NML3AcdAddrTrackInfo *acd_track) -{ - return acd_track && !acd_track->_priv.acd_dirty_track; -} - static void _acd_track_data_clear(NML3AcdAddrTrackInfo *acd_track) { @@ -1044,7 +1252,7 @@ _acd_data_collect_tracks_data(const AcdData * acd_data, guint32 * out_best_acd_timeout_msec, NML3AcdDefendType *out_best_acd_defend_type) { - NML3AcdDefendType best_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_NONE; + NML3AcdDefendType best_acd_defend_type = _NM_L3_ACD_DEFEND_TYPE_NONE; guint32 best_acd_timeout_msec = G_MAXUINT32; guint n = 0; guint i; @@ -1063,7 +1271,7 @@ _acd_data_collect_tracks_data(const AcdData * acd_data, best_acd_defend_type = acd_track->_priv.acd_defend_type_track; } - nm_assert(n == 0 || best_acd_defend_type > NM_L3_ACD_DEFEND_TYPE_NONE); + 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); @@ -1136,9 +1344,9 @@ _l3_acd_nacd_event_down_timeout_cb(gpointer user_data) static gboolean _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) { - NML3Cfg *self = user_data; - gboolean success = FALSE; - int r; + gs_unref_object NML3Cfg *self = g_object_ref(user_data); + gboolean success = FALSE; + int r; nm_assert(NM_IS_L3CFG(self)); nm_assert(self->priv.p->nacd); @@ -1155,6 +1363,13 @@ _l3_acd_nacd_event(int fd, GIOCondition condition, gpointer user_data) AcdData * acd_data; NAcdEvent * event; + if (!self->priv.p->nacd) { + /* In the loop we emit signals, where *anything* might happen. + * Check that we still have the nacd instance. */ + success = TRUE; + goto out; + } + r = n_acd_pop_event(self->priv.p->nacd, &event); if (r) { _LOGT("acd: pop-event failed with error %d", r); @@ -1286,7 +1501,7 @@ _l3_acd_nacd_instance_reset(NML3Cfg *self, NMTernary start_timer, gboolean acd_d switch (start_timer) { case NM_TERNARY_FALSE: _l3_changed_configs_set_dirty(self); - nm_l3cfg_commit_on_idle_schedule(self); + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); break; case NM_TERNARY_TRUE: self->priv.p->nacd_instance_ensure_retry = @@ -1527,8 +1742,8 @@ _l3_acd_data_add(NML3Cfg * self, .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_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); @@ -1794,7 +2009,7 @@ _nm_printf(5, 6) static void _l3_acd_data_state_set_full(NML3Cfg * self, /* 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); + nm_l3cfg_commit_on_idle_schedule(self, NM_L3_CFG_COMMIT_TYPE_AUTO); } } @@ -2464,7 +2679,7 @@ handle_start_defending: 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_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) { @@ -2570,33 +2785,163 @@ nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr) /*****************************************************************************/ +gboolean +nm_l3cfg_check_ready(NML3Cfg * self, + const NML3ConfigData * l3cd, + int addr_family, + NML3CfgCheckReadyFlags flags, + gboolean * acd_used) +{ + NMDedupMultiIter iter; + const NMPObject *obj; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert_addr_family_or_unspec(addr_family); + + NM_SET_OUT(acd_used, FALSE); + + if (!l3cd) + return TRUE; + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET) + && NM_FLAGS_HAS(flags, NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY)) { + gboolean pending = FALSE; + + nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP4_ADDRESS) { + const NML3AcdAddrInfo *addr_info; + + addr_info = nm_l3cfg_get_acd_addr_info(self, NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address); + if (!addr_info) { + /* We don't track the this address? That's odd. Not ready. */ + pending = TRUE; + } else { + if (addr_info->state <= NM_L3_ACD_ADDR_STATE_PROBING) { + /* Still probing. Not ready. */ + pending = TRUE; + } else if (addr_info->state == NM_L3_ACD_ADDR_STATE_USED) { + NM_SET_OUT(acd_used, TRUE); + } + } + /* we only care that we don't have ACD still pending. Otherwise we are ready, + * including if we have no addr_info about this address or the address is in use. */ + } + if (pending) + return FALSE; + } + + if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6) + && NM_FLAGS_HAS(flags, NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY)) { + nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP6_ADDRESS) { + ObjStateData *obj_state; + + obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &obj); + + if (!obj_state) { + /* Hm, we don't track this object? That is odd. Not ready. */ + return FALSE; + } + + if (!obj_state->os_nm_configured && !obj_state->os_plobj) { + /* We didn't (yet) configure this address and it also is not in platform. + * Not ready. */ + return FALSE; + } + + if (obj_state->os_plobj + && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP6_ADDRESS(obj_state->os_plobj)->n_ifa_flags, + IFA_F_TENTATIVE)) { + /* The address is configured in kernel, but still tentative. Not ready. */ + return FALSE; + } + + /* This address is ready. Even if it is not (not anymore) configured in kernel (as + * indicated by obj_state->os_plobj). We apparently did configure it once, and + * it's no longer tentative. This address are good. */ + } + } + + return TRUE; +} + +/*****************************************************************************/ + static gboolean _l3_commit_on_idle_cb(gpointer user_data) { - NML3Cfg *self = user_data; + _nm_unused gs_unref_object NML3Cfg *self_keep_alive = NULL; + NML3Cfg * self = user_data; + NML3CfgCommitType commit_type; + + commit_type = self->priv.p->commit_on_idle_type; + + if (nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source)) + self_keep_alive = self; + else + nm_assert_not_reached(); - nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); + self->priv.p->commit_on_idle_type = NM_L3_CFG_COMMIT_TYPE_AUTO; - _LOGT("commit on idle"); - _l3_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, TRUE); + _l3_commit(self, commit_type, TRUE); return G_SOURCE_REMOVE; } +/* DOC(l3cfg:commit-type): + * + * Usually we don't want to call the synchronous nm_l3cfg_commit(), because + * that has side effects and might not be safe to do (depending on the current + * circumstances in which commit is called). The usually proper thing to do + * is schedule a commit on an idle handler. Use this function. + * + * During commit, the actually used commit-type (that is, the level of "how much" + * will be synced) is determined by users who register their desired commit + * type via nm_l3cfg_commit_type_register(), where always the "maxium" is used. + * + * nm_l3cfg_commit() and nm_l3cfg_commit_on_idle_schedule() also accept an additional + * commit_type argument. This acts like a one-shot registration. + */ gboolean -nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self) +nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self, NML3CfgCommitType commit_type) { + char sbuf_commit_type[50]; + nm_assert(NM_IS_L3CFG(self)); + nm_assert(NM_IN_SET(commit_type, + NM_L3_CFG_COMMIT_TYPE_AUTO, + NM_L3_CFG_COMMIT_TYPE_ASSUME, + NM_L3_CFG_COMMIT_TYPE_UPDATE, + NM_L3_CFG_COMMIT_TYPE_REAPPLY)); - if (self->priv.p->commit_on_idle_source) + if (self->priv.p->commit_on_idle_source) { + if (self->priv.p->commit_on_idle_type < commit_type) { + /* For multiple calls, we collect the maximum "commit-type". */ + _LOGT("commit on idle (scheduled) (update to %s)", + _l3_cfg_commit_type_to_string(commit_type, + sbuf_commit_type, + sizeof(sbuf_commit_type))); + self->priv.p->commit_on_idle_type = commit_type; + } return FALSE; + } + + _LOGT("commit on idle (scheduled) (%s)", + _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type))); + self->priv.p->commit_on_idle_source = nm_g_idle_add_source(_l3_commit_on_idle_cb, self); + self->priv.p->commit_on_idle_type = commit_type; + + /* While we have an idle update scheduled, we need to keep the instance alive. */ + g_object_ref(self); - _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); return TRUE; } +gboolean +nm_l3cfg_commit_on_idle_is_scheduled(NML3Cfg *self) +{ + nm_assert(NM_IS_L3CFG(self)); + + return !!(self->priv.p->commit_on_idle_source); +} + /*****************************************************************************/ #define _l3_config_datas_at(l3_config_datas, idx) \ @@ -2695,8 +3040,11 @@ nm_l3cfg_add_config(NML3Cfg * self, guint32 default_route_metric_6, guint32 default_route_penalty_4, guint32 default_route_penalty_6, + int default_dns_priority_4, + int default_dns_priority_6, NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, + NML3CfgConfigFlags config_flags, NML3ConfigMergeFlags merge_flags) { L3ConfigData *l3_config_data; @@ -2761,6 +3109,7 @@ nm_l3cfg_add_config(NML3Cfg * self, *l3_config_data = (L3ConfigData){ .tag_confdata = tag, .l3cd = nm_l3_config_data_ref_and_seal(l3cd), + .config_flags = config_flags, .merge_flags = merge_flags, .default_route_table_4 = default_route_table_4, .default_route_table_6 = default_route_table_6, @@ -2768,6 +3117,8 @@ nm_l3cfg_add_config(NML3Cfg * self, .default_route_metric_6 = default_route_metric_6, .default_route_penalty_4 = default_route_penalty_4, .default_route_penalty_6 = default_route_penalty_6, + .default_dns_priority_4 = default_dns_priority_4, + .default_dns_priority_6 = default_dns_priority_6, .acd_defend_type_confdata = acd_defend_type, .acd_timeout_msec_confdata = acd_timeout_msec, .priority_confdata = priority, @@ -2784,6 +3135,10 @@ nm_l3cfg_add_config(NML3Cfg * self, l3_config_data->priority_confdata = priority; changed = TRUE; } + if (l3_config_data->config_flags != config_flags) { + l3_config_data->config_flags = config_flags; + changed = TRUE; + } if (l3_config_data->merge_flags != merge_flags) { l3_config_data->merge_flags = merge_flags; changed = TRUE; @@ -2812,6 +3167,14 @@ nm_l3cfg_add_config(NML3Cfg * self, l3_config_data->default_route_penalty_6 = default_route_penalty_6; changed = TRUE; } + if (l3_config_data->default_dns_priority_4 != default_dns_priority_4) { + l3_config_data->default_dns_priority_4 = default_dns_priority_4; + changed = TRUE; + } + if (l3_config_data->default_dns_priority_6 != default_dns_priority_6) { + l3_config_data->default_dns_priority_6 = default_dns_priority_6; + 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; @@ -2879,17 +3242,23 @@ _l3cfg_remove_config(NML3Cfg * self, } gboolean -nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg) +nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *l3cd) { - nm_assert(ifcfg); + nm_assert(l3cd); + + return _l3cfg_remove_config(self, tag, FALSE, l3cd); +} - return _l3cfg_remove_config(self, tag, FALSE, ifcfg); +gboolean +nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag) +{ + return _l3cfg_remove_config(self, tag, FALSE, NULL); } gboolean -nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty) +nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag) { - return _l3cfg_remove_config(self, tag, only_dirty, NULL); + return _l3cfg_remove_config(self, tag, TRUE, NULL); } /*****************************************************************************/ @@ -2897,13 +3266,15 @@ nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag, gboolean only_dirty typedef struct { NML3Cfg * self; gconstpointer tag; + bool assume_config_once; + bool to_commit; } L3ConfigMergeHookAddObjData; static gboolean -_l3_hook_add_addr_cb(const NML3ConfigData *l3cd, - const NMPObject * obj, - NMTernary * out_ip4acd_not_ready, - gpointer user_data) +_l3_hook_add_obj_cb(const NML3ConfigData * l3cd, + const NMPObject * obj, + NML3ConfigMergeHookResult *hook_result, + gpointer user_data) { const L3ConfigMergeHookAddObjData *hook_data = user_data; NML3Cfg * self = hook_data->self; @@ -2911,40 +3282,68 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, in_addr_t addr; gboolean acd_bad = FALSE; - nm_assert(out_ip4acd_not_ready && *out_ip4acd_not_ready == NM_TERNARY_DEFAULT); + nm_assert(obj); + nm_assert(hook_result); + nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); + nm_assert(hook_result->assume_config_once == NM_OPTION_BOOL_DEFAULT); - if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS) - return TRUE; + hook_result->assume_config_once = hook_data->assume_config_once; - addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: - if (ACD_ADDR_SKIP(addr)) - goto out; + addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; - acd_data = _l3_acd_data_find(self, addr); + if (ACD_ADDR_SKIP(addr)) + goto out_ip4_address; - 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; - } + acd_data = _l3_acd_data_find(self, addr); - nm_assert( - _acd_track_data_is_not_dirty(_acd_data_find_track(acd_data, l3cd, obj, hook_data->tag))); - if (!NM_IN_SET(acd_data->info.state, - NM_L3_ACD_ADDR_STATE_READY, - NM_L3_ACD_ADDR_STATE_DEFENDING)) - acd_bad = TRUE; + if (!hook_data->to_commit) { + nm_assert(self->priv.p->changed_configs_acd_state); + /* We don't do an actual commit in _l3cfg_update_combined_config(). That means our acd-data + * is not up to date. Check whether we have no acd_data ready, and if not, consider the address + * as not ready. It cannot be ready until the next commit starts ACD. */ + if (!acd_data) { + acd_bad = TRUE; + goto out_ip4_address; + } + nm_assert(({ + NML3AcdAddrTrackInfo *_ti = + _acd_data_find_track(acd_data, l3cd, obj, hook_data->tag); -out: - *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; - return TRUE; + !_ti || _ti->_priv.acd_dirty_track; + })); + } else { + /* If we commit, we called _l3_acd_data_add_all(), thus our acd_data must be present + * and not dirty. */ + nm_assert(({ + NML3AcdAddrTrackInfo *_ti = + _acd_data_find_track(acd_data, l3cd, obj, hook_data->tag); + + _ti && !_ti->_priv.acd_dirty_track; + })); + } + + if (!NM_IN_SET(acd_data->info.state, + NM_L3_ACD_ADDR_STATE_READY, + NM_L3_ACD_ADDR_STATE_DEFENDING)) { + acd_bad = TRUE; + goto out_ip4_address; + } + +out_ip4_address: + hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE; + return TRUE; + + default: + nm_assert_not_reached(); + /* fall-through */ + case NMP_OBJECT_TYPE_IP6_ADDRESS: + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + return TRUE; + } } static void @@ -3007,29 +3406,54 @@ _l3cfg_update_combined_config(NML3Cfg * self, if (l3_config_datas_len > 0) { L3ConfigMergeHookAddObjData hook_data = { - .self = self, + .self = self, + .to_commit = to_commit, }; l3cd = nm_l3_config_data_new(nm_platform_get_multi_idx(self->priv.platform), - self->priv.ifindex); + self->priv.ifindex, + NM_IP_CONFIG_SOURCE_UNKNOWN); for (i = 0; i < l3_config_datas_len; 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)) + if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD)) continue; hook_data.tag = l3cd_data->tag_confdata; + hook_data.assume_config_once = + NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE); + nm_l3_config_data_merge(l3cd, l3cd_data->l3cd, l3cd_data->merge_flags, l3cd_data->default_route_table_x, l3cd_data->default_route_metric_x, l3cd_data->default_route_penalty_x, - _l3_hook_add_addr_cb, + l3cd_data->default_dns_priority_x, + _l3_hook_add_obj_cb, &hook_data); } + for (i = 0; i < l3_config_datas_len; i++) { + const L3ConfigData *l3cd_data = l3_config_datas_arr[i]; + int IS_IPv4; + + if (NM_FLAGS_HAS(l3cd_data->config_flags, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD)) + continue; + + for (IS_IPv4 = 1; IS_IPv4 >= 0; IS_IPv4--) { + nm_l3_config_data_add_dependent_device_routes( + l3cd, + IS_IPv4 ? AF_INET : AF_INET6, + l3cd_data->default_route_table_x[IS_IPv4], + l3cd_data->default_route_metric_x[IS_IPv4], + l3cd_data->l3cd); + } + } + + nm_l3_config_data_add_dependent_onlink_routes(l3cd, AF_UNSPEC); + nm_assert(l3cd); nm_assert(nm_l3_config_data_get_ifindex(l3cd) == self->priv.ifindex); @@ -3060,6 +3484,8 @@ out: nm_l3_config_data_ref(self->priv.p->combined_l3cd_merged); commited_changed = TRUE; + _obj_states_update_all(self); + _nm_l3cfg_emit_signal_notify_l3cd_changed(self, l3cd_commited_old, self->priv.p->combined_l3cd_commited, @@ -3095,75 +3521,45 @@ out: /*****************************************************************************/ -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; + NML3Cfg * self = NM_L3CFG(user_data); + ObjStateData *obj_state; + gint64 now_msec; + gint64 expiry_msec; - self->priv.p->routes_temporary_not_available_id = 0; + nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - if (!self->priv.p->routes_temporary_not_available_hash) - return G_SOURCE_REMOVE; + obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, + ObjStateData, + os_temporary_not_available_lst); - /* we check the timeouts again. That is, because we allow to remove - * entries from routes_temporary_not_available_hash, without rescheduling - * out timeouts. */ + if (!obj_state) + return G_SOURCE_CONTINUE; now_msec = nm_utils_get_monotonic_timestamp_msec(); - expiry_threshold_msec = now_msec - ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; - oldest_msec = G_MAXINT64; + expiry_msec = obj_state->os_temporary_not_available_timestamp_msec + + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC; - 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_commit() again. */ - _nm_l3cfg_emit_signal_notify_simple( - self, - NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED); - return G_SOURCE_REMOVE; + if (now_msec < expiry_msec) { + /* the timeout is not yet reached. Restart the timer... */ + self->priv.p->obj_state_temporary_not_available_timeout_source = + nm_g_timeout_add_source(expiry_msec - now_msec, + _routes_temporary_not_available_timeout, + self); + return G_SOURCE_CONTINUE; } - 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; + /* One (or several) routes expired. We emit a signal, but we don't schedule it again. + * We expect the callers to commit again, which will one last time try to configure + * the route. If that again fails, we detect the timeout, log a warning and don't + * track the object as not temporary-not-available anymore. */ + _nm_l3cfg_emit_signal_notify_simple( + self, + NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED); + return G_SOURCE_CONTINUE; } static gboolean @@ -3171,14 +3567,15 @@ _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; - + ObjStateData * obj_state; + ObjStateData * obj_state_safe; + gint64 now_msec; + gboolean prune_all = FALSE; + gboolean success = TRUE; + guint i; + const NMPClass *klass; + + klass = nmp_class_from_type(NMP_OBJECT_TYPE_IP_ROUTE(NM_IS_IPv4(addr_family))); now_msec = nm_utils_get_monotonic_timestamp_msec(); if (nm_g_ptr_array_len(routes_temporary_not_available_arr) <= 0) { @@ -3186,36 +3583,45 @@ _routes_temporary_not_available_update(NML3Cfg * self, 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; + c_list_for_each_entry (obj_state, + &self->priv.p->obj_state_temporary_not_available_lst_head, + os_temporary_not_available_lst) { + if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { + nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0); + obj_state->os_tna_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]; + char sbuf[sizeof(_nm_utils_to_string_buffer)]; 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); + obj_state = g_hash_table_lookup(self->priv.p->obj_state_hash, &o); - if (data) { - if (!data->dirty) - continue; + if (!obj_state) { + /* Hm? We don't track this object? Very odd, a bug? */ + nm_assert_not_reached(); + continue; + } - nm_assert(data->timestamp_msec > 0 && data->timestamp_msec <= now_msec); + if (obj_state->os_temporary_not_available_timestamp_msec > 0) { + nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0 + && obj_state->os_temporary_not_available_timestamp_msec <= now_msec); - if (now_msec > data->timestamp_msec + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { - /* timeout. Could not add this address. */ + if (!obj_state->os_tna_dirty) { + /* Odd, this only can happen if routes_temporary_not_available_arr contains duplicates. + * It should not. */ + nm_assert_not_reached(); + continue; + } + + if (now_msec > obj_state->os_temporary_not_available_timestamp_msec + + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC) { + /* Timeout. Could not add this address. + * + * For now, keep it obj_state->os_tna_dirty and prune it below. */ _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))); @@ -3223,7 +3629,7 @@ _routes_temporary_not_available_update(NML3Cfg * self, continue; } - data->dirty = FALSE; + obj_state->os_tna_dirty = FALSE; continue; } @@ -3231,47 +3637,229 @@ _routes_temporary_not_available_update(NML3Cfg * self, 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); + obj_state->os_tna_dirty = FALSE; + obj_state->os_temporary_not_available_timestamp_msec = now_msec; + c_list_link_tail(&self->priv.p->obj_state_temporary_not_available_lst_head, + &obj_state->os_temporary_not_available_lst); } 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; + c_list_for_each_entry_safe (obj_state, + obj_state_safe, + &self->priv.p->obj_state_temporary_not_available_lst_head, + os_temporary_not_available_lst) { + if (prune_all || obj_state->os_tna_dirty) { + if (NMP_OBJECT_GET_CLASS(obj_state->obj) == klass) { + obj_state->os_temporary_not_available_timestamp_msec = 0; + c_list_unlink(&obj_state->os_temporary_not_available_lst); } - 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); + nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); + + obj_state = c_list_first_entry(&self->priv.p->obj_state_temporary_not_available_lst_head, + ObjStateData, + os_temporary_not_available_lst); + if (obj_state) { + self->priv.p->obj_state_temporary_not_available_timeout_source = + nm_g_timeout_add_source((obj_state->os_temporary_not_available_timestamp_msec + + ROUTES_TEMPORARY_NOT_AVAILABLE_MAX_AGE_MSEC - now_msec), + _routes_temporary_not_available_timeout, + self); } return success; } /*****************************************************************************/ +static const char * +ip6_privacy_to_str(NMSettingIP6ConfigPrivacy ip6_privacy) +{ + switch (ip6_privacy) { + case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED: + return "0"; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR: + return "1"; + case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR: + return "2"; + case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN: + break; + } + return nm_assert_unreachable_val("0"); +} + +static void +_l3_commit_ndisc_params(NML3Cfg *self, NML3CfgCommitType commit_type) +{ + const NML3ConfigData *l3cd; + gboolean retrans_set = FALSE; + gboolean reachable_set = FALSE; + gboolean hop_limit_set = FALSE; + guint32 reachable = 0; + guint32 retrans = 0; + int hop_limit = 0; + const char * ifname; + + if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE) { + self->priv.p->ndisc_reachable_time_msec_set = FALSE; + self->priv.p->ndisc_retrans_timer_msec_set = FALSE; + self->priv.p->ndisc_hop_limit_set = FALSE; + return; + } + + l3cd = self->priv.p->combined_l3cd_commited; + if (l3cd) { + reachable_set = nm_l3_config_data_get_ndisc_reachable_time_msec(l3cd, &reachable); + retrans_set = nm_l3_config_data_get_ndisc_retrans_timer_msec(l3cd, &retrans); + hop_limit = nm_l3_config_data_get_ndisc_hop_limit(l3cd, &hop_limit); + } + ifname = nm_l3cfg_get_ifname(self, TRUE); + + if (reachable_set + && (!self->priv.p->ndisc_reachable_time_msec_set + || self->priv.p->ndisc_reachable_time_msec != reachable)) { + self->priv.p->ndisc_reachable_time_msec = reachable; + self->priv.p->ndisc_reachable_time_msec_set = TRUE; + if (ifname) { + nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(self->priv.platform, + ifname, + reachable); + } + } + + if (retrans_set + && (!self->priv.p->ndisc_retrans_timer_msec_set + || self->priv.p->ndisc_reachable_time_msec != retrans)) { + self->priv.p->ndisc_retrans_timer_msec = retrans; + self->priv.p->ndisc_retrans_timer_msec_set = TRUE; + if (ifname) { + nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(self->priv.platform, ifname, retrans); + } + } + + if (hop_limit_set + && (!self->priv.p->ndisc_hop_limit_set || self->priv.p->ndisc_hop_limit != hop_limit)) { + self->priv.p->ndisc_hop_limit = hop_limit; + self->priv.p->ndisc_hop_limit_set = TRUE; + if (ifname) { + nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(self->priv.platform, + ifname, + hop_limit); + } + } + + // FIXME: restore values if necessary +} + +static void +_l3_commit_ip6_privacy(NML3Cfg *self, NML3CfgCommitType commit_type) +{ + NMSettingIP6ConfigPrivacy ip6_privacy; + NMSettingIP6ConfigPrivacy ip6_privacy_set_before; + const char * ifname; + + if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE) + ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + else + ip6_privacy = nm_l3_config_data_get_ip6_privacy(self->priv.p->combined_l3cd_commited); + + if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) { + if (!self->priv.p->ip6_privacy_set) { + /* Nothing to set. But do we need to reset a previous value? */ + return; + } + self->priv.p->ip6_privacy_set = FALSE; + ip6_privacy = self->priv.p->ip6_privacy_initial; + ifname = nm_l3cfg_get_ifname(self, TRUE); + _LOGT("commit-ip6-privacy: reset initial value %d (was %d)%s%s", + (int) ip6_privacy, + (int) self->priv.p->ip6_privacy_set_before, + NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)")); + if (ip6_privacy == NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN) + return; + if (!ifname) + return; + goto set; + } + + nm_assert(NM_IN_SET(ip6_privacy, + NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR, + NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR)); + + if (self->priv.p->ip6_privacy_set && self->priv.p->ip6_privacy_set_before == ip6_privacy + && commit_type < NM_L3_CFG_COMMIT_TYPE_REAPPLY) { + /* Already set. We leave this alone except during reapply. */ + return; + } + + ip6_privacy_set_before = self->priv.p->ip6_privacy_set_before; + self->priv.p->ip6_privacy_set_before = ip6_privacy; + + if (!self->priv.p->ip6_privacy_set) { + gint64 s = G_MININT64; + + self->priv.p->ip6_privacy_set = TRUE; + ifname = nm_l3cfg_get_ifname(self, TRUE); + if (ifname) { + s = nm_platform_sysctl_ip_conf_get_int_checked(self->priv.platform, + AF_INET6, + ifname, + "use_tempaddr", + 10, + G_MININT32, + G_MAXINT32, + G_MININT64); + if (s != G_MININT64) + s = NM_CLAMP(s, 0, 2); + } + switch (s) { + case 0: + self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED; + break; + case 1: + self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR; + break; + case 2: + self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR; + break; + default: + nm_assert_not_reached(); + /* fall-through */ + case G_MININT64: + self->priv.p->ip6_privacy_initial = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN; + break; + } + _LOGT("commit-ip6-privacy: set value %d (initial value was %d)%s%s", + (int) ip6_privacy, + (int) self->priv.p->ip6_privacy_initial, + NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)")); + if (!ifname) + return; + /* The first time, we always set the value, and don't skip it based on what we + * read. */ + goto set; + } + + ifname = nm_l3cfg_get_ifname(self, TRUE); + _LOGT("commit-ip6-privacy: set value %d (after %d, initial value was %d)%s%s", + (int) ip6_privacy, + (int) ip6_privacy_set_before, + (int) self->priv.p->ip6_privacy_initial, + NM_PRINT_FMT_QUOTED2(ifname, ", ifname ", ifname, " (skip, no interface)")); + if (!ifname) + return; + +set: + nm_assert(ifname); + self->priv.p->ip6_privacy_set_before = ip6_privacy; + nm_platform_sysctl_ip_conf_set(self->priv.platform, + AF_INET6, + ifname, + "use_tempaddr", + ip6_privacy_to_str(ip6_privacy)); +} static gboolean _l3_commit_one(NML3Cfg * self, @@ -3303,58 +3891,35 @@ _l3_commit_one(NML3Cfg * self, nm_utils_addr_family_to_char(addr_family), _l3_cfg_commit_type_to_string(commit_type, sbuf_commit_type, sizeof(sbuf_commit_type))); - 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_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; - 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; - } + const NMDedupMultiHeadEntry * head_entry; + const ObjStatesSyncFilterData sync_filter_data = { + .self = self, + .commit_type = commit_type, + }; + 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); + _obj_states_sync_filter, + (gpointer) &sync_filter_data); - 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; - 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); + _obj_states_sync_filter, + (gpointer) &sync_filter_data); route_table_sync = nm_l3_config_data_get_route_table_sync(self->priv.p->combined_l3cd_commited, addr_family); } + if (!IS_IPv4) { + _l3_commit_ip6_privacy(self, commit_type); + _l3_commit_ndisc_params(self, commit_type); + } + if (route_table_sync == NM_IP_ROUTE_TABLE_SYNC_MODE_NONE) route_table_sync = NM_IP_ROUTE_TABLE_SYNC_MODE_ALL; @@ -3367,17 +3932,11 @@ _l3_commit_one(NML3Cfg * self, addr_family, self->priv.ifindex, route_table_sync); - } else if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) { - 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); + _obj_state_zombie_lst_prune_all(self, addr_family); + } else + _obj_state_zombie_lst_get_prune_lists(self, addr_family, &addresses_prune, &routes_prune); - /* 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(). */ /* FIXME(l3cfg): need to honor and set nm_l3_config_data_get_mtu(). */ nm_platform_ip_address_sync(self->priv.platform, @@ -3409,8 +3968,11 @@ _l3_commit_one(NML3Cfg * self, static void _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) { - nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; - gboolean commit_type_detected = FALSE; + _nm_unused gs_unref_object NML3Cfg *self_keep_alive = NULL; + nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL; + NML3CfgCommitType commit_type_auto; + gboolean commit_type_from_auto = FALSE; + gboolean is_sticky_update = FALSE; char sbuf_ct[30]; gboolean changed_combined_l3cd; @@ -3423,44 +3985,45 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) 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: - /* if in "AUTO" mode we currently have commit-type "UPDATE", that - * causes also the following update to still be "UPDATE". Either - * the same commit */ - commit_type_detected = TRUE; - commit_type = nm_l3cfg_commit_type_get(self); - if (commit_type == NM_L3_CFG_COMMIT_TYPE_UPDATE) - self->priv.p->commit_type_update_sticky = TRUE; - else if (self->priv.p->commit_type_update_sticky) { + /* The actual commit type is always the maximum of what is requested + * and what is registered via nm_l3cfg_commit_type_register(), combined + * with the ad-hoc requested @commit_type argument. */ + commit_type_auto = nm_l3cfg_commit_type_get(self); + if (commit_type == NM_L3_CFG_COMMIT_TYPE_AUTO || commit_type_auto > commit_type) { + commit_type_from_auto = TRUE; + commit_type = commit_type_auto; + } + + /* Levels UPDATE and higher are sticky. That means, when do perform such a commit + * type, then the next one will at least be of level "UPDATE". The idea is + * that if the current commit adds an address, then the following needs + * to do at least "UPDATE" level to remove it again. Even if in the meantime + * the "UPDATE" is unregistered (nm_l3cfg_commit_type_unregister()). */ + if (commit_type < NM_L3_CFG_COMMIT_TYPE_UPDATE) { + if (self->priv.p->commit_type_update_sticky) { self->priv.p->commit_type_update_sticky = FALSE; commit_type = NM_L3_CFG_COMMIT_TYPE_UPDATE; + is_sticky_update = TRUE; } - break; - case NM_L3_CFG_COMMIT_TYPE_ASSUME: - break; - case NM_L3_CFG_COMMIT_TYPE_REAPPLY: - case NM_L3_CFG_COMMIT_TYPE_UPDATE: - self->priv.p->commit_type_update_sticky = FALSE; - break; - case NM_L3_CFG_COMMIT_TYPE_NONE: - break; - } + } else + self->priv.p->commit_type_update_sticky = TRUE; - _LOGT("commit %s%s%s", + _LOGT("commit %s%s%s%s", _l3_cfg_commit_type_to_string(commit_type, sbuf_ct, sizeof(sbuf_ct)), - commit_type_detected ? " (auto)" : "", + commit_type_from_auto ? " (auto)" : "", + is_sticky_update ? " (sticky-update)" : "", is_idle ? " (idle handler)" : ""); - if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) - return; + nm_assert(commit_type > NM_L3_CFG_COMMIT_TYPE_AUTO); - self->priv.p->commit_reentrant_count++; + if (nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source)) + self_keep_alive = self; + self->priv.p->commit_on_idle_type = NM_L3_CFG_COMMIT_TYPE_AUTO; - nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); + if (commit_type <= NM_L3_CFG_COMMIT_TYPE_NONE) + return; - if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) - _l3cfg_externally_removed_objs_drop(self); + self->priv.p->commit_reentrant_count++; _l3cfg_update_combined_config(self, TRUE, @@ -3468,7 +4031,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) &l3cd_old, &changed_combined_l3cd); - /* FIXME(l3cfg): handle items currently not configurable in kernel. */ + _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT); _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); @@ -3481,6 +4044,7 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) _nm_l3cfg_emit_signal_notify_simple(self, NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT); } +/* See DOC(l3cfg:commit-type) */ void nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type) { @@ -3508,29 +4072,39 @@ nm_l3cfg_commit_type_get(NML3Cfg *self) * @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. + * @source: the source of the commit type, for logging. * * 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_commit() with %NM_L3_CFG_COMMIT_TYPE_AUTO. * - * Returns: a handle tracking the registration, or %NULL of @commit_type + * Returns: a handle tracking the registration, or %NULL if @commit_type * is %NM_L3_CFG_COMMIT_TYPE_NONE. */ NML3CfgCommitTypeHandle * nm_l3cfg_commit_type_register(NML3Cfg * self, NML3CfgCommitType commit_type, - NML3CfgCommitTypeHandle *existing_handle) + NML3CfgCommitTypeHandle *existing_handle, + const char * source) { NML3CfgCommitTypeHandle *handle; NML3CfgCommitTypeHandle *h; gboolean linked; + NML3CfgCommitTypeHandle *ret = NULL; + char buf[64]; 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)); + + /* It would be easy (and maybe convenient) to allow that @existing_handle + * can currently be registered on another NML3Cfg instance. But then we couldn't + * do this assertion, and it seems error prone to allow arbitrary handles where + * we cannot check whether it is valid. So if @existing_handle is given, it + * must be tracked by @self (and only by @self). */ nm_assert( !existing_handle || c_list_contains(&self->priv.p->commit_type_lst_head, &existing_handle->commit_type_lst)); @@ -3538,21 +4112,24 @@ nm_l3cfg_commit_type_register(NML3Cfg * self, if (existing_handle) { if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE) { nm_l3cfg_commit_type_unregister(self, existing_handle); - return NULL; + goto out; + } + if (existing_handle->commit_type == commit_type) { + ret = existing_handle; + goto out; } - 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; + goto out; + handle = g_slice_new(NML3CfgCommitTypeHandle); if (c_list_is_empty(&self->priv.p->commit_type_lst_head)) g_object_ref(self); } + handle->commit_type = commit_type; + 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) { @@ -3564,7 +4141,15 @@ nm_l3cfg_commit_type_register(NML3Cfg * self, if (!linked) c_list_link_tail(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst); - return handle; + ret = handle; +out: + _LOGT("commit type register (type \"%s\", source \"%s\", existing " NM_HASH_OBFUSCATE_PTR_FMT + ") -> " NM_HASH_OBFUSCATE_PTR_FMT "", + _l3_cfg_commit_type_to_string(commit_type, buf, sizeof(buf)), + source, + NM_HASH_OBFUSCATE_PTR(existing_handle), + NM_HASH_OBFUSCATE_PTR(ret)); + return ret; } void @@ -3577,6 +4162,8 @@ nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle) nm_assert(c_list_contains(&self->priv.p->commit_type_lst_head, &handle->commit_type_lst)); + _LOGT("commit type unregister " NM_HASH_OBFUSCATE_PTR_FMT "", NM_HASH_OBFUSCATE_PTR(handle)); + c_list_unlink_stale(&handle->commit_type_lst); if (c_list_is_empty(&self->priv.p->commit_type_lst_head)) g_object_unref(self); @@ -3688,7 +4275,7 @@ _nm_l3cfg_unregister_ipv4ll(NML3Cfg *self) { nm_assert(NM_IS_L3CFG(self)); - /* we don't own the refernce to "self->priv.p->ipv4ll", but + /* we don't own the reference to "self->priv.p->ipv4ll", but * when that instance gets destroyed, we get called back to * forget about it. Basically, it's like a weak pointer. */ @@ -3747,6 +4334,14 @@ nm_l3cfg_init(NML3Cfg *self) c_list_init(&self->priv.p->acd_lst_head); c_list_init(&self->priv.p->acd_event_notify_lst_head); c_list_init(&self->priv.p->commit_type_lst_head); + c_list_init(&self->priv.p->obj_state_lst_head); + c_list_init(&self->priv.p->obj_state_temporary_not_available_lst_head); + c_list_init(&self->priv.p->obj_state_zombie_lst_head); + + self->priv.p->obj_state_hash = g_hash_table_new_full(nmp_object_indirect_id_hash, + nmp_object_indirect_id_equal, + _obj_state_data_free, + NULL); } static void @@ -3786,9 +4381,7 @@ finalize(GObject *object) nm_assert(c_list_is_empty(&self->priv.p->commit_type_lst_head)); - nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); - - nm_assert(nm_g_array_len(self->priv.p->property_emit_list) == 0u); + nm_assert(!self->priv.p->commit_on_idle_source); _l3_acd_data_prune(self, TRUE); @@ -3801,15 +4394,12 @@ 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); + nm_clear_g_source_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - nm_clear_pointer(&self->priv.p->externally_removed_objs_hash, g_hash_table_unref); + nm_clear_pointer(&self->priv.p->obj_state_hash, g_hash_table_destroy); + nm_assert(c_list_is_empty(&self->priv.p->obj_state_lst_head)); + nm_assert(c_list_is_empty(&self->priv.p->obj_state_temporary_not_available_lst_head)); + nm_assert(c_list_is_empty(&self->priv.p->obj_state_zombie_lst_head)); g_clear_object(&self->priv.netns); g_clear_object(&self->priv.platform); |