From 398f6a8afbe11aea976a078f3028348e52c34ee1 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 8 Sep 2021 16:44:05 +0200 Subject: [PATCH 01/17] glib-aux: add nm_assert_is_bool() helper --- src/libnm-glib-aux/nm-shared-utils.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 83240d2b94..49d25787cc 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -18,6 +18,7 @@ typedef enum _nm_packed { NM_OPTION_BOOL_TRUE = 1, } NMOptionBool; +#define nm_assert_is_bool(value) nm_assert(NM_IN_SET((value), 0, 1)) #define nm_assert_is_ternary(value) nm_assert(NM_IN_SET((value), -1, 0, 1)) /*****************************************************************************/ From 31df5d554a1a16fbe50470e270bab3d7e55530ad Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 16:20:19 +0200 Subject: [PATCH 02/17] platform: add missing flags to nm_platform_addr_flags2str() --- src/libnm-platform/nm-platform.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 80cfe97b13..0681624826 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -6361,12 +6361,15 @@ NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str, NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"), NM_UTILS_FLAGS2STR(IFA_F_NODAD, "nodad"), NM_UTILS_FLAGS2STR(IFA_F_OPTIMISTIC, "optimistic"), + NM_UTILS_FLAGS2STR(IFA_F_DADFAILED, "dadfailed"), NM_UTILS_FLAGS2STR(IFA_F_HOMEADDRESS, "homeaddress"), NM_UTILS_FLAGS2STR(IFA_F_DEPRECATED, "deprecated"), + NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), NM_UTILS_FLAGS2STR(IFA_F_PERMANENT, "permanent"), NM_UTILS_FLAGS2STR(IFA_F_MANAGETEMPADDR, "mngtmpaddr"), NM_UTILS_FLAGS2STR(IFA_F_NOPREFIXROUTE, "noprefixroute"), - NM_UTILS_FLAGS2STR(IFA_F_TENTATIVE, "tentative"), ); + NM_UTILS_FLAGS2STR(IFA_F_MCAUTOJOIN, "mcautojoin"), + NM_UTILS_FLAGS2STR(IFA_F_STABLE_PRIVACY, "stable-privacy"), ); NM_UTILS_ENUM2STR_DEFINE(nm_platform_route_scope2str, int, From 7df4b2a2eb204854eba13f3e90b1ed580b631c0f Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 16:22:51 +0200 Subject: [PATCH 03/17] platform: use IFA_F_SECONDARY instead of IFA_F_TEMPORARY These names are aliases. Prefer one over the other. --- src/core/ndisc/nm-ndisc.h | 4 ++-- src/core/tests/test-ip6-config.c | 12 ++++++------ src/libnm-platform/nm-platform.c | 14 ++++++++------ 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/core/ndisc/nm-ndisc.h b/src/core/ndisc/nm-ndisc.h index 968f739ee2..5b43472f6c 100644 --- a/src/core/ndisc/nm-ndisc.h +++ b/src/core/ndisc/nm-ndisc.h @@ -241,7 +241,7 @@ static inline gboolean nm_ndisc_dad_addr_is_fail_candidate_event(NMPlatformSignalChangeType change_type, const NMPlatformIP6Address *addr) { - return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY) + return !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY) && ((change_type == NM_PLATFORM_SIGNAL_CHANGED && addr->n_ifa_flags & IFA_F_DADFAILED) || (change_type == NM_PLATFORM_SIGNAL_REMOVED && addr->n_ifa_flags & IFA_F_TENTATIVE)); @@ -255,7 +255,7 @@ nm_ndisc_dad_addr_is_fail_candidate(NMPlatform *platform, const NMPObject *obj) addr = NMP_OBJECT_CAST_IP6_ADDRESS( nm_platform_lookup_obj(platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj)); if (addr - && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TEMPORARY) + && (NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_SECONDARY) || !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_DADFAILED))) { /* the address still/again exists and is not in DADFAILED state. Skip it. */ return FALSE; diff --git a/src/core/tests/test-ip6-config.c b/src/core/tests/test-ip6-config.c index ddf4c789c8..2e6d8aaa19 100644 --- a/src/core/tests/test-ip6-config.c +++ b/src/core/tests/test-ip6-config.c @@ -327,7 +327,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("2607:f0d0:1002:51::8", NULL, 64, @@ -336,7 +336,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("2607:f0d0:1002:51::0", NULL, 64, @@ -345,7 +345,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("fec0::1", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); ADDR_ADD("fe80::208:74ff:feda:625c", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); ADDR_ADD("fe80::208:74ff:feda:625d", NULL, 128, 0, NM_IP_CONFIG_SOURCE_KERNEL, 0, 0, 0, 0); @@ -374,7 +374,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("2607:f0d0:1002:51::4", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); ADDR_ADD("2607:f0d0:1002:51::5", NULL, 64, 0, NM_IP_CONFIG_SOURCE_USER, 0, 0, 0, 0); ADDR_ADD("2607:f0d0:1002:51::8", @@ -385,7 +385,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("2607:f0d0:1002:51::0", NULL, 64, @@ -394,7 +394,7 @@ test_nm_ip6_config_addresses_sort(void) 0, 0, 0, - IFA_F_TEMPORARY); + IFA_F_SECONDARY); ADDR_ADD("2607:f0d0:1002:51::6", NULL, 64, diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 0681624826..54af5d71cc 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -3657,7 +3657,7 @@ _addr_array_clean_expired(int addr_family, } #endif - if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_TEMPORARY)) { + if (!NM_IS_IPv4(addr_family) && NM_FLAGS_HAS(a->n_ifa_flags, IFA_F_SECONDARY)) { /* temporary addresses are never added explicitly by NetworkManager but * kernel adds them via mngtempaddr flag. * @@ -4262,7 +4262,7 @@ nm_platform_ip_address_get_prune_list(NMPlatform *self, if (!IS_IPv4) { if (exclude_ipv6_temporary_addrs - && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_TEMPORARY)) + && NM_FLAGS_HAS(NMP_OBJECT_CAST_IP_ADDRESS(obj)->n_ifa_flags, IFA_F_SECONDARY)) continue; } @@ -6356,6 +6356,8 @@ NM_UTILS_ENUM2STR_DEFINE(nm_platform_link_inet6_addrgenmode2str, NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_STABLE_PRIVACY, "stable-privacy"), NM_UTILS_ENUM2STR(NM_IN6_ADDR_GEN_MODE_RANDOM, "random"), ); +G_STATIC_ASSERT(IFA_F_SECONDARY == IFA_F_TEMPORARY); + NM_UTILS_FLAGS2STR_DEFINE(nm_platform_addr_flags2str, unsigned, NM_UTILS_FLAGS2STR(IFA_F_SECONDARY, "secondary"), @@ -7812,20 +7814,20 @@ nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1, NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address), _address_pretty_sort_get_prio_6(&a1->address)); - ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY); - ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_TEMPORARY); + ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY); + ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY); if (ipv6_privacy1 || ipv6_privacy2) { gboolean public1 = TRUE; gboolean public2 = TRUE; if (ipv6_privacy1) { - if (a1->n_ifa_flags & IFA_F_TEMPORARY) + if (a1->n_ifa_flags & IFA_F_SECONDARY) public1 = prefer_temp; else public1 = !prefer_temp; } if (ipv6_privacy2) { - if (a2->n_ifa_flags & IFA_F_TEMPORARY) + if (a2->n_ifa_flags & IFA_F_SECONDARY) public2 = prefer_temp; else public2 = !prefer_temp; From e38ddb52e3c53494b3f2212710c7ecfd10be6387 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 11:21:08 +0200 Subject: [PATCH 04/17] all: rename nmtst_* functions that are used by the daemon The name prefix "nmtst_*" is reserved for test helpers and stub function. Such functions should not be in the actual build artifacts, like the NetworkManager binary. Instead, nmtst_connection_assert_unchanging() is not a test helper. It is a assertion function that is only enabled with NM_MORE_ASSERTS builds. That's different. Rename. In other words, $ nm src/core/NetworkManager src/libnm-client-impl/.libs/libnm.so | grep nmtst should give no results. --- src/core/settings/nm-settings-connection.c | 2 +- src/core/settings/nm-settings.c | 2 +- .../plugins/ifcfg-rh/nms-ifcfg-rh-storage.c | 2 +- .../plugins/ifupdown/nms-ifupdown-plugin.c | 2 +- .../plugins/keyfile/nms-keyfile-storage.c | 2 +- src/libnm-core-impl/nm-connection.c | 32 +++++++++---------- src/libnm-core-impl/nm-setting-private.h | 2 +- src/libnm-core-impl/nm-simple-connection.c | 2 +- src/libnm-core-impl/tests/test-setting.c | 2 +- src/libnm-core-intern/nm-core-internal.h | 6 ++-- 10 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/core/settings/nm-settings-connection.c b/src/core/settings/nm-settings-connection.c index d4bfca0c40..71da97d0f7 100644 --- a/src/core/settings/nm-settings-connection.c +++ b/src/core/settings/nm-settings-connection.c @@ -382,7 +382,7 @@ _nm_settings_connection_set_connection(NMSettingsConnection * self, NM_SETTING_COMPARE_FLAG_EXACT)) { connection_old = priv->connection; priv->connection = g_object_ref(new_connection); - nmtst_connection_assert_unchanging(priv->connection); + nm_assert_connection_unchanging(priv->connection); _getsettings_cached_clear(priv); _nm_settings_notify_sorted_by_autoconnect_priority_maybe_changed(priv->settings); diff --git a/src/core/settings/nm-settings.c b/src/core/settings/nm-settings.c index 32374c5940..b71b9d189d 100644 --- a/src/core/settings/nm-settings.c +++ b/src/core/settings/nm-settings.c @@ -1352,7 +1352,7 @@ _connection_changed_track(NMSettings * self, || (_nm_connection_verify(connection, NULL) == NM_SETTING_VERIFY_SUCCESS)); nm_assert(!connection || nm_streq0(uuid, nm_connection_get_uuid(connection))); - nmtst_connection_assert_unchanging(connection); + nm_assert_connection_unchanging(connection); sett_conn_entry = _sett_conn_entries_get(self, uuid) ?: _sett_conn_entries_create_and_add(self, uuid); diff --git a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c index 134bdf6844..3feb444026 100644 --- a/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c +++ b/src/core/settings/plugins/ifcfg-rh/nms-ifcfg-rh-storage.c @@ -112,7 +112,7 @@ nms_ifcfg_rh_storage_new_connection(NMSIfcfgRHPlugin * plugin, nm_assert(NM_IS_CONNECTION(connection_take)); nm_assert(_nm_connection_verify(connection_take, NULL) == NM_SETTING_VERIFY_SUCCESS); - nmtst_connection_assert_unchanging(connection_take); + nm_assert_connection_unchanging(connection_take); self = _storage_new(plugin, nm_connection_get_uuid(connection_take), filename); self->connection = connection_take; diff --git a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c index b66139e3a4..602a7327d2 100644 --- a/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c +++ b/src/core/settings/plugins/ifupdown/nms-ifupdown-plugin.c @@ -323,7 +323,7 @@ load_eni_ifaces(NMSIfupdownPlugin *self) NM_PRINT_FMT_QUOTED(local, " (", local->message, ")", "")); sd = NULL; } else { - nmtst_connection_assert_unchanging(connection); + nm_assert_connection_unchanging(connection); uuid = nm_connection_get_uuid(connection); if (!storage) diff --git a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c index 8c526c81b4..ec0634c43d 100644 --- a/src/core/settings/plugins/keyfile/nms-keyfile-storage.c +++ b/src/core/settings/plugins/keyfile/nms-keyfile-storage.c @@ -168,7 +168,7 @@ nms_keyfile_storage_new_connection(NMSKeyfilePlugin * plugin, nm_assert(filename && filename[0] == '/'); nm_assert(storage_type >= NMS_KEYFILE_STORAGE_TYPE_RUN && storage_type <= _NMS_KEYFILE_STORAGE_TYPE_LIB_LAST); - nmtst_connection_assert_unchanging(connection_take); + nm_assert_connection_unchanging(connection_take); self = _storage_new(plugin, nm_connection_get_uuid(connection_take), diff --git a/src/libnm-core-impl/nm-connection.c b/src/libnm-core-impl/nm-connection.c index 9bc8df3f9b..dc85ea0c03 100644 --- a/src/libnm-core-impl/nm-connection.c +++ b/src/libnm-core-impl/nm-connection.c @@ -2115,23 +2115,23 @@ _nm_connection_ensure_normalized(NMConnection * connection, #if NM_MORE_ASSERTS static void -_nmtst_connection_unchanging_changed_cb(NMConnection *connection, gpointer user_data) +_nm_assert_connection_unchanging_changed_cb(NMConnection *connection, gpointer user_data) { nm_assert_not_reached(); } static void -_nmtst_connection_unchanging_secrets_updated_cb(NMConnection *connection, - const char * setting_name, - gpointer user_data) +_nm_assert_connection_unchanging_secrets_updated_cb(NMConnection *connection, + const char * setting_name, + gpointer user_data) { nm_assert_not_reached(); } -const char _nmtst_connection_unchanging_user_data = 0; +const char _nm_assert_connection_unchanging_user_data = 0; void -nmtst_connection_assert_unchanging(NMConnection *connection) +nm_assert_connection_unchanging(NMConnection *connection) { if (!connection) return; @@ -2144,7 +2144,7 @@ nmtst_connection_assert_unchanging(NMConnection *connection) 0, NULL, NULL, - (gpointer) &_nmtst_connection_unchanging_user_data) + (gpointer) &_nm_assert_connection_unchanging_user_data) != 0) { /* avoid connecting the assertion handler multiple times. */ return; @@ -2152,16 +2152,16 @@ nmtst_connection_assert_unchanging(NMConnection *connection) g_signal_connect(connection, NM_CONNECTION_CHANGED, - G_CALLBACK(_nmtst_connection_unchanging_changed_cb), - (gpointer) &_nmtst_connection_unchanging_user_data); + G_CALLBACK(_nm_assert_connection_unchanging_changed_cb), + (gpointer) &_nm_assert_connection_unchanging_user_data); g_signal_connect(connection, NM_CONNECTION_SECRETS_CLEARED, - G_CALLBACK(_nmtst_connection_unchanging_changed_cb), - (gpointer) &_nmtst_connection_unchanging_user_data); + G_CALLBACK(_nm_assert_connection_unchanging_changed_cb), + (gpointer) &_nm_assert_connection_unchanging_user_data); g_signal_connect(connection, NM_CONNECTION_SECRETS_UPDATED, - G_CALLBACK(_nmtst_connection_unchanging_secrets_updated_cb), - (gpointer) &_nmtst_connection_unchanging_user_data); + G_CALLBACK(_nm_assert_connection_unchanging_secrets_updated_cb), + (gpointer) &_nm_assert_connection_unchanging_user_data); } #endif @@ -2338,7 +2338,7 @@ nm_connection_need_secrets(NMConnection *connection, GPtrArray **hints) if (!setting) continue; - nm_assert(!setting_before || _nmtst_nm_setting_sort(setting_before, setting) < 0); + nm_assert(!setting_before || _nm_setting_sort_for_nm_assert(setting_before, setting) < 0); nm_assert(!setting_before || _nm_setting_compare_priority(setting_before, setting) <= 0); setting_before = setting; @@ -2660,7 +2660,7 @@ nm_connection_is_type(NMConnection *connection, const char *type) } int -_nmtst_nm_setting_sort(NMSetting *a, NMSetting *b) +_nm_setting_sort_for_nm_assert(NMSetting *a, NMSetting *b) { g_assert(NM_IS_SETTING(a)); g_assert(NM_IS_SETTING(b)); @@ -2727,7 +2727,7 @@ nm_connection_get_settings(NMConnection *connection, guint *out_length) NMSetting *setting = priv->settings[nm_meta_setting_types_by_priority[i]]; if (setting) { - nm_assert(j == 0 || _nmtst_nm_setting_sort(arr[j - 1], setting) < 0); + nm_assert(j == 0 || _nm_setting_sort_for_nm_assert(arr[j - 1], setting) < 0); arr[j++] = setting; } } diff --git a/src/libnm-core-impl/nm-setting-private.h b/src/libnm-core-impl/nm-setting-private.h index 843fdb6453..b7c60536a0 100644 --- a/src/libnm-core-impl/nm-setting-private.h +++ b/src/libnm-core-impl/nm-setting-private.h @@ -175,7 +175,7 @@ void _nm_setting_ip_config_private_init(gpointer self, NMSettingIPConfigPrivate NMSettingPriority _nm_setting_get_base_type_priority(NMSetting *setting); int _nm_setting_compare_priority(gconstpointer a, gconstpointer b); -int _nmtst_nm_setting_sort(NMSetting *a, NMSetting *b); +int _nm_setting_sort_for_nm_assert(NMSetting *a, NMSetting *b); /*****************************************************************************/ diff --git a/src/libnm-core-impl/nm-simple-connection.c b/src/libnm-core-impl/nm-simple-connection.c index 6b5b118413..f16d9d59c6 100644 --- a/src/libnm-core-impl/nm-simple-connection.c +++ b/src/libnm-core-impl/nm-simple-connection.c @@ -161,7 +161,7 @@ dispose(GObject *object) #if NM_MORE_ASSERTS g_signal_handlers_disconnect_by_data(object, - (gpointer) &_nmtst_connection_unchanging_user_data); + (gpointer) &_nm_assert_connection_unchanging_user_data); #endif nm_connection_clear_secrets(connection); diff --git a/src/libnm-core-impl/tests/test-setting.c b/src/libnm-core-impl/tests/test-setting.c index c84bee19d2..7b8a5ba494 100644 --- a/src/libnm-core-impl/tests/test-setting.c +++ b/src/libnm-core-impl/tests/test-setting.c @@ -152,7 +152,7 @@ test_nm_meta_setting_types_by_priority(void) for (j = 0; j < i; j++) { NMSetting *other = arr->pdata[j]; - if (_nmtst_nm_setting_sort(other, setting) >= 0) { + if (_nm_setting_sort_for_nm_assert(other, setting) >= 0) { g_error("sort order for nm_meta_setting_types_by_priority[%d vs %d] is wrong: %s " "should be before %s", j, diff --git a/src/libnm-core-intern/nm-core-internal.h b/src/libnm-core-intern/nm-core-internal.h index c50eaf41ee..f3db626a3f 100644 --- a/src/libnm-core-intern/nm-core-internal.h +++ b/src/libnm-core-intern/nm-core-internal.h @@ -272,11 +272,11 @@ gboolean _nm_connection_ensure_normalized(NMConnection * connection, gboolean _nm_connection_remove_setting(NMConnection *connection, GType setting_type); #if NM_MORE_ASSERTS -extern const char _nmtst_connection_unchanging_user_data; -void nmtst_connection_assert_unchanging(NMConnection *connection); +extern const char _nm_assert_connection_unchanging_user_data; +void nm_assert_connection_unchanging(NMConnection *connection); #else static inline void -nmtst_connection_assert_unchanging(NMConnection *connection) +nm_assert_connection_unchanging(NMConnection *connection) {} #endif From d422434945958f40283ba702cd750f5771bf6f66 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 11:07:26 +0200 Subject: [PATCH 05/17] glib-aux: add nm_g_array_{first,last}() helpers A GArray can commonly used like a stack or a fifo list. Add convenience accessors to get the first/last element. --- src/libnm-glib-aux/nm-shared-utils.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/libnm-glib-aux/nm-shared-utils.h b/src/libnm-glib-aux/nm-shared-utils.h index 49d25787cc..eb7eb08dc5 100644 --- a/src/libnm-glib-aux/nm-shared-utils.h +++ b/src/libnm-glib-aux/nm-shared-utils.h @@ -2097,6 +2097,28 @@ nm_g_array_unref(GArray *arr) g_array_unref(arr); } +#define nm_g_array_first(arr, type) \ + ({ \ + GArray *const _arr = (arr); \ + guint _len; \ + \ + nm_assert(_arr); \ + _len = _arr->len; \ + nm_assert(_len > 0); \ + &g_array_index(arr, type, 0); \ + }) + +#define nm_g_array_last(arr, type) \ + ({ \ + GArray *const _arr = (arr); \ + guint _len; \ + \ + nm_assert(_arr); \ + _len = _arr->len; \ + nm_assert(_len > 0); \ + &g_array_index(arr, type, _len - 1u); \ + }) + #define nm_g_array_append_new(arr, type) \ ({ \ GArray *const _arr = (arr); \ From 0f5ed150082817d4bb4520e32561e4079301b69b Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 19:12:52 +0200 Subject: [PATCH 06/17] glib-aux/tests: add NMTST_COPY() helper --- src/libnm-glib-aux/nm-test-utils.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/libnm-glib-aux/nm-test-utils.h b/src/libnm-glib-aux/nm-test-utils.h index 2e01f3e810..253aaf0e86 100644 --- a/src/libnm-glib-aux/nm-test-utils.h +++ b/src/libnm-glib-aux/nm-test-utils.h @@ -1458,6 +1458,19 @@ next:; /*****************************************************************************/ +/* uses an expression statement to copy and return @arg. The use is for functions + * like nmtst_inet4_from_string(), which return a static (thread-local) variable. + * If you use such a statement twice, the result will be overwritten. The macro + * prevents that. */ +#define NMTST_COPY(arg) \ + ({ \ + typeof(arg) _arg = (arg); \ + \ + _arg; \ + }) + +/*****************************************************************************/ + #define __define_nmtst_static(NUM, SIZE) \ static inline const char *nmtst_static_##SIZE##_##NUM(const char *str) \ { \ From 49e85bee0ee91c88ccdd29dc4a1ede36bc3ceacf Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 7 Sep 2021 09:34:43 +0200 Subject: [PATCH 07/17] core/tests: add nmtst_utils_host_id_{push,pop}() helpers to stub the host-id The host-id gets read from /var/lib/NetworkManager/secret_key, and cached in a global variable. Other parts of the code can get the host ID using a singleton function. For testing, we need to inject a different host-id. Add two push/pop functions for that. Unlike nm_utils_host_id_get(), these functions are not thread-safe (nor is it possible to make them thread-safe in a reasonable manner). --- src/core/nm-core-utils.c | 75 +++++++++++++++++++++++++++++++++++++++- src/core/nm-core-utils.h | 27 +++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/src/core/nm-core-utils.c b/src/core/nm-core-utils.c index 2e727e6a1f..747e1c0920 100644 --- a/src/core/nm-core-utils.c +++ b/src/core/nm-core-utils.c @@ -2877,10 +2877,11 @@ typedef struct { bool timestamp_is_good : 1; } HostIdData; +static const HostIdData *volatile host_id_static; + static const HostIdData * _host_id_get(void) { - static const HostIdData *volatile host_id_static; const HostIdData *host_id; again: @@ -2941,6 +2942,78 @@ nm_utils_host_id_get_timestamp_ns(void) return _host_id_get()->timestamp_ns; } +static GArray * nmtst_host_id_stack = NULL; +static GMutex nmtst_host_id_lock; +const HostIdData *nmtst_host_id_static_0 = NULL; + +void +nmtst_utils_host_id_push(const guint8 *host_id, + gssize host_id_len, + gboolean is_good, + const gint64 *timestamp_ns) +{ + NM_G_MUTEX_LOCKED(&nmtst_host_id_lock); + gs_free char *str1_to_free = NULL; + HostIdData * h; + + g_assert(host_id_len >= -1); + + if (host_id_len < 0) + host_id_len = host_id ? strlen((const char *) host_id) : 0; + + nm_log_dbg(LOGD_CORE, + "nmtst: host-id push: \"%s\" (%zu), is-good=%d, timestamp=%" G_GINT64_FORMAT "%s", + nm_utils_buf_utf8safe_escape(host_id, + host_id_len, + NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, + &str1_to_free), + (gsize) host_id_len, + !!is_good, + timestamp_ns ? *timestamp_ns : 0, + timestamp_ns ? "" : " (not-good)"); + + if (!nmtst_host_id_stack) { + nmtst_host_id_stack = g_array_new(FALSE, FALSE, sizeof(HostIdData)); + nmtst_host_id_static_0 = g_atomic_pointer_get(&host_id_static); + } + + h = nm_g_array_append_new(nmtst_host_id_stack, HostIdData); + + *h = (HostIdData){ + .host_id = nm_memdup(host_id, host_id_len), + .host_id_len = host_id_len, + .timestamp_ns = timestamp_ns ? *timestamp_ns : 0, + .is_good = is_good, + .timestamp_is_good = !!timestamp_ns, + }; + + g_atomic_pointer_set(&host_id_static, h); +} + +void +nmtst_utils_host_id_pop(void) +{ + NM_G_MUTEX_LOCKED(&nmtst_host_id_lock); + HostIdData *h; + + g_assert(nmtst_host_id_stack); + g_assert(nmtst_host_id_stack->len > 0); + + nm_log_dbg(LOGD_CORE, "nmtst: host-id pop"); + + h = &g_array_index(nmtst_host_id_stack, HostIdData, nmtst_host_id_stack->len - 1); + + g_free((char *) h->host_id); + g_array_set_size(nmtst_host_id_stack, nmtst_host_id_stack->len - 1u); + + if (!g_atomic_pointer_compare_and_exchange( + &host_id_static, + h, + nmtst_host_id_stack->len == 0u ? nmtst_host_id_static_0 + : nm_g_array_last(nmtst_host_id_stack, HostIdData))) + g_assert_not_reached(); +} + /*****************************************************************************/ static const UuidData * diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index a1fa8830e9..614df02e75 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -247,6 +247,33 @@ const char *const * nm_utils_proc_cmdline_split(void); gboolean nm_utils_host_id_get(const guint8 **out_host_id, gsize *out_host_id_len); gint64 nm_utils_host_id_get_timestamp_ns(void); +void nmtst_utils_host_id_push(const guint8 *host_id, + gssize host_id_len, + gboolean is_good, + const gint64 *timestamp_ns); + +void nmtst_utils_host_id_pop(void); + +static inline void +_nmtst_auto_utils_host_id_context_pop(const char *const *unused) +{ + nmtst_utils_host_id_pop(); +} + +#define _NMTST_UTILS_HOST_ID_CONTEXT(uniq, host_id) \ + _nm_unused nm_auto(_nmtst_auto_utils_host_id_context_pop) \ + const char *const NM_UNIQ_T(_host_id_context_, uniq) = ({ \ + const gint64 _timestamp_ns = 1631000672; \ + \ + nmtst_utils_host_id_push((const guint8 *) "" host_id "", \ + NM_STRLEN(host_id), \ + TRUE, \ + &_timestamp_ns); \ + "" host_id ""; \ + }) + +#define NMTST_UTILS_HOST_ID_CONTEXT(host_id) _NMTST_UTILS_HOST_ID_CONTEXT(NM_UNIQ, host_id) + /*****************************************************************************/ int nm_utils_arp_type_detect_from_hwaddrlen(gsize hwaddr_len); From 4c3eed28cdac48a9893c78d6fa75d4974b1d3704 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Tue, 31 Aug 2021 17:12:45 +0200 Subject: [PATCH 08/17] l3cfg: add config_flags argument to nm_l3cfg_add_config() We will need to present additional options for tracking the configuration. Add a flags argument. --- src/core/nm-l3-ipv4ll.c | 1 + src/core/nm-l3cfg.c | 7 +++++++ src/core/nm-l3cfg.h | 5 +++++ src/core/tests/test-l3cfg.c | 2 ++ 4 files changed, 15 insertions(+) diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c index 2df87852bb..a0889341c9 100644 --- a/src/core/nm-l3-ipv4ll.c +++ b/src/core/nm-l3-ipv4ll.c @@ -593,6 +593,7 @@ _l3cd_config_add(NML3IPv4LL *self) 0, NM_L3_ACD_DEFEND_TYPE_ONCE, self->reg_timeout_msec, + NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD)) nm_assert_not_reached(); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 4f661aa060..4234aadeea 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -104,6 +104,7 @@ struct _NML3CfgCommitTypeHandle { typedef struct { const NML3ConfigData *l3cd; + NML3CfgConfigFlags config_flags; NML3ConfigMergeFlags merge_flags; union { struct { @@ -2710,6 +2711,7 @@ nm_l3cfg_add_config(NML3Cfg * self, guint32 default_route_penalty_6, NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, + NML3CfgConfigFlags config_flags, NML3ConfigMergeFlags merge_flags) { L3ConfigData *l3_config_data; @@ -2774,6 +2776,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, @@ -2797,6 +2800,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; diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 63f786f99a..6e02103b1c 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -28,6 +28,10 @@ typedef enum _nm_packed { NM_L3_ACD_DEFEND_TYPE_ALWAYS, } NML3AcdDefendType; +typedef enum _nm_packed { + NM_L3CFG_CONFIG_FLAGS_NONE = 0, +} NML3CfgConfigFlags; + typedef enum _nm_packed { NM_L3_ACD_ADDR_STATE_INIT, NM_L3_ACD_ADDR_STATE_PROBING, @@ -291,6 +295,7 @@ gboolean nm_l3cfg_add_config(NML3Cfg * self, guint32 default_route_penalty_6, NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, + NML3CfgConfigFlags config_flags, NML3ConfigMergeFlags merge_flags); gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *ifcfg); diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c index 2ef9f36eb8..09efb92465 100644 --- a/src/core/tests/test-l3cfg.c +++ b/src/core/tests/test-l3cfg.c @@ -434,6 +434,7 @@ test_l3cfg(gconstpointer test_data) 0, tdata->acd_defend_type_a, tdata->acd_timeout_msec_a, + NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_NONE); } @@ -595,6 +596,7 @@ _test_l3_ipv4ll_signal_notify(NML3Cfg * l3cfg, 0, NM_L3_ACD_DEFEND_TYPE_ONCE, nmtst_get_rand_bool() ? tdata->acd_timeout_msec : 0u, + NM_L3CFG_CONFIG_FLAGS_NONE, NM_L3_CONFIG_MERGE_FLAGS_NONE)) g_assert_not_reached(); nm_l3cfg_commit_on_idle_schedule(nm_l3_ipv4ll_get_l3cfg(tdata->l3ipv4ll)); From 3b92ad8b6df3e36ddfa536facb2bcc9bf2cdf9d7 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 2 Sep 2021 16:51:36 +0200 Subject: [PATCH 09/17] l3cfg: change NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD to be a NML3CfgConfigFlags value It's really not related to NML3ConfigMergeFlags, but fits better to NML3CfgConfigFlags. --- src/core/nm-l3-config-data.h | 18 ++++-------------- src/core/nm-l3-ipv4ll.c | 4 ++-- src/core/nm-l3cfg.c | 2 +- src/core/nm-l3cfg.h | 16 +++++++++++++++- 4 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index 0ea5bd0e69..7391c4c110 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -52,15 +52,6 @@ typedef enum { /** * NML3ConfigMergeFlags: * @NM_L3_CONFIG_MERGE_FLAGS_NONE: no flags set - * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set, - * the the NML3ConfigData doesn't get merged and it's information won't be - * synced. The only purpose is to run ACD on its IPv4 addresses, but - * regardless whether ACD succeeds/fails, the IP addresses won't be configured. - * The point is to run ACD first (without configuring it), and only - * commit the settings if requested. That can either happen by - * nm_l3cfg_add_config() the same NML3Cfg again (with a different - * tag), or by calling nm_l3cfg_add_config() again with this flag - * cleared (and the same tag). * @NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES: don't merge routes * @NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES: don't merge default routes. * Note that if the respective NML3ConfigData has NM_L3_CONFIG_DAT_FLAGS_IGNORE_MERGE_NO_DEFAULT_ROUTES @@ -71,11 +62,10 @@ typedef enum { */ typedef enum _nm_packed { NM_L3_CONFIG_MERGE_FLAGS_NONE = 0, - NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD = (1LL << 0), - NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 1), - NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 2), - NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 3), - NM_L3_CONFIG_MERGE_FLAGS_CLONE = (1LL << 4), + NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES = (1LL << 0), + NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES = (1LL << 1), + NM_L3_CONFIG_MERGE_FLAGS_NO_DNS = (1LL << 2), + NM_L3_CONFIG_MERGE_FLAGS_CLONE = (1LL << 3), } NML3ConfigMergeFlags; /*****************************************************************************/ diff --git a/src/core/nm-l3-ipv4ll.c b/src/core/nm-l3-ipv4ll.c index a0889341c9..521ff5ef24 100644 --- a/src/core/nm-l3-ipv4ll.c +++ b/src/core/nm-l3-ipv4ll.c @@ -593,8 +593,8 @@ _l3cd_config_add(NML3IPv4LL *self) 0, NM_L3_ACD_DEFEND_TYPE_ONCE, self->reg_timeout_msec, - NM_L3CFG_CONFIG_FLAGS_NONE, - NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD)) + NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD, + NM_L3_CONFIG_MERGE_FLAGS_NONE)) nm_assert_not_reached(); self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg, diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 4234aadeea..06d74cadc7 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -3043,7 +3043,7 @@ _l3cfg_update_combined_config(NML3Cfg * self, 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; diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 6e02103b1c..48ce86321a 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -28,8 +28,22 @@ typedef enum _nm_packed { NM_L3_ACD_DEFEND_TYPE_ALWAYS, } NML3AcdDefendType; +/** + * NML3CfgConfigFlags: + * @NM_L3CFG_CONFIG_FLAGS_NONE: no flags, the default. + * @NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD: if this merge flag is set, + * the the NML3ConfigData doesn't get merged and it's information won't be + * synced. The only purpose is to run ACD on its IPv4 addresses, but + * regardless whether ACD succeeds/fails, the IP addresses won't be configured. + * The point is to run ACD first (without configuring it), and only + * commit the settings if requested. That can either happen by + * nm_l3cfg_add_config() the same NML3Cfg again (with a different + * tag), or by calling nm_l3cfg_add_config() again with this flag + * cleared (and the same tag). + */ typedef enum _nm_packed { - NM_L3CFG_CONFIG_FLAGS_NONE = 0, + NM_L3CFG_CONFIG_FLAGS_NONE = 0, + NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), } NML3CfgConfigFlags; typedef enum _nm_packed { From cfebd0e50493f496926e115d6ed3a2482da2e598 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 09:08:22 +0200 Subject: [PATCH 10/17] l3cfg: add a "result" structure to nm_l3_config_data_merge()'s add-obj-hook --- src/core/nm-l3-config-data.c | 21 ++++++++++++--------- src/core/nm-l3-config-data.h | 14 +++++++++----- src/core/nm-l3cfg.c | 15 ++++++++------- 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 4af73b34e6..0e4c4f289a 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2696,7 +2696,7 @@ nm_l3_config_data_merge(NML3ConfigData * self, const guint32 * default_route_table_x /* length 2, for IS_IPv4 */, const guint32 * default_route_metric_x /* length 2, for IS_IPv4 */, const guint32 * default_route_penalty_x /* length 2, for IS_IPv4 */, - NML3ConfigMergeHookAddObj hook_add_addr, + NML3ConfigMergeHookAddObj hook_add_obj, gpointer hook_user_data) { static const guint32 x_default_route_table_x[2] = {RT_TABLE_MAIN, RT_TABLE_MAIN}; @@ -2734,19 +2734,22 @@ nm_l3_config_data_merge(NML3ConfigData * self, &obj, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { NMPlatformIPXAddress addr_stack; - const NMPlatformIPAddress *addr = NULL; - NMTernary ip4acd_not_ready = NM_TERNARY_DEFAULT; + const NMPlatformIPAddress *addr = NULL; + NML3ConfigMergeHookResult hook_result = { + .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + }; - if (hook_add_addr && !hook_add_addr(src, obj, &ip4acd_not_ready, hook_user_data)) + if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data)) continue; - if (IS_IPv4 && ip4acd_not_ready != NM_TERNARY_DEFAULT - && (!!ip4acd_not_ready) != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) { + nm_assert(IS_IPv4 || hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); + if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4 + && (!!hook_result.ip4acd_not_ready) + != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) { addr_stack.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); - addr_stack.a4.ip4acd_not_ready = (!!ip4acd_not_ready); + addr_stack.a4.ip4acd_not_ready = (!!hook_result.ip4acd_not_ready); addr = &addr_stack.ax; - } else - nm_assert(IS_IPv4 || ip4acd_not_ready == NM_TERNARY_DEFAULT); + } nm_l3_config_data_add_address_full(self, addr_family, diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index 7391c4c110..ac0278d3db 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -135,10 +135,14 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex * mu NMPlatform * platform, NMSettingIP6ConfigPrivacy ipv6_privacy_rfc4941); -typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData *l3cd, - const NMPObject * obj, - NMTernary * out_ip4acd_not_ready, - gpointer user_data); +typedef struct { + NMOptionBool ip4acd_not_ready; +} NML3ConfigMergeHookResult; + +typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData * l3cd, + const NMPObject * obj, + NML3ConfigMergeHookResult *result, + gpointer user_data); void nm_l3_config_data_merge(NML3ConfigData * self, const NML3ConfigData *src, @@ -146,7 +150,7 @@ void nm_l3_config_data_merge(NML3ConfigData * self, const guint32 *default_route_table_x /* length 2, for IS_IPv4 */, const guint32 *default_route_metric_x /* length 2, for IS_IPv4 */, const guint32 *default_route_penalty_x /* length 2, for IS_IPv4 */, - NML3ConfigMergeHookAddObj hook_add_addr, + NML3ConfigMergeHookAddObj hook_add_obj, gpointer hook_user_data); GPtrArray *nm_l3_config_data_get_blacklisted_ip4_routes(const NML3ConfigData *self, diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 06d74cadc7..90c3ec6546 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -2926,10 +2926,10 @@ typedef struct { } 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; @@ -2937,7 +2937,8 @@ _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(hook_result); + nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS) return TRUE; @@ -2969,7 +2970,7 @@ _l3_hook_add_addr_cb(const NML3ConfigData *l3cd, acd_bad = TRUE; out: - *out_ip4acd_not_ready = acd_bad ? NM_TERNARY_TRUE : NM_TERNARY_FALSE; + hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE; return TRUE; } @@ -3053,7 +3054,7 @@ _l3cfg_update_combined_config(NML3Cfg * self, l3cd_data->default_route_table_x, l3cd_data->default_route_metric_x, l3cd_data->default_route_penalty_x, - _l3_hook_add_addr_cb, + _l3_hook_add_obj_cb, &hook_data); } From 075bdefb71be45e3db26bab296a1946664bc857e Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 09:33:31 +0200 Subject: [PATCH 11/17] l3cfg: also call nm_l3_config_data_merge()'s add-obj-hook for routes --- src/core/nm-l3-config-data.c | 9 +++++ src/core/nm-l3cfg.c | 68 +++++++++++++++++++++--------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 0e4c4f289a..5f2554dac2 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2766,6 +2766,10 @@ nm_l3_config_data_merge(NML3ConfigData * self, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj); NMPlatformIPXRoute r; + NML3ConfigMergeHookResult hook_result = { + .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + }; + #define _ensure_r() \ G_STMT_START \ @@ -2781,6 +2785,11 @@ nm_l3_config_data_merge(NML3ConfigData * self, } \ G_STMT_END + if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data)) + continue; + + nm_assert(hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) { if (r_src->table_any) { _ensure_r(); diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 90c3ec6546..53ec0e4d8d 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -2937,41 +2937,51 @@ _l3_hook_add_obj_cb(const NML3ConfigData * l3cd, in_addr_t addr; gboolean acd_bad = FALSE; + nm_assert(obj); nm_assert(hook_result); nm_assert(hook_result->ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); - if (NMP_OBJECT_GET_TYPE(obj) != NMP_OBJECT_TYPE_IP4_ADDRESS) + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + + addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; + + if (ACD_ADDR_SKIP(addr)) + goto out_ip4_address; + + acd_data = _l3_acd_data_find(self, addr); + + if (!acd_data) { + /* we don't yet track an ACD state for this address. That can only + * happened 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_ip4_address; + } + + 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; + +out_ip4_address: + hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE; return TRUE; - addr = NMP_OBJECT_CAST_IP4_ADDRESS(obj)->address; - - if (ACD_ADDR_SKIP(addr)) - goto out; - - acd_data = _l3_acd_data_find(self, addr); - - 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; + 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; } - - 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; - -out: - hook_result->ip4acd_not_ready = acd_bad ? NM_OPTION_BOOL_TRUE : NM_OPTION_BOOL_FALSE; - return TRUE; } static void From 2eb7983a04d81fa4c9d4eee27ebd709c3ce0b00d Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 09:53:58 +0200 Subject: [PATCH 12/17] l3cfg: refactor modification of address in nm_l3_config_data_merge() --- src/core/nm-l3-config-data.c | 41 +++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 5f2554dac2..3ccc0bb3b5 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2733,44 +2733,60 @@ nm_l3_config_data_merge(NML3ConfigData * self, src, &obj, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4)) { - NMPlatformIPXAddress addr_stack; - const NMPlatformIPAddress *addr = NULL; + const NMPlatformIPAddress *a_src = NMP_OBJECT_CAST_IP_ADDRESS(obj); + NMPlatformIPXAddress a; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, }; +#define _ensure_a() \ + G_STMT_START \ + { \ + if (a_src != &a.ax) { \ + a_src = &a.ax; \ + if (IS_IPv4) \ + a.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); \ + else \ + a.a6 = *NMP_OBJECT_CAST_IP6_ADDRESS(obj); \ + } \ + } \ + G_STMT_END + + nm_assert(a_src->ifindex == self->ifindex); + if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data)) continue; nm_assert(IS_IPv4 || hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); + if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4 && (!!hook_result.ip4acd_not_ready) - != NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) { - addr_stack.a4 = *NMP_OBJECT_CAST_IP4_ADDRESS(obj); - addr_stack.a4.ip4acd_not_ready = (!!hook_result.ip4acd_not_ready); - addr = &addr_stack.ax; + != ((const NMPlatformIP4Address *) a_src)->ip4acd_not_ready) { + _ensure_a(); + a.a4.ip4acd_not_ready = (!!hook_result.ip4acd_not_ready); } nm_l3_config_data_add_address_full(self, addr_family, - addr ? NULL : obj, - addr, + a_src == &a.ax ? NULL : obj, + a_src == &a.ax ? a_src : NULL, NM_L3_CONFIG_ADD_FLAGS_EXCLUSIVE, NULL); } +#undef _ensure_a + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES)) { nm_l3_config_data_iter_obj_for_each (&iter, src, &obj, NMP_OBJECT_TYPE_IP_ROUTE(IS_IPv4)) { - const NMPlatformIPRoute *r_src = NMP_OBJECT_CAST_IP_ROUTE(obj); - NMPlatformIPXRoute r; + const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj); + NMPlatformIPXRoute r; NML3ConfigMergeHookResult hook_result = { .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, }; - #define _ensure_r() \ G_STMT_START \ { \ @@ -2780,11 +2796,12 @@ nm_l3_config_data_merge(NML3ConfigData * self, r.r4 = *NMP_OBJECT_CAST_IP4_ROUTE(obj); \ else \ r.r6 = *NMP_OBJECT_CAST_IP6_ROUTE(obj); \ - r.rx.ifindex = self->ifindex; \ } \ } \ G_STMT_END + nm_assert(r_src->ifindex == self->ifindex); + if (hook_add_obj && !hook_add_obj(src, obj, &hook_result, hook_user_data)) continue; From e07b41c4309f6f54695d1d14821fb81de4ef1b80 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 13:58:59 +0200 Subject: [PATCH 13/17] platform: add assume_config_once flags to NMPlatformIP{Address,Route} NMPlatformIP{Address,Route} are mainly the structs that we receive via netlink and get cached in the NMPlatform cache. However, the same structures are also used by the upper layers to track which addresses to add. Add a flag to addresses and routes, for a certain behavior, relevant during NML3Cfg commit. The idea is that during commits for NML3Cfg of type NM_L3_CFG_COMMIT_TYPE_ASSUME, no new addresses are added that are not already configured. In some cases, we want to override that, and need a flag to track that. More about that later. --- src/libnm-platform/nm-platform.c | 45 +++++++++++++++++++++++--------- src/libnm-platform/nm-platform.h | 10 +++++++ src/libnm-platform/nmp-object.h | 15 +++++++++++ 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 54af5d71cc..70e7582761 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -6304,6 +6304,7 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf " src %s" "%s" /* external */ "%s" /* ip4acd_not_ready */ + "%s" /* a_assume_config_once */ "", s_address, address->plen, @@ -6322,7 +6323,8 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf str_label, nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), address->external ? " ext" : "", - address->ip4acd_not_ready ? " ip4acd-not-ready" : ""); + address->ip4acd_not_ready ? " ip4acd-not-ready" : "", + address->a_assume_config_once ? " assume-config-once" : ""); g_free(str_peer); return buf; } @@ -6441,7 +6443,10 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf g_snprintf( buf, len, - "%s/%d lft %s pref %s%s%s%s%s src %s%s", + "%s/%d lft %s pref %s%s%s%s%s src %s" + "%s" /* external */ + "%s" /* a_assume_config_once */ + "", s_address, address->plen, str_lft_p, @@ -6451,7 +6456,8 @@ nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf str_dev, _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)), nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), - address->external ? " ext" : ""); + address->external ? " external" : "", + address->a_assume_config_once ? " assume-config-once" : ""); g_free(str_peer); return buf; } @@ -6543,6 +6549,7 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz "%s" /* initrwnd */ "%s" /* mtu */ "%s" /* is_external */ + "%s" /* r_assume_config_once */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -6599,7 +6606,8 @@ nm_platform_ip4_route_to_string(const NMPlatformIP4Route *route, char *buf, gsiz route->lock_mtu ? "lock " : "", route->mtu) : "", - route->is_external ? " (E)" : ""); + route->is_external ? " is-external" : "", + route->r_assume_config_once ? " assume-config-once" : ""); return buf; } @@ -6670,6 +6678,7 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz "%s" /* mtu */ "%s" /* pref */ "%s" /* is_external */ + "%s" /* r_assume_config_once */ "", nm_net_aux_rtnl_rtntype_n2a_maybe_buf(nm_platform_route_type_uncoerce(route->type_coerced), str_type), @@ -6730,7 +6739,8 @@ nm_platform_ip6_route_to_string(const NMPlatformIP6Route *route, char *buf, gsiz " pref %s", nm_icmpv6_router_pref_to_string(route->rt_pref, str_pref2, sizeof(str_pref2))) : "", - route->is_external ? " (E)" : ""); + route->is_external ? " is-external" : "", + route->r_assume_config_once ? " assume-config-once" : ""); return buf; } @@ -7866,7 +7876,8 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState NM_HASH_COMBINE_BOOLS(guint8, obj->external, obj->use_ip4_broadcast_address, - obj->ip4acd_not_ready)); + obj->ip4acd_not_ready, + obj->a_assume_config_once)); nm_hash_update_strarr(h, obj->label); } @@ -7889,6 +7900,7 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad NM_CMP_FIELD_STR(a, b, label); NM_CMP_FIELD_UNSAFE(a, b, external); NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready); + NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once); return 0; } @@ -7905,7 +7917,7 @@ nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState obj->plen, obj->address, obj->peer_address, - NM_HASH_COMBINE_BOOLS(guint8, obj->external)); + NM_HASH_COMBINE_BOOLS(guint8, obj->external, obj->a_assume_config_once)); } int @@ -7926,6 +7938,7 @@ nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a, const NMPlatformIP6Ad NM_CMP_FIELD(a, b, preferred); NM_CMP_FIELD(a, b, n_ifa_flags); NM_CMP_FIELD_UNSAFE(a, b, external); + NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once); return 0; } @@ -8026,7 +8039,7 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, obj->initrwnd, obj->mtu, obj->r_rtm_flags, - NM_HASH_COMBINE_BOOLS(guint8, + NM_HASH_COMBINE_BOOLS(guint16, obj->metric_any, obj->table_any, obj->lock_window, @@ -8034,7 +8047,8 @@ nm_platform_ip4_route_hash_update(const NMPlatformIP4Route *obj, obj->lock_initcwnd, obj->lock_initrwnd, obj->lock_mtu, - obj->is_external)); + obj->is_external, + obj->r_assume_config_once)); break; } } @@ -8124,8 +8138,10 @@ nm_platform_ip4_route_cmp(const NMPlatformIP4Route *a, NM_CMP_FIELD(a, b, initcwnd); NM_CMP_FIELD(a, b, initrwnd); NM_CMP_FIELD(a, b, mtu); - if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) { NM_CMP_FIELD_UNSAFE(a, b, is_external); + NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once); + } break; } return 0; @@ -8210,7 +8226,7 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, obj->rt_source, obj->mss, obj->r_rtm_flags, - NM_HASH_COMBINE_BOOLS(guint8, + NM_HASH_COMBINE_BOOLS(guint16, obj->metric_any, obj->table_any, obj->lock_window, @@ -8218,7 +8234,8 @@ nm_platform_ip6_route_hash_update(const NMPlatformIP6Route *obj, obj->lock_initcwnd, obj->lock_initrwnd, obj->lock_mtu, - obj->is_external), + obj->is_external, + obj->r_assume_config_once), obj->window, obj->cwnd, obj->initcwnd, @@ -8301,8 +8318,10 @@ nm_platform_ip6_route_cmp(const NMPlatformIP6Route *a, NM_CMP_DIRECT(_route_pref_normalize(a->rt_pref), _route_pref_normalize(b->rt_pref)); else NM_CMP_FIELD(a, b, rt_pref); - if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) + if (cmp_type == NM_PLATFORM_IP_ROUTE_CMP_TYPE_FULL) { NM_CMP_FIELD_UNSAFE(a, b, is_external); + NM_CMP_FIELD_UNSAFE(a, b, r_assume_config_once); + } break; } return 0; diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 22cfb092be..690ff68adf 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -329,6 +329,11 @@ typedef enum { * flag may indicate that the address is just for tracking purpose only, but the ACD * state is not yet ready for the address to be configured. */ \ bool ip4acd_not_ready : 1; \ + \ + /* Whether the address is should be configured once during assume. This is a meta flag + * that is not honored by NMPlatform (netlink code). Instead, it can be used by the upper + * layers which use NMPlatformIPAddress to track addresses that should be configured. */ \ + bool a_assume_config_once : 1; \ ; /** @@ -473,6 +478,11 @@ typedef union { * and is not reflected on netlink. */ \ bool is_external : 1; \ \ + /* Whether the route is should be configured once during assume. This is a meta flag + * that is not honored by NMPlatform (netlink code). Instead, it can be used by the upper + * layers which use NMPlatformIPRoute to track routes that should be configured. */ \ + bool r_assume_config_once : 1; \ + \ /* rtnh_flags * * Routes with rtm_flags RTM_F_CLONED are hidden by platform and diff --git a/src/libnm-platform/nmp-object.h b/src/libnm-platform/nmp-object.h index bf789fb077..6d9060cba4 100644 --- a/src/libnm-platform/nmp-object.h +++ b/src/libnm-platform/nmp-object.h @@ -1082,6 +1082,21 @@ nm_platform_lookup_object_by_addr_family(NMPlatform * platform, /*****************************************************************************/ +static inline gboolean +nmp_object_get_assume_config_once(const NMPObject *obj) +{ + switch (NMP_OBJECT_GET_TYPE(obj)) { + case NMP_OBJECT_TYPE_IP4_ADDRESS: + case NMP_OBJECT_TYPE_IP6_ADDRESS: + return NMP_OBJECT_CAST_IP_ADDRESS(obj)->a_assume_config_once; + case NMP_OBJECT_TYPE_IP4_ROUTE: + case NMP_OBJECT_TYPE_IP6_ROUTE: + return NMP_OBJECT_CAST_IP_ROUTE(obj)->r_assume_config_once; + default: + return nm_assert_unreachable_val(FALSE); + } +} + static inline const char * nmp_object_link_get_ifname(const NMPObject *obj) { From a909a4b305e0c07f3ab9297e54adb5c4fd149a2c Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 14:46:47 +0200 Subject: [PATCH 14/17] platform: move ip4acd_not_ready flag to NMPlatformIP4Address This flag is only relevant for IPv4. That is, because the way we do ACD/DAD is fundamentally different between IPv4 and IPv6. For IPv4, we use libn-acd while IPv6 we configure the address in kernel and wait for the tentative flag to go away. --- src/core/nm-l3-config-data.c | 4 ++-- src/core/nm-l3cfg.c | 2 +- src/libnm-platform/nm-platform.c | 8 ++++---- src/libnm-platform/nm-platform.h | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 3ccc0bb3b5..2c28f4cc65 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -2761,9 +2761,9 @@ nm_l3_config_data_merge(NML3ConfigData * self, if (hook_result.ip4acd_not_ready != NM_OPTION_BOOL_DEFAULT && IS_IPv4 && (!!hook_result.ip4acd_not_ready) - != ((const NMPlatformIP4Address *) a_src)->ip4acd_not_ready) { + != ((const NMPlatformIP4Address *) a_src)->a_acd_not_ready) { _ensure_a(); - a.a4.ip4acd_not_ready = (!!hook_result.ip4acd_not_ready); + a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready); } nm_l3_config_data_add_address_full(self, diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 53ec0e4d8d..c243c6a5bd 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -701,7 +701,7 @@ _l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointe GHashTable * externally_removed_objs_hash = user_data; if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS - && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->ip4acd_not_ready) + && NMP_OBJECT_CAST_IP4_ADDRESS(obj)->a_acd_not_ready) return FALSE; return !nm_g_hash_table_contains(externally_removed_objs_hash, obj); diff --git a/src/libnm-platform/nm-platform.c b/src/libnm-platform/nm-platform.c index 70e7582761..e65e47041e 100644 --- a/src/libnm-platform/nm-platform.c +++ b/src/libnm-platform/nm-platform.c @@ -6303,7 +6303,7 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf "%s" /* label */ " src %s" "%s" /* external */ - "%s" /* ip4acd_not_ready */ + "%s" /* a_acd_not_ready */ "%s" /* a_assume_config_once */ "", s_address, @@ -6323,7 +6323,7 @@ nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf str_label, nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)), address->external ? " ext" : "", - address->ip4acd_not_ready ? " ip4acd-not-ready" : "", + address->a_acd_not_ready ? " ip4acd-not-ready" : "", address->a_assume_config_once ? " assume-config-once" : ""); g_free(str_peer); return buf; @@ -7876,7 +7876,7 @@ nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState NM_HASH_COMBINE_BOOLS(guint8, obj->external, obj->use_ip4_broadcast_address, - obj->ip4acd_not_ready, + obj->a_acd_not_ready, obj->a_assume_config_once)); nm_hash_update_strarr(h, obj->label); } @@ -7899,7 +7899,7 @@ nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a, const NMPlatformIP4Ad NM_CMP_FIELD(a, b, n_ifa_flags); NM_CMP_FIELD_STR(a, b, label); NM_CMP_FIELD_UNSAFE(a, b, external); - NM_CMP_FIELD_UNSAFE(a, b, ip4acd_not_ready); + NM_CMP_FIELD_UNSAFE(a, b, a_acd_not_ready); NM_CMP_FIELD_UNSAFE(a, b, a_assume_config_once); return 0; } diff --git a/src/libnm-platform/nm-platform.h b/src/libnm-platform/nm-platform.h index 690ff68adf..e3f6d0433e 100644 --- a/src/libnm-platform/nm-platform.h +++ b/src/libnm-platform/nm-platform.h @@ -325,11 +325,6 @@ typedef enum { \ bool use_ip4_broadcast_address : 1; \ \ - /* Whether the address is ready to be configured. By default, an address is, but this - * flag may indicate that the address is just for tracking purpose only, but the ACD - * state is not yet ready for the address to be configured. */ \ - bool ip4acd_not_ready : 1; \ - \ /* Whether the address is should be configured once during assume. This is a meta flag * that is not honored by NMPlatform (netlink code). Instead, it can be used by the upper * layers which use NMPlatformIPAddress to track addresses that should be configured. */ \ @@ -356,6 +351,11 @@ typedef struct { struct _NMPlatformIP4Address { __NMPlatformIPAddress_COMMON; + /* Whether the address is ready to be configured. By default, an address is, but this + * flag may indicate that the address is just for tracking purpose only, but the ACD + * state is not yet ready for the address to be configured. */ + bool a_acd_not_ready : 1; + /* The local address IFA_LOCAL. */ in_addr_t address; From 8a3d913de8c7902d828cc0fb214a6220b148b6cd Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 14:10:39 +0200 Subject: [PATCH 15/17] l3cfg: add NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE flag It's a bit tricky how this flag works. It's needed for IPv6 link local addresses, which commits changes in %NM_L3_CFG_COMMIT_TYPE_ASSUME mode. See the code comments how it works. This commit only adds the flags and let's the NMPlatformIP{Address,Route} properly track it. What is still needed is to actually implement any meaning to that during the sync. --- src/core/nm-l3-config-data.c | 34 ++++++++++++++++++++++++++++++++-- src/core/nm-l3-config-data.h | 1 + src/core/nm-l3cfg.c | 7 +++++++ src/core/nm-l3cfg.h | 17 +++++++++++++++-- 4 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/core/nm-l3-config-data.c b/src/core/nm-l3-config-data.c index 2c28f4cc65..3ae17d5217 100644 --- a/src/core/nm-l3-config-data.c +++ b/src/core/nm-l3-config-data.c @@ -1104,6 +1104,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex * multi_idx, obj_new_stackinit.ip_address.addr_source = obj_old->ip_address.addr_source; modified = TRUE; } + + /* OR assume_config_once flag */ + if (obj_new->ip_address.a_assume_config_once + && !obj_old->ip_address.a_assume_config_once) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_address.a_assume_config_once = TRUE; + modified = TRUE; + } break; case NMP_OBJECT_TYPE_IP4_ROUTE: case NMP_OBJECT_TYPE_IP6_ROUTE: @@ -1113,6 +1121,14 @@ _l3_config_data_add_obj(NMDedupMultiIndex * multi_idx, obj_new_stackinit.ip_route.rt_source = obj_old->ip_route.rt_source; modified = TRUE; } + + /* OR assume_config_once flag */ + if (obj_new->ip_route.r_assume_config_once + && !obj_old->ip_route.r_assume_config_once) { + obj_new = nmp_object_stackinit_obj(&obj_new_stackinit, obj_new); + obj_new_stackinit.ip_route.r_assume_config_once = TRUE; + modified = TRUE; + } break; default: nm_assert_not_reached(); @@ -2736,7 +2752,8 @@ nm_l3_config_data_merge(NML3ConfigData * self, const NMPlatformIPAddress *a_src = NMP_OBJECT_CAST_IP_ADDRESS(obj); NMPlatformIPXAddress a; NML3ConfigMergeHookResult hook_result = { - .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + .assume_config_once = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_a() \ @@ -2766,6 +2783,12 @@ nm_l3_config_data_merge(NML3ConfigData * self, a.a4.a_acd_not_ready = (!!hook_result.ip4acd_not_ready); } + if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT + && (!!hook_result.assume_config_once) != a_src->a_assume_config_once) { + _ensure_a(); + a.ax.a_assume_config_once = (!!hook_result.assume_config_once); + } + nm_l3_config_data_add_address_full(self, addr_family, a_src == &a.ax ? NULL : obj, @@ -2784,7 +2807,8 @@ nm_l3_config_data_merge(NML3ConfigData * self, const NMPlatformIPRoute * r_src = NMP_OBJECT_CAST_IP_ROUTE(obj); NMPlatformIPXRoute r; NML3ConfigMergeHookResult hook_result = { - .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + .ip4acd_not_ready = NM_OPTION_BOOL_DEFAULT, + .assume_config_once = NM_OPTION_BOOL_DEFAULT, }; #define _ensure_r() \ @@ -2807,6 +2831,12 @@ nm_l3_config_data_merge(NML3ConfigData * self, nm_assert(hook_result.ip4acd_not_ready == NM_OPTION_BOOL_DEFAULT); + if (hook_result.assume_config_once != NM_OPTION_BOOL_DEFAULT + && (!!hook_result.assume_config_once) != r_src->r_assume_config_once) { + _ensure_r(); + r.rx.r_assume_config_once = (!!hook_result.assume_config_once); + } + if (!NM_FLAGS_HAS(merge_flags, NM_L3_CONFIG_MERGE_FLAGS_CLONE)) { if (r_src->table_any) { _ensure_r(); diff --git a/src/core/nm-l3-config-data.h b/src/core/nm-l3-config-data.h index ac0278d3db..b2f36b3ed1 100644 --- a/src/core/nm-l3-config-data.h +++ b/src/core/nm-l3-config-data.h @@ -137,6 +137,7 @@ NML3ConfigData *nm_l3_config_data_new_from_platform(NMDedupMultiIndex * mu typedef struct { NMOptionBool ip4acd_not_ready; + NMOptionBool assume_config_once; } NML3ConfigMergeHookResult; typedef gboolean (*NML3ConfigMergeHookAddObj)(const NML3ConfigData * l3cd, diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index c243c6a5bd..2e05200e38 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -2923,6 +2923,7 @@ nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag) typedef struct { NML3Cfg * self; gconstpointer tag; + bool assume_config_once; } L3ConfigMergeHookAddObjData; static gboolean @@ -2940,6 +2941,9 @@ _l3_hook_add_obj_cb(const NML3ConfigData * l3cd, 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); + + hook_result->assume_config_once = hook_data->assume_config_once; switch (NMP_OBJECT_GET_TYPE(obj)) { case NMP_OBJECT_TYPE_IP4_ADDRESS: @@ -3058,6 +3062,9 @@ _l3cfg_update_combined_config(NML3Cfg * self, 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, diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 48ce86321a..9190e1f398 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -40,10 +40,23 @@ typedef enum _nm_packed { * nm_l3cfg_add_config() the same NML3Cfg again (with a different * tag), or by calling nm_l3cfg_add_config() again with this flag * cleared (and the same tag). + * @NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE: a commit with + * %NM_L3_CFG_COMMIT_TYPE_ASSUME, means to not remove/add + * addresses that are missing/already exist. The assume mode + * is for taking over a device gracefully after restart, so + * it aims to preserve whatever was configured (or not configured). + * With this flag enabled, the first commit in assume mode will still + * add the addresses/routes. This is necessary for example with IPv6LL. + * Also while assuming a device, we want to configure things + * (like an IPv6 address), so we need to bypass the common + * "don't change" behavior. At least once. If the address/route + * is still not (no longer) configured on the subsequent + * commit, it's not getting added again. */ typedef enum _nm_packed { - NM_L3CFG_CONFIG_FLAGS_NONE = 0, - NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), + NM_L3CFG_CONFIG_FLAGS_NONE = 0, + NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), + NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1), } NML3CfgConfigFlags; typedef enum _nm_packed { From 929eae245d5dd19010d5a71f2a5f4b5add5a1829 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 6 Sep 2021 15:06:15 +0200 Subject: [PATCH 16/17] l3cfg: implement NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE and rework object state NML3Cfg tracks state about all addresses/routes. It needs that (at least) for the following reaons: 1) if a address/route gets added by NetworkManager and then gets externally removed then it is presumed that the user did this. In this case, we remember that ("externally-removed") to not re-add the address/route, until we do a full reapply. This was previously tracked as "externally_removed_objs_hash". 2) when NML3Cfg configures a address/route in kernel, and later the address/route is no longer to be configured, then NML3Cfg needs to delete it again. It thus needs to remember which addresses/routes it configured earlier to remove them. This was previously tracked via "last_addresses_x" and "last_routes_x". 3) kernel rejects configuring certain routes while a related IPv6 address is still tentative. That means, NML3Cfg needs to detect that, remember it, and retry later. That is previously tracked as "routes_temporary_not_available_hash". 4) during NM_L3_CFG_COMMIT_TYPE_ASSUME, we don't remove extraneous and don't add missing addresses/routes. This commit mode is done while assuming a device, that is, gracefully taking over after a restart. However, sometimes while assuming a device we forcefully want to configure an address/route. That happens for example if we do IPv6 link local addressing. Then we really want to add that address/route, even in assume mode. That is what the NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE flag does, and to implement that we need to track whether we already tried to add the address/route previously. This is something new. Consolidate these various states in a new "obj_state_hash" and "ObjStateData" structure. This solves above points the following way: 1) to track externally removed objects, we have a flag in ObjStateData that indicates whether the object was every configured and whether it currently is configured. Based on that we make decisions to configure (or not) an address. See "_obj_states_sync_filter()". 2) we now mark objects that NML3Cfg configured, which are still in platform and which are no longer to be configured as "zombies". 3) this is now tracked via ObjStateData's "os_temporary_not_available_lst". 4) with the available ObjStateData we can make appropriate decisions in "_obj_states_sync_filter()". --- src/core/nm-l3cfg.c | 906 ++++++++++++++++++++++++++++---------------- 1 file changed, 579 insertions(+), 327 deletions(-) diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 2e05200e38..9baef4e359 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,57 @@ typedef struct { G_STATIC_ASSERT(G_STRUCT_OFFSET(AcdData, info.addr) == 0); +typedef struct { + const NMPObject *obj; + + 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 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). */ + bool os_in_platform : 1; + + /* 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; @@ -158,9 +220,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; @@ -182,39 +246,7 @@ 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]; - }; - - union { - struct { - guint externally_removed_objs_cnt_routes_6; - guint externally_removed_objs_cnt_routes_4; - }; - guint externally_removed_objs_cnt_routes_x[2]; - }; - - union { - struct { - GPtrArray *last_addresses_6; - GPtrArray *last_addresses_4; - }; - GPtrArray *last_addresses_x[2]; - }; - - union { - struct { - GPtrArray *last_routes_6; - GPtrArray *last_routes_4; - }; - GPtrArray *last_routes_x[2]; - }; - - guint routes_temporary_not_available_id; + GSource *obj_state_temporary_not_available_timeout_source; gint8 commit_reentrant_count; @@ -554,157 +586,436 @@ _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 \ + { \ + 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_in_platform || _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) { \ + 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_in_platform \ + == (!!nm_platform_lookup_entry(_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, gboolean in_platform) +{ + ObjStateData *obj_state; + + obj_state = g_slice_new(ObjStateData); + *obj_state = (ObjStateData){ + .obj = nmp_object_ref(obj), + .os_in_platform = in_platform, + .os_was_in_platform = in_platform, + .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; - 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); + c_list_unlink_stale(&obj_state->os_lst); + c_list_unlink_stale(&obj_state->os_temporary_not_available_lst); + nmp_object_unref(obj_state->obj); + nm_g_slice_free(obj_state); } -static void -_l3cfg_externally_removed_objs_drop_unused(NML3Cfg *self) +static const char * +_obj_state_data_to_string(const ObjStateData *obj_state, char *buf, gsize buf_size) { - GHashTableIter h_iter; - const NMPObject *obj; - char sbuf[sizeof(_nm_utils_to_string_buffer)]; + 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_in_platform) { + 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; + + 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 +_obj_states_externally_removed_track(NML3Cfg *self, const NMPObject *obj, gboolean in_platform) +{ + 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 + == (!!nm_platform_lookup_entry(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 (obj_state->os_in_platform == (!!in_platform)) + goto out; + + if (!in_platform && !c_list_is_empty(&obj_state->os_zombie_lst)) { + /* this is a zombie. We can forget about it.*/ + obj_state->os_in_platform = FALSE; + 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))))--; + nm_assert(c_list_is_empty(&obj_state->os_zombie_lst)); + + if (in_platform) { + obj_state->os_in_platform = TRUE; + 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; + } + + obj_state->os_in_platform = FALSE; + _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 +_obj_states_update_all(NML3Cfg *self) +{ + 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)); + + 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_entry(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 (_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); + } + } + + 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_in_platform && 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); } } } -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_commited) - return; - - 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); - } - } - 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 (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 int IS_IPv4 = NM_IS_IPv4(addr_family); - NMDedupMultiIter iter; - const NMPObject *obj; - - if (!self->priv.p->combined_l3cd_commited) - return; - - 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); - } -} +typedef struct { + NML3Cfg * self; + NML3CfgCommitType commit_type; +} ObjStatesSyncFilterData; static gboolean -_l3cfg_externally_removed_objs_filter(/* const NMDedupMultiObj * */ gconstpointer o, - gpointer user_data) +_obj_states_sync_filter(/* const NMDedupMultiObj * */ gconstpointer o, gpointer user_data) { - const NMPObject *obj = o; - GHashTable * externally_removed_objs_hash = 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; - if (NMP_OBJECT_GET_TYPE(obj) == NMP_OBJECT_TYPE_IP4_ADDRESS + 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; - return !nm_g_hash_table_contains(externally_removed_objs_hash, obj); + 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 (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_in_platform + && sync_filter_data->commit_type != NM_L3_CFG_COMMIT_TYPE_REAPPLY) + return FALSE; + + return TRUE; +} + +static void +_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); + 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; + + nm_assert(NM_IS_L3CFG(self)); + nm_assert(out_addresses_prune && !*out_addresses_prune); + nm_assert(out_routes_prune && !*out_routes_prune); + + 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 void +_obj_state_zombie_lst_prune_all(NML3Cfg *self, int addr_family) +{ + char sbuf[sizeof(_nm_utils_to_string_buffer)]; + ObjStateData *obj_state; + ObjStateData *obj_state_safe; + + /* 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; + + 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))); + } } /*****************************************************************************/ @@ -846,7 +1157,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; } @@ -3105,6 +3416,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, @@ -3140,75 +3453,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 (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 (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 (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 @@ -3216,13 +3499,12 @@ _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; now_msec = nm_utils_get_monotonic_timestamp_msec(); @@ -3231,36 +3513,43 @@ _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; - } - } 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); + c_list_for_each_entry (obj_state, + &self->priv.p->obj_state_temporary_not_available_lst_head, + os_temporary_not_available_lst) { + nm_assert(obj_state->os_temporary_not_available_timestamp_msec > 0); + obj_state->os_tna_dirty = TRUE; } 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) + if (!obj_state) { + /* Hm? We don't track this object? Very odd, a bug? */ + nm_assert_not_reached(); + continue; + } + + 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 (!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; + } - 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. */ + 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))); @@ -3268,7 +3557,7 @@ _routes_temporary_not_available_update(NML3Cfg * self, continue; } - data->dirty = FALSE; + obj_state->os_tna_dirty = FALSE; continue; } @@ -3276,41 +3565,34 @@ _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; - } - g_hash_table_iter_remove(&iter); + 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) { + obj_state->os_temporary_not_available_timestamp_msec = 0; + c_list_unlink(&obj_state->os_temporary_not_available_lst); } - 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; @@ -3348,52 +3630,24 @@ _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; + const NMDedupMultiHeadEntry * head_entry; + const ObjStatesSyncFilterData sync_filter_data = { + .self = self, + .commit_type = commit_type, + }; - 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; - } 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, @@ -3412,13 +3666,9 @@ _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_*(). */ @@ -3504,9 +3754,6 @@ _l3_commit(NML3Cfg *self, NML3CfgCommitType commit_type, gboolean is_idle) nm_clear_g_source_inst(&self->priv.p->commit_on_idle_source); - if (commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY) - _l3cfg_externally_removed_objs_drop(self); - _l3cfg_update_combined_config(self, TRUE, commit_type == NM_L3_CFG_COMMIT_TYPE_REAPPLY, @@ -3792,6 +4039,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 @@ -3846,15 +4101,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_inst(&self->priv.p->obj_state_temporary_not_available_timeout_source); - 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); + 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); From aa070fb82190cda05cb77082d5f67709010ff5d4 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Wed, 25 Aug 2021 11:30:35 +0200 Subject: [PATCH 17/17] core: add NML3IPv6LL helper This helper class is supposed to encapsulate most logic about configuring IPv6 link local addresses and exposes a simpler API in order to simplify NMDevice. Currently this logic is spread out in NMDevice. Also, NML3IPv6LL directly uses NML3Cfg, thereby freeing NMDevice to care about that too much. For several reasons, NML3IPv6LL works different than NML3IPv4LL. For one, with IPv6 we need to configure the address in kernel, which does DAD for us. So, NML3IPv6LL will tell NML3Cfg to configure those addresses that it wants to probe. For IPv4, it only tells NML3Cfg to do ACD, without configuring anything yet. That is left to the caller. --- Makefile.am | 2 + src/core/meson.build | 1 + src/core/nm-core-utils.h | 2 + src/core/nm-l3-ipv6ll.c | 713 ++++++++++++++++++++++++++++++++++++ src/core/nm-l3-ipv6ll.h | 106 ++++++ src/core/nm-l3cfg.c | 2 +- src/core/nm-l3cfg.h | 1 + src/core/tests/test-l3cfg.c | 277 ++++++++++++++ 8 files changed, 1103 insertions(+), 1 deletion(-) create mode 100644 src/core/nm-l3-ipv6ll.c create mode 100644 src/core/nm-l3-ipv6ll.h diff --git a/Makefile.am b/Makefile.am index 32e88ee480..22634626d0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2382,6 +2382,8 @@ src_core_libNetworkManagerBase_la_SOURCES = \ src/core/nm-l3-config-data.h \ src/core/nm-l3-ipv4ll.c \ src/core/nm-l3-ipv4ll.h \ + src/core/nm-l3-ipv6ll.c \ + src/core/nm-l3-ipv6ll.h \ src/core/nm-l3cfg.c \ src/core/nm-l3cfg.h \ src/core/nm-ip-config.c \ diff --git a/src/core/meson.build b/src/core/meson.build index 1a3f334fd8..46b636817d 100644 --- a/src/core/meson.build +++ b/src/core/meson.build @@ -51,6 +51,7 @@ libNetworkManagerBase = static_library( 'nm-netns.c', 'nm-l3-config-data.c', 'nm-l3-ipv4ll.c', + 'nm-l3-ipv6ll.c', 'nm-l3cfg.c', 'nm-ip-config.c', 'nm-ip4-config.c', diff --git a/src/core/nm-core-utils.h b/src/core/nm-core-utils.h index 614df02e75..38f56adc08 100644 --- a/src/core/nm-core-utils.h +++ b/src/core/nm-core-utils.h @@ -298,6 +298,8 @@ typedef enum { NM_UTILS_STABLE_TYPE_RANDOM = 3, } NMUtilsStableType; +#define NM_UTILS_STABLE_TYPE_NONE ((NMUtilsStableType) -1) + NMUtilsStableType nm_utils_stable_id_parse(const char *stable_id, const char *deviceid, const char *hwaddr, diff --git a/src/core/nm-l3-ipv6ll.c b/src/core/nm-l3-ipv6ll.c new file mode 100644 index 0000000000..05149e66a6 --- /dev/null +++ b/src/core/nm-l3-ipv6ll.c @@ -0,0 +1,713 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "src/core/nm-default-daemon.h" + +#include "nm-l3-ipv6ll.h" + +#include + +#include "nm-core-utils.h" + +/*****************************************************************************/ + +/* FIXME(next): ensure that NML3IPv6LL generates the same stable privacy addresses + * as previous implementation. */ + +/*****************************************************************************/ + +NM_UTILS_LOOKUP_STR_DEFINE(nm_l3_ipv6ll_state_to_string, + NML3IPv6LLState, + NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("???"), + NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_NONE, "none"), + NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_STARTING, "starting"), + NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, + "dad-in-progress"), + NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_READY, "ready"), + NM_UTILS_LOOKUP_STR_ITEM(NM_L3_IPV6LL_STATE_DAD_FAILED, "dad-failed"), ); + +/*****************************************************************************/ + +struct _NML3IPv6LL { + NML3Cfg * l3cfg; + NML3CfgCommitTypeHandle *l3cfg_commit_handle; + NML3IPv6LLNotifyFcn notify_fcn; + gpointer user_data; + GSource * starting_on_idle_source; + GSource * wait_for_addr_source; + GSource * emit_changed_idle_source; + gulong l3cfg_signal_notify_id; + NML3IPv6LLState state; + + /* if we have cur_lladdr set, then this might cache the last + * matching NMPObject from the platform cache. This only serves + * for optimizing the lookup to the platform cache. */ + const NMPlatformIP6Address *cur_lladdr_obj; + + struct in6_addr cur_lladdr; + + /* if we have cur_lladdr and _state_has_lladdr() indicates that + * the LL address is suitable, this is a NML3ConfigData instance + * with the configuration. */ + const NML3ConfigData *l3cd; + + /* "assume" means that we first look whether there is any suitable + * IPv6 address on the device, and in that case, try to use that + * instead of generating a new one. Otherwise, we always try to + * generate a new LL address. */ + bool assume : 1; + + struct { + NMUtilsStableType stable_type; + guint32 dad_counter; + struct { + const char *ifname; + const char *network_id; + } stable_privacy; + struct { + NMUtilsIPv6IfaceId iid; + } token; + } addrgen; +}; + +/*****************************************************************************/ + +#define _NMLOG_DOMAIN LOGD_IP6 +#define _NMLOG_PREFIX_NAME "ipv6ll" +#define _NMLOG(level, ...) \ + G_STMT_START \ + { \ + nm_log((level), \ + (_NMLOG_DOMAIN), \ + NULL, \ + NULL, \ + _NMLOG_PREFIX_NAME "[" NM_HASH_OBFUSCATE_PTR_FMT \ + ",ifindex=%d]: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \ + NM_HASH_OBFUSCATE_PTR(self), \ + nm_l3cfg_get_ifindex((self)->l3cfg) _NM_UTILS_MACRO_REST(__VA_ARGS__)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +#define L3CD_TAG(self) (&(self)->notify_fcn) + +/*****************************************************************************/ + +#define _ASSERT(self) \ + G_STMT_START \ + { \ + NML3IPv6LL *const _self = (self); \ + \ + nm_assert(NM_IS_L3_IPV6LL(_self)); \ + } \ + G_STMT_END + +/*****************************************************************************/ + +static void _check(NML3IPv6LL *self); + +/*****************************************************************************/ + +NML3Cfg * +nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self) +{ + nm_assert(NM_IS_L3_IPV6LL(self)); + + return self->l3cfg; +} + +int +nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self) +{ + nm_assert(NM_IS_L3_IPV6LL(self)); + + return nm_l3cfg_get_ifindex(self->l3cfg); +} + +NMPlatform * +nm_l3_ipv6ll_get_platform(NML3IPv6LL *self) +{ + nm_assert(NM_IS_L3_IPV6LL(self)); + + return nm_l3cfg_get_platform(self->l3cfg); +} + +/*****************************************************************************/ + +static gboolean +_state_has_lladdr(NML3IPv6LLState state) +{ + return NM_IN_SET(state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY); +} + +NML3IPv6LLState +nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr) +{ + nm_assert(NM_IS_L3_IPV6LL(self)); + + NM_SET_OUT(out_lladdr, _state_has_lladdr(self->state) ? &self->cur_lladdr : NULL); + return self->state; +} + +static const NML3ConfigData * +_l3cd_config_create(int ifindex, const struct in6_addr *lladdr, NMDedupMultiIndex *multi_idx) +{ + NML3ConfigData *l3cd; + + nm_assert(ifindex > 0); + nm_assert(lladdr); + nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr)); + + l3cd = nm_l3_config_data_new(multi_idx, ifindex, NM_IP_CONFIG_SOURCE_IP6LL); + + nm_l3_config_data_add_address_6( + l3cd, + NM_PLATFORM_IP6_ADDRESS_INIT(.address = *lladdr, + .plen = 64, + .addr_source = NM_IP_CONFIG_SOURCE_IP6LL)); + + nm_l3_config_data_add_route_6( + l3cd, + NM_PLATFORM_IP6_ROUTE_INIT(.network.s6_addr16[0] = htons(0xfe80u), + .plen = 64, + .metric_any = TRUE, + .table_any = TRUE, + .rt_source = NM_IP_CONFIG_SOURCE_IP6LL)); + + return nm_l3_config_data_seal(l3cd); +} + +const NML3ConfigData * +nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self) +{ + nm_assert(NM_IS_L3_IPV6LL(self)); + + if (!_state_has_lladdr(self->state)) { + nm_assert(!self->l3cd); + return NULL; + } + + if (!self->l3cd) { + self->l3cd = _l3cd_config_create(nm_l3_ipv6ll_get_ifindex(self), + &self->cur_lladdr, + nm_l3cfg_get_multi_idx(self->l3cfg)); + } + + return self->l3cd; +} + +/*****************************************************************************/ + +static gboolean +_emit_changed_on_idle_cb(gpointer user_data) +{ + NML3IPv6LL * self = user_data; + const struct in6_addr *lladdr; + NML3IPv6LLState state; + char sbuf[INET6_ADDRSTRLEN]; + + nm_clear_g_source_inst(&self->emit_changed_idle_source); + + state = nm_l3_ipv6ll_get_state(self, &lladdr); + + _LOGT("emit changed signal (state=%s%s%s)", + nm_l3_ipv6ll_state_to_string(state), + lladdr ? ", " : "", + lladdr ? _nm_utils_inet6_ntop(lladdr, sbuf) : ""); + + self->notify_fcn(self, state, lladdr, self->user_data); + + return G_SOURCE_CONTINUE; +} + +/*****************************************************************************/ + +static gboolean +_generate_new_address(NML3IPv6LL *self, struct in6_addr *out_lladdr) +{ + struct in6_addr lladdr; + + memset(&lladdr, 0, sizeof(struct in6_addr)); + lladdr.s6_addr16[0] = htons(0xfe80u); + + if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) { + if (self->addrgen.dad_counter > 0) + return FALSE; + self->addrgen.dad_counter++; + nm_utils_ipv6_addr_set_interface_identifier(&lladdr, &self->addrgen.token.iid); + } else { + /* RFC7217 says we MUST limit the number of retries, and it SHOULD try + * at least IDGEN_RETRIES times (that is, 3 times). + * + * 3 times seems really low. Instead, let's try 6 times. */ + G_STATIC_ASSERT(NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES == 3); + if (self->addrgen.dad_counter >= NM_STABLE_PRIVACY_RFC7217_IDGEN_RETRIES + 3) + return FALSE; + + nm_utils_ipv6_addr_set_stable_privacy(self->addrgen.stable_type, + &lladdr, + self->addrgen.stable_privacy.ifname, + self->addrgen.stable_privacy.network_id, + self->addrgen.dad_counter++); + } + + *out_lladdr = lladdr; + return TRUE; +} + +/*****************************************************************************/ + +static gboolean +_pladdr_is_ll_failed(const NMPlatformIP6Address *addr) +{ + nm_assert(addr); + nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address)); + + return NM_FLAGS_ANY(addr->n_ifa_flags, IFA_F_DADFAILED | IFA_F_DEPRECATED); +} + +static gboolean +_pladdr_is_ll_tentative(const NMPlatformIP6Address *addr) +{ + nm_assert(addr); + nm_assert(IN6_IS_ADDR_LINKLOCAL(&addr->address)); + nm_assert(!_pladdr_is_ll_failed(addr)); + + return NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_TENTATIVE) + && !NM_FLAGS_HAS(addr->n_ifa_flags, IFA_F_OPTIMISTIC); +} + +static const NMPlatformIP6Address * +_pladdr_find_ll(NML3IPv6LL *self, gboolean *out_cur_addr_failed) +{ + NMDedupMultiIter iter; + NMPLookup lookup; + const NMPlatformIP6Address *pladdr1 = NULL; + const NMPObject * obj; + const NMPlatformIP6Address *pladdr_ready = NULL; + const NMPlatformIP6Address *pladdr_tentative = NULL; + gboolean cur_addr_check = TRUE; + gboolean cur_addr_failed = FALSE; + gboolean pladdr1_looked_up = FALSE; + + nm_assert(!self->cur_lladdr_obj + || IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &self->cur_lladdr_obj->address)); + + *out_cur_addr_failed = FALSE; + + if (self->state == NM_L3_IPV6LL_STATE_READY && self->cur_lladdr_obj) { + nm_assert(!_pladdr_is_ll_tentative(self->cur_lladdr_obj)); + pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS( + nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self), + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + NMP_OBJECT_UP_CAST(self->cur_lladdr_obj))); + if (self->cur_lladdr_obj == pladdr1) { + /* Fast-path. We are ready and the cur_lladdr_obj is still in the cache. We + * got the result with a dictionary lookup without need to iterate over + * all addresses. */ + return self->cur_lladdr_obj; + } + pladdr1_looked_up = TRUE; + } + + if (!self->assume) { + /* We don't accept any suitable LL address, only he one we are waiting for. + * Let's do a dictionary lookup. */ + + if (IN6_IS_ADDR_LINKLOCAL(&self->cur_lladdr)) { + if (!pladdr1_looked_up) { + NMPObject needle; + + nmp_object_stackinit_id_ip6_address(&needle, + nm_l3_ipv6ll_get_ifindex(self), + &self->cur_lladdr); + pladdr1 = NMP_OBJECT_CAST_IP6_ADDRESS( + nm_platform_lookup_obj(nm_l3_ipv6ll_get_platform(self), + NMP_CACHE_ID_TYPE_OBJECT_TYPE, + &needle)); + } + if (pladdr1) { + if (!_pladdr_is_ll_failed(pladdr1)) + return pladdr1; + *out_cur_addr_failed = TRUE; + } + } else + nm_assert(!pladdr1_looked_up); + + return NULL; + } + + if (!NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY)) + cur_addr_check = FALSE; + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, nm_l3_ipv6ll_get_ifindex(self)); + + nm_platform_iter_obj_for_each (&iter, nm_l3_ipv6ll_get_platform(self), &lookup, &obj) { + const NMPlatformIP6Address *pladdr = NMP_OBJECT_CAST_IP6_ADDRESS(obj); + + if (!IN6_IS_ADDR_LINKLOCAL(&pladdr->address)) + continue; + + if (_pladdr_is_ll_failed(pladdr)) { + if (cur_addr_check && IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address)) { + /* "pladdr" is the address we are currently doing DAD for. But it failed. + * We need to recognize and report to the caller, to stop waiting for this + * address. */ + cur_addr_failed = TRUE; + cur_addr_check = FALSE; + } + continue; + } + + if (_pladdr_is_ll_tentative(pladdr)) { + if (!pladdr_tentative) + pladdr_tentative = pladdr; + else if (pladdr == self->cur_lladdr_obj) + pladdr_tentative = pladdr; + else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address)) + pladdr_tentative = pladdr; + continue; + } + + if (pladdr == self->cur_lladdr_obj) { + /* it doesn't get any better. We have our best address. */ + return pladdr; + } + if (!pladdr_ready) + pladdr_ready = pladdr; + else if (IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, &pladdr->address)) + pladdr_ready = pladdr; + } + + *out_cur_addr_failed = cur_addr_failed; + return pladdr_ready ?: pladdr_tentative; +} + +/*****************************************************************************/ + +static void +_lladdr_handle_changed(NML3IPv6LL *self) +{ + const NML3ConfigData *l3cd; + gboolean changed = FALSE; + + /* We register the l3cd with l3cfg to start DAD. That is different from + * NML3IPv4LL, where we use NM_L3_CONFIG_MERGE_FLAGS_ONLY_FOR_ACD. The difference + * is that for IPv6 we let kernel do DAD, so we need to actually configure the + * address. For IPv4, we can run ACD without configuring anything in kernel, + * and let the user decide how to proceed. + * + * Also in this case, we use the most graceful commit-type (NM_L3_CFG_COMMIT_TYPE_ASSUME), + * but for that to work, we also need NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE flag. */ + + l3cd = nm_l3_ipv6ll_get_l3cd(self); + + if (l3cd) { + if (nm_l3cfg_add_config(self->l3cfg, + L3CD_TAG(self), + TRUE, + l3cd, + NM_L3CFG_CONFIG_PRIORITY_IPV6LL, + 0, + 0, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP4, + NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6, + 0, + 0, + NM_L3_ACD_DEFEND_TYPE_ALWAYS, + 0, + NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE, + NM_L3_CONFIG_MERGE_FLAGS_NONE)) + changed = TRUE; + } else { + if (nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self))) + changed = TRUE; + } + + self->l3cfg_commit_handle = nm_l3cfg_commit_type_register(self->l3cfg, + l3cd ? NM_L3_CFG_COMMIT_TYPE_ASSUME + : NM_L3_CFG_COMMIT_TYPE_NONE, + self->l3cfg_commit_handle); + + if (changed) + nm_l3cfg_commit_on_idle_schedule(self->l3cfg); + + if (!self->emit_changed_idle_source) { + _LOGT("schedule changed signal on idle"); + self->emit_changed_idle_source = nm_g_idle_add_source(_emit_changed_on_idle_cb, self); + } +} + +/*****************************************************************************/ + +static gboolean +_set_cur_lladdr(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr) +{ + gboolean changed = FALSE; + + if (lladdr) { + nm_assert(IN6_IS_ADDR_LINKLOCAL(lladdr)); + if (!IN6_ARE_ADDR_EQUAL(&self->cur_lladdr, lladdr)) { + self->cur_lladdr = *lladdr; + nm_clear_l3cd(&self->l3cd); + changed = TRUE; + } + } else { + if (!nm_ip_addr_is_null(AF_INET6, &self->cur_lladdr)) { + nm_clear_l3cd(&self->l3cd); + self->cur_lladdr = nm_ip_addr_zero.addr6; + changed = TRUE; + } + nm_assert(!self->l3cd); + nm_assert(!_state_has_lladdr(state)); + } + + if (self->state != state) { + if (!_state_has_lladdr(state)) + nm_clear_l3cd(&self->l3cd); + self->state = state; + changed = TRUE; + } + + return changed; +} + +static gboolean +_set_cur_lladdr_obj(NML3IPv6LL *self, NML3IPv6LLState state, const NMPlatformIP6Address *lladdr_obj) +{ + nm_assert(lladdr_obj); + nm_assert(_state_has_lladdr(state)); + + nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, lladdr_obj); + return _set_cur_lladdr(self, state, &lladdr_obj->address); +} + +static gboolean +_set_cur_lladdr_bin(NML3IPv6LL *self, NML3IPv6LLState state, const struct in6_addr *lladdr) +{ + nmp_object_ref_set_up_cast(&self->cur_lladdr_obj, NULL); + return _set_cur_lladdr(self, state, lladdr); +} + +static gboolean +_wait_for_addr_timeout_cb(gpointer user_data) +{ + NML3IPv6LL *self = user_data; + + nm_clear_g_source_inst(&self->wait_for_addr_source); + + nm_assert( + NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_FAILED, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS)); + + _check(self); + + return G_SOURCE_CONTINUE; +} + +static void +_check(NML3IPv6LL *self) +{ + const NMPlatformIP6Address *pladdr; + char sbuf[INET6_ADDRSTRLEN]; + gboolean cur_addr_failed; + struct in6_addr lladdr; + + pladdr = _pladdr_find_ll(self, &cur_addr_failed); + + if (pladdr) { + nm_clear_g_source_inst(&self->wait_for_addr_source); + + if (_pladdr_is_ll_tentative(pladdr)) { + if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, pladdr)) { + _LOGT("changed: waiting for address %s to complete DAD", + _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf)); + _lladdr_handle_changed(self); + } + return; + } + + if (_set_cur_lladdr_obj(self, NM_L3_IPV6LL_STATE_READY, pladdr)) { + _LOGT("changed: address %s is ready", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf)); + _lladdr_handle_changed(self); + } + return; + } + + if (self->cur_lladdr_obj || cur_addr_failed) { + /* we were doing DAD, but the address is no longer a suitable candidate. + * Prematurely abort DAD to generate a new address below. */ + nm_assert( + NM_IN_SET(self->state, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, NM_L3_IPV6LL_STATE_READY)); + if (self->state == NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS) + _LOGT("changed: address %s did not complete DAD", + _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf)); + else { + _LOGT("changed: address %s is gone", _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf)); + } + + /* reset the state here, so that we are sure that the following + * _set_cur_lladdr_bin() calls (below) will notice the change + * and trigger a _lladdr_handle_changed(). */ + _set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_STARTING, NULL); + nm_clear_g_source_inst(&self->wait_for_addr_source); + } else if (self->wait_for_addr_source) { + /* we are waiting. Nothing to do for now. */ + return; + } + + if (!_generate_new_address(self, &lladdr)) { + /* our DAD counter expired. We reset it, and start a timer to retry + * and recover. */ + self->addrgen.dad_counter = 0; + self->wait_for_addr_source = + nm_g_timeout_add_source(10000, _wait_for_addr_timeout_cb, self); + if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_FAILED, NULL)) { + _LOGW("changed: no IPv6 link local address to retry after Duplicate Address Detection " + "failures (back off)"); + _lladdr_handle_changed(self); + } + return; + } + + /* we give NML3Cfg 2 seconds to configure the address on the interface. We + * thus very soon expect to see this address configured (and kernel started DAD). + * If that does not happen within timeout, we assume that this address failed DAD. */ + self->wait_for_addr_source = nm_g_timeout_add_source(2000, _wait_for_addr_timeout_cb, self); + if (_set_cur_lladdr_bin(self, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, &lladdr)) { + _LOGT("changed: starting DAD for address %s", + _nm_utils_inet6_ntop(&self->cur_lladdr, sbuf)); + _lladdr_handle_changed(self); + } + return; +} + +/*****************************************************************************/ + +static void +_l3cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NML3IPv6LL *self) +{ + if (notify_data->notify_type == NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE) { + if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags, + nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS))) + _check(self); + return; + } +} + +/*****************************************************************************/ + +static gboolean +_starting_on_idle_cb(gpointer user_data) +{ + NML3IPv6LL *self = user_data; + + nm_clear_g_source_inst(&self->starting_on_idle_source); + + self->l3cfg_signal_notify_id = + g_signal_connect(self->l3cfg, NM_L3CFG_SIGNAL_NOTIFY, G_CALLBACK(_l3cfg_notify_cb), self); + + _check(self); + + return G_SOURCE_CONTINUE; +} + +/*****************************************************************************/ + +NML3IPv6LL * +_nm_l3_ipv6ll_new(NML3Cfg * l3cfg, + gboolean assume, + NMUtilsStableType stable_type, + const char * ifname, + const char * network_id, + const NMUtilsIPv6IfaceId *token_iid, + NML3IPv6LLNotifyFcn notify_fcn, + gpointer user_data) +{ + NML3IPv6LL *self; + + g_return_val_if_fail(NM_IS_L3CFG(l3cfg), NULL); + g_return_val_if_fail(notify_fcn, NULL); + g_return_val_if_fail( + (stable_type == NM_UTILS_STABLE_TYPE_NONE && !ifname && !network_id && token_iid) + || (stable_type != NM_UTILS_STABLE_TYPE_NONE && ifname && network_id && !token_iid), + NULL); + + self = g_slice_new(NML3IPv6LL); + *self = (NML3IPv6LL){ + .l3cfg = g_object_ref(l3cfg), + .notify_fcn = notify_fcn, + .user_data = user_data, + .state = NM_L3_IPV6LL_STATE_STARTING, + .starting_on_idle_source = nm_g_idle_add_source(_starting_on_idle_cb, self), + .l3cfg_signal_notify_id = 0, + .cur_lladdr_obj = NULL, + .cur_lladdr = IN6ADDR_ANY_INIT, + .assume = assume, + .addrgen = + { + .stable_type = stable_type, + .dad_counter = 0, + }, + }; + + if (self->addrgen.stable_type == NM_UTILS_STABLE_TYPE_NONE) { + char sbuf_token[sizeof(self->addrgen.token.iid) * 3]; + + self->addrgen.token.iid = *token_iid; + _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT ", ifindex=%d, token=%s%s", + NM_HASH_OBFUSCATE_PTR(l3cfg), + nm_l3cfg_get_ifindex(l3cfg), + nm_utils_bin2hexstr_full(&self->addrgen.token.iid, + sizeof(self->addrgen.token.iid), + ':', + FALSE, + sbuf_token), + self->assume ? ", assume" : ""); + } else { + self->addrgen.stable_privacy.ifname = g_strdup(ifname); + self->addrgen.stable_privacy.network_id = g_strdup(network_id); + _LOGT("created: l3cfg=" NM_HASH_OBFUSCATE_PTR_FMT + ", ifindex=%d, stable-type=%u, ifname=%s, network_id=%s%s", + NM_HASH_OBFUSCATE_PTR(l3cfg), + nm_l3cfg_get_ifindex(l3cfg), + (unsigned) self->addrgen.stable_type, + self->addrgen.stable_privacy.ifname, + self->addrgen.stable_privacy.network_id, + self->assume ? ", assume" : ""); + } + + return self; +} + +void +nm_l3_ipv6ll_destroy(NML3IPv6LL *self) +{ + if (!self) + return; + + _ASSERT(self); + + _LOGT("finalize"); + + nm_l3cfg_commit_type_unregister(self->l3cfg, g_steal_pointer(&self->l3cfg_commit_handle)); + + nm_l3cfg_remove_config_all(self->l3cfg, L3CD_TAG(self)); + + nm_clear_g_source_inst(&self->starting_on_idle_source); + nm_clear_g_source_inst(&self->wait_for_addr_source); + nm_clear_g_source_inst(&self->emit_changed_idle_source); + nm_clear_g_signal_handler(self->l3cfg, &self->l3cfg_signal_notify_id); + + g_clear_object(&self->l3cfg); + + nm_clear_l3cd(&self->l3cd); + + nm_clear_nmp_object_up_cast(&self->cur_lladdr_obj); + + if (self->addrgen.stable_type != NM_UTILS_STABLE_TYPE_NONE) { + g_free((char *) self->addrgen.stable_privacy.ifname); + g_free((char *) self->addrgen.stable_privacy.network_id); + } + + nm_g_slice_free(self); +} diff --git a/src/core/nm-l3-ipv6ll.h b/src/core/nm-l3-ipv6ll.h new file mode 100644 index 0000000000..8125b5d06a --- /dev/null +++ b/src/core/nm-l3-ipv6ll.h @@ -0,0 +1,106 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#ifndef __NM_L3_IPV6LL_H__ +#define __NM_L3_IPV6LL_H__ + +#include "nm-l3cfg.h" +#include "nm-core-utils.h" + +/*****************************************************************************/ + +typedef struct _NML3IPv6LL NML3IPv6LL; + +typedef enum _nm_packed { + + /* NONE is not actually used by NML3IPv6LL. This is a bogus placeholder + * state for external users. */ + NM_L3_IPV6LL_STATE_NONE, + + NM_L3_IPV6LL_STATE_STARTING, + NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS, + NM_L3_IPV6LL_STATE_READY, + NM_L3_IPV6LL_STATE_DAD_FAILED, +} NML3IPv6LLState; + +const char *nm_l3_ipv6ll_state_to_string(NML3IPv6LLState state); + +typedef void (*NML3IPv6LLNotifyFcn)(NML3IPv6LL * ipv6ll, + NML3IPv6LLState state, + const struct in6_addr *lladdr, + gpointer user_data); + +static inline gboolean +NM_IS_L3_IPV6LL(const NML3IPv6LL *self) +{ + nm_assert(!self || (NM_IS_L3CFG(*((NML3Cfg **) self)))); + return !!self; +} + +NML3IPv6LL *_nm_l3_ipv6ll_new(NML3Cfg * l3cfg, + gboolean assume, + NMUtilsStableType stable_type, + const char * ifname, + const char * network_id, + const NMUtilsIPv6IfaceId *token_iid, + NML3IPv6LLNotifyFcn notify_fcn, + gpointer user_data); + +static inline NML3IPv6LL * +nm_l3_ipv6ll_new_stable_privacy(NML3Cfg * l3cfg, + gboolean assume, + NMUtilsStableType stable_type, + const char * ifname, + const char * network_id, + NML3IPv6LLNotifyFcn notify_fcn, + gpointer user_data) +{ + nm_assert(stable_type != NM_UTILS_STABLE_TYPE_NONE); + return _nm_l3_ipv6ll_new(l3cfg, + assume, + stable_type, + ifname, + network_id, + NULL, + notify_fcn, + user_data); +} + +static inline NML3IPv6LL * +nm_l3_ipv6ll_new_token(NML3Cfg * l3cfg, + gboolean assume, + const NMUtilsIPv6IfaceId *token_iid, + NML3IPv6LLNotifyFcn notify_fcn, + gpointer user_data) +{ + return _nm_l3_ipv6ll_new(l3cfg, + assume, + NM_UTILS_STABLE_TYPE_NONE, + NULL, + NULL, + token_iid, + notify_fcn, + user_data); +} + +void nm_l3_ipv6ll_destroy(NML3IPv6LL *self); + +NM_AUTO_DEFINE_FCN0(NML3IPv6LL *, _nm_auto_destroy_l3ipv6ll, nm_l3_ipv6ll_destroy); +#define nm_auto_destroy_l3ipv6ll nm_auto(_nm_auto_destroy_l3ipv6ll) + +/*****************************************************************************/ + +NML3Cfg *nm_l3_ipv6ll_get_l3cfg(NML3IPv6LL *self); + +int nm_l3_ipv6ll_get_ifindex(NML3IPv6LL *self); + +NMPlatform *nm_l3_ipv6ll_get_platform(NML3IPv6LL *self); + +/*****************************************************************************/ + +NML3IPv6LLState nm_l3_ipv6ll_get_state(NML3IPv6LL *self, const struct in6_addr **out_lladdr); + +const NML3ConfigData *nm_l3_ipv6ll_get_l3cd(NML3IPv6LL *self); + +/*****************************************************************************/ + +#endif /* __NM_L3_IPV6LL_H__ */ diff --git a/src/core/nm-l3cfg.c b/src/core/nm-l3cfg.c index 9baef4e359..1b9eadea32 100644 --- a/src/core/nm-l3cfg.c +++ b/src/core/nm-l3cfg.c @@ -3806,7 +3806,7 @@ nm_l3cfg_commit_type_get(NML3Cfg *self) * 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 * diff --git a/src/core/nm-l3cfg.h b/src/core/nm-l3cfg.h index 9190e1f398..193ff72a7c 100644 --- a/src/core/nm-l3cfg.h +++ b/src/core/nm-l3cfg.h @@ -7,6 +7,7 @@ #include "nm-l3-config-data.h" #define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0 +#define NM_L3CFG_CONFIG_PRIORITY_IPV6LL 0 #define NM_ACD_TIMEOUT_RFC5227_MSEC 9000u #define NM_TYPE_L3CFG (nm_l3cfg_get_type()) diff --git a/src/core/tests/test-l3cfg.c b/src/core/tests/test-l3cfg.c index 09efb92465..e4c6beb4b9 100644 --- a/src/core/tests/test-l3cfg.c +++ b/src/core/tests/test-l3cfg.c @@ -2,8 +2,11 @@ #include "src/core/nm-default-daemon.h" +#include + #include "nm-l3cfg.h" #include "nm-l3-ipv4ll.h" +#include "nm-l3-ipv6ll.h" #include "nm-netns.h" #include "libnm-platform/nm-platform.h" @@ -780,6 +783,276 @@ test_l3_ipv4ll(gconstpointer test_data) /*****************************************************************************/ +#define _LLADDR_TEST1 "fe80::dd5a:8a44:48bc:3ad" +#define _LLADDR_TEST2 "fe80::878b:938e:46f9:4807" + +typedef struct { + const TestFixture1 *f; + NML3Cfg * l3cfg0; + NML3IPv6LL * l3ipv6ll; + int step; + int ipv6ll_callback_step; + bool steps_done : 1; + const NMPObject * lladdr0; +} TestL3IPv6LLData; + +static const NMPlatformIP6Address * +_test_l3_ipv6ll_find_lladdr(TestL3IPv6LLData *tdata, int ifindex) +{ + const NMPlatformIP6Address *found = NULL; + NMDedupMultiIter iter; + const NMPObject * obj; + NMPLookup lookup; + + g_assert(tdata); + + nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex); + nm_platform_iter_obj_for_each (&iter, tdata->f->platform, &lookup, &obj) { + const NMPlatformIP6Address *a = NMP_OBJECT_CAST_IP6_ADDRESS(obj); + + if (!IN6_IS_ADDR_LINKLOCAL(&a->address)) + continue; + + if (!found) + found = a; + else + g_assert_not_reached(); + } + + return found; +} + +static const NMPObject * +_test_l3_ipv6ll_find_lladdr_wait(TestL3IPv6LLData *tdata, int ifindex) +{ + const NMPObject *obj = NULL; + + nmtst_main_context_iterate_until_assert(NULL, 3000, ({ + const NMPlatformIP6Address *a; + + a = _test_l3_ipv6ll_find_lladdr(tdata, ifindex); + if (a + && !NM_FLAGS_HAS(a->n_ifa_flags, + IFA_F_TENTATIVE)) + obj = NMP_OBJECT_UP_CAST(a); + obj; + })); + + return obj; +} + +static const NMPlatformIP6Address * +_test_l3_ipv6ll_find_inet6(TestL3IPv6LLData *tdata, const struct in6_addr *addr) +{ + const NMPlatformIP6Address *a; + + a = nmtstp_platform_ip6_address_find(nm_l3cfg_get_platform(tdata->l3cfg0), + nmtst_get_rand_bool() ? 0 : tdata->f->ifindex0, + addr); + if (a) { + g_assert_cmpint(a->ifindex, ==, tdata->f->ifindex0); + g_assert_cmpmem(addr, sizeof(*addr), &a->address, sizeof(a->address)); + } + + g_assert(a + == nm_platform_ip6_address_get(nm_l3cfg_get_platform(tdata->l3cfg0), + tdata->f->ifindex0, + addr)); + + return a; +} + +static void +_test_l3_ipv6ll_signal_notify(NML3Cfg * l3cfg, + const NML3ConfigNotifyData *notify_data, + TestL3IPv6LLData * tdata) +{ + g_assert_cmpint(tdata->step, >=, 1); + g_assert_cmpint(tdata->step, <=, 2); +} + +static void +_test_l3_ipv6ll_callback_changed(NML3IPv6LL * ipv6ll, + NML3IPv6LLState state, + const struct in6_addr *lladdr, + gpointer user_data) +{ + TestL3IPv6LLData * tdata = user_data; + int step = tdata->ipv6ll_callback_step++; + const NMPlatformIP6Address *a1; + + g_assert_cmpint(tdata->step, ==, 1); + g_assert(!tdata->steps_done); + + switch (step) { + case 0: + if (NM_IN_SET(tdata->f->test_idx, 1, 2, 4)) { + g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS); + g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1); + } else if (NM_IN_SET(tdata->f->test_idx, 3)) { + g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); + g_assert( + IN6_ARE_ADDR_EQUAL(lladdr, &NMP_OBJECT_CAST_IP6_ADDRESS(tdata->lladdr0)->address)); + tdata->steps_done = TRUE; + } else + g_assert_not_reached(); + break; + case 1: + if (NM_IN_SET(tdata->f->test_idx, 1, 2)) { + g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); + g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST1); + a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr); + g_assert(a1); + g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE)); + tdata->steps_done = TRUE; + } else if (NM_IN_SET(tdata->f->test_idx, 4)) { + g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS); + g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2); + } else + g_assert_not_reached(); + break; + case 2: + if (NM_IN_SET(tdata->f->test_idx, 4)) { + g_assert_cmpint(state, ==, NM_L3_IPV6LL_STATE_READY); + g_assert_cmpstr(nmtst_inet6_to_string(lladdr), ==, _LLADDR_TEST2); + a1 = _test_l3_ipv6ll_find_inet6(tdata, lladdr); + g_assert(a1); + g_assert(!NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE)); + tdata->steps_done = TRUE; + } else + g_assert_not_reached(); + break; + default: + g_assert_not_reached(); + } +} + +static void +test_l3_ipv6ll(gconstpointer test_data) +{ + NMTST_UTILS_HOST_ID_CONTEXT("l3-ipv6ll"); + const int TEST_IDX = GPOINTER_TO_INT(test_data); + nm_auto(_test_fixture_1_teardown) TestFixture1 test_fixture = {}; + gs_unref_object NML3Cfg *l3cfg0 = NULL; + TestL3IPv6LLData tdata_stack = { + .step = 0, + .steps_done = FALSE, + }; + TestL3IPv6LLData *const tdata = &tdata_stack; + char sbuf1[sizeof(_nm_utils_to_string_buffer)]; + int r; + + _LOGD("test start (/l3-ipv6ll/%d)", TEST_IDX); + + if (nmtst_test_quick()) { + gs_free char *msg = + g_strdup_printf("Skipping test: don't run long running test %s (NMTST_DEBUG=slow)\n", + g_get_prgname() ?: "test-l3-ipv6ll"); + + g_test_skip(msg); + return; + } + + tdata->f = _test_fixture_1_setup(&test_fixture, TEST_IDX); + + if (NM_IN_SET(tdata->f->test_idx, 4)) { + _LOGD("add conflicting IPv6LL on other interface..."); + r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, FALSE); + g_assert_cmpint(r, >=, 0); + + r = nm_platform_link_set_inet6_addr_gen_mode(tdata->f->platform, + tdata->f->ifindex1, + NM_IN6_ADDR_GEN_MODE_NONE); + g_assert_cmpint(r, >=, 0); + + r = nm_platform_link_change_flags(tdata->f->platform, tdata->f->ifindex1, IFF_UP, TRUE); + g_assert_cmpint(r, >=, 0); + + nmtstp_ip6_address_add(tdata->f->platform, + -1, + tdata->f->ifindex1, + *nmtst_inet6_from_string(_LLADDR_TEST1), + 64, + in6addr_any, + NM_PLATFORM_LIFETIME_PERMANENT, + NM_PLATFORM_LIFETIME_PERMANENT, + 0); + + _LOGD("wait for IPv6 LL address..."); + tdata->lladdr0 = + nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex1)); + } else if (NM_IN_SET(tdata->f->test_idx, 2, 3)) { + _LOGD("wait for IPv6 LL address..."); + tdata->lladdr0 = + nmp_object_ref(_test_l3_ipv6ll_find_lladdr_wait(tdata, tdata->f->ifindex0)); + } + + if (tdata->lladdr0) { + _LOGD("got IPv6 LL address %s", + nmp_object_to_string(tdata->lladdr0, + NMP_OBJECT_TO_STRING_PUBLIC, + sbuf1, + sizeof(sbuf1))); + } + + l3cfg0 = _netns_access_l3cfg(tdata->f->netns, tdata->f->ifindex0); + tdata->l3cfg0 = l3cfg0; + + g_signal_connect(tdata->l3cfg0, + NM_L3CFG_SIGNAL_NOTIFY, + G_CALLBACK(_test_l3_ipv6ll_signal_notify), + tdata); + + tdata->l3ipv6ll = nm_l3_ipv6ll_new_stable_privacy(tdata->l3cfg0, + NM_IN_SET(tdata->f->test_idx, 3), + NM_UTILS_STABLE_TYPE_UUID, + tdata->f->ifname0, + "b6a5b934-c649-43dc-a524-3dfdb74f9419", + _test_l3_ipv6ll_callback_changed, + tdata); + + g_assert(nm_l3_ipv6ll_get_l3cfg(tdata->l3ipv6ll) == tdata->l3cfg0); + g_assert_cmpint(nm_l3_ipv6ll_get_ifindex(tdata->l3ipv6ll), ==, tdata->f->ifindex0); + + tdata->step = 1; + nmtst_main_context_iterate_until_assert(NULL, 7000, tdata->steps_done); + + g_assert_cmpint(tdata->step, ==, 1); + if (NM_IN_SET(tdata->f->test_idx, 3)) + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1); + else if (NM_IN_SET(tdata->f->test_idx, 4)) + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3); + else + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2); + g_assert(tdata->steps_done); + + tdata->step = 2; + nmtst_main_context_iterate_until(NULL, nmtst_get_rand_uint32() % 1000, FALSE); + + g_assert_cmpint(tdata->step, ==, 2); + if (NM_IN_SET(tdata->f->test_idx, 3)) + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 1); + else if (NM_IN_SET(tdata->f->test_idx, 4)) + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 3); + else + g_assert_cmpint(tdata->ipv6ll_callback_step, ==, 2); + g_assert(tdata->steps_done); + g_assert(tdata->steps_done); + + tdata->step = 0; + tdata->steps_done = FALSE; + + g_signal_handlers_disconnect_by_func(tdata->l3cfg0, + G_CALLBACK(_test_l3_ipv6ll_signal_notify), + tdata); + + nm_l3_ipv6ll_destroy(tdata->l3ipv6ll); + + nm_clear_nmp_object(&tdata->lladdr0); +} + +/*****************************************************************************/ + NMTstpSetupFunc const _nmtstp_setup_platform_func = nm_linux_platform_setup; void @@ -797,4 +1070,8 @@ _nmtstp_setup_tests(void) g_test_add_data_func("/l3cfg/4", GINT_TO_POINTER(4), test_l3cfg); g_test_add_data_func("/l3-ipv4ll/1", GINT_TO_POINTER(1), test_l3_ipv4ll); g_test_add_data_func("/l3-ipv4ll/2", GINT_TO_POINTER(2), test_l3_ipv4ll); + g_test_add_data_func("/l3-ipv6ll/1", GINT_TO_POINTER(1), test_l3_ipv6ll); + g_test_add_data_func("/l3-ipv6ll/2", GINT_TO_POINTER(2), test_l3_ipv6ll); + g_test_add_data_func("/l3-ipv6ll/3", GINT_TO_POINTER(3), test_l3_ipv6ll); + g_test_add_data_func("/l3-ipv6ll/4", GINT_TO_POINTER(4), test_l3_ipv6ll); }