From 824f2f26db465c67339260e10a728ef6f25a4c7f Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Sat, 13 Aug 2022 02:37:18 +0200 Subject: [PATCH] iwd: Work around timing when new 802.1x connection activated Try work around the issue documented by Emil Velikov in https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1264 When we mirror an 802.1x connection to an IWD config file and there's an AP in range with matching SSID, that connection should become available for activation. In IWD terms when an 802.1x network becomes a Known Network, it can be connected to using the .Connect D-Bus method. However there's a delay between writing the IWD config file and receiving the InterfaceAdded event for the Known Network so we don't immediately find out that the network can now be used. If an NM client creates a new connection for an 802.1x AP and tries to activate it quickly enough, NMDeviceIWD will not allow it to because it won't know the network is known yet. To work around this, we save the SSIDs of 802.1x connections we recently mirrored to IWD config files, for an arbitrary 2 seconds period, and we treat them as Known Networks in that period since in theory activations should succeed. The alternative proposed in the !1264 is to drop NMDeviceIWD checks that there's a Known Network for the 802.1x connection being activated since IWD will eventually perform the same checks and IWD is the ultimate authority on whether the profile is IWD-connectable. --- src/core/devices/wifi/nm-device-iwd.c | 11 +++- src/core/devices/wifi/nm-iwd-manager.c | 82 ++++++++++++++++++++++++++ src/core/devices/wifi/nm-iwd-manager.h | 2 + 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c index 73232e341..300ec7c94 100644 --- a/src/core/devices/wifi/nm-device-iwd.c +++ b/src/core/devices/wifi/nm-device-iwd.c @@ -799,7 +799,8 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * * thus are Known Networks. */ if (security == NM_IWD_NETWORK_SECURITY_8021X) { - if (!is_connection_known_network(connection)) { + if (!is_connection_known_network(connection) + && !nm_iwd_manager_is_recently_mirrored(nm_iwd_manager_get(), ssid)) { nm_utils_error_set_literal(error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, "802.1x connections must have IWD provisioning files"); @@ -932,7 +933,9 @@ check_connection_available(NMDevice *device, */ if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) && security == NM_IWD_NETWORK_SECURITY_8021X) { - if (!is_ap_known_network(ap)) { + if (!is_ap_known_network(ap) + && !nm_iwd_manager_is_recently_mirrored(nm_iwd_manager_get(), + nm_setting_wireless_get_ssid(s_wifi))) { nm_utils_error_set_literal( error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY, @@ -2327,7 +2330,9 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) * fail, for other combinations we will let the Connect call fail * or ask us for any missing secrets through the Agent. */ - if (nm_connection_get_setting_802_1x(connection) && !is_ap_known_network(ap)) { + if (nm_connection_get_setting_802_1x(connection) && !is_ap_known_network(ap) + && !nm_iwd_manager_is_recently_mirrored(nm_iwd_manager_get(), + nm_setting_wireless_get_ssid(s_wireless))) { _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) access point '%s' has 802.1x security but is not configured " "in IWD.", diff --git a/src/core/devices/wifi/nm-iwd-manager.c b/src/core/devices/wifi/nm-iwd-manager.c index 2e0d51e5d..ec111329a 100644 --- a/src/core/devices/wifi/nm-iwd-manager.c +++ b/src/core/devices/wifi/nm-iwd-manager.c @@ -46,6 +46,11 @@ typedef struct { const KnownNetworkId *id; } KnownNetworkData; +typedef struct { + GBytes *ssid; + gint64 timestamp; +} RecentlyMirroredData; + typedef struct { NMManager *manager; NMSettings *settings; @@ -62,6 +67,7 @@ typedef struct { GHashTable *p2p_devices; NMIwdWfdInfo wfd_info; guint wfd_use_count; + GSList *recently_mirrored; } NMIwdManagerPrivate; struct _NMIwdManager { @@ -353,6 +359,70 @@ register_agent(NMIwdManager *self) /*****************************************************************************/ +static void +recently_mirrored_data_free(void *data) +{ + RecentlyMirroredData *rmd = data; + + g_bytes_unref(rmd->ssid); + g_free(rmd); +} + +/* When we mirror an 802.1x connection to an IWD config file, and there's an + * AP in range with matching SSID, that connection should become available + * for activation. In IWD terms when an 802.1x network becomes a Known + * Network, it can be connected to using the .Connect D-Bus method. + * + * However there's a delay between writing the IWD config file and receiving + * the InterfaceAdded event for the Known Network so we don't immediately + * find out that the network can now be used. If an NM client creates a + * new connection for an 802.1x AP and tries to activate it immediately, + * NMDeviceIWD will not allow it to because it doesn't know the network is + * known yet. To work around this, we save the SSIDs of 802.1x connections + * we recently mirrored to IWD config files, for 2 seconds, and we treat + * them as Known Networks in that period since in theory activations should + * succeed. + */ +bool +nm_iwd_manager_is_recently_mirrored(NMIwdManager *self, const GBytes *ssid) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + gint64 now = nm_utils_get_monotonic_timestamp_nsec(); + GSList *iter; + RecentlyMirroredData *rmd; + + /* Drop entries older than 2 seconds */ + while (priv->recently_mirrored) { + rmd = priv->recently_mirrored->data; + if (now < rmd->timestamp + 2000000000) + break; + + priv->recently_mirrored = g_slist_remove(priv->recently_mirrored, rmd); + recently_mirrored_data_free(rmd); + } + + for (iter = priv->recently_mirrored; iter; iter = iter->next) { + rmd = iter->data; + if (g_bytes_equal(ssid, rmd->ssid)) + return TRUE; + } + + return FALSE; +} + +static void +save_mirrored(NMIwdManager *self, GBytes *ssid) +{ + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + RecentlyMirroredData *rmd = g_malloc(sizeof(RecentlyMirroredData)); + + rmd->ssid = g_bytes_ref(ssid); + rmd->timestamp = nm_utils_get_monotonic_timestamp_nsec(); + priv->recently_mirrored = g_slist_append(priv->recently_mirrored, rmd); +} + +/*****************************************************************************/ + static KnownNetworkId * known_network_id_new(const char *name, NMIwdNetworkSecurity security) { @@ -721,6 +791,9 @@ sett_conn_changed(NMSettingsConnection *sett_conn, "iwd: changed Wi-Fi connection %s mirrored as IWD profile %s", nm_settings_connection_get_id(sett_conn), full_path); + + if (security == NM_IWD_NETWORK_SECURITY_8021X) + save_mirrored(nm_iwd_manager_get(), ssid); } /* Look up an existing NMSettingsConnection for a network that has been @@ -1283,6 +1356,7 @@ connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer gs_free_error GError *error = NULL; nm_auto_unref_keyfile GKeyFile *iwd_config = NULL; NMSettingsConnectionIntFlags flags; + NMIwdNetworkSecurity security; if (!nm_streq(nm_settings_connection_get_connection_type(sett_conn), "802-11-wireless")) return; @@ -1338,6 +1412,12 @@ connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer _LOGD("New Wi-Fi connection %s mirrored as IWD profile %s", nm_settings_connection_get_id(sett_conn), full_path); + + if (nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security) + && security == NM_IWD_NETWORK_SECURITY_8021X) { + NMSettingWireless *s_wifi = nm_connection_get_setting_wireless(conn); + save_mirrored(nm_iwd_manager_get(), nm_setting_wireless_get_ssid(s_wifi)); + } } static gboolean @@ -1952,6 +2032,8 @@ dispose(GObject *object) g_hash_table_unref(nm_steal_pointer(&priv->p2p_devices)); + g_slist_free_full(nm_steal_pointer(&priv->recently_mirrored), recently_mirrored_data_free); + G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object); } diff --git a/src/core/devices/wifi/nm-iwd-manager.h b/src/core/devices/wifi/nm-iwd-manager.h index 02cd6bba5..80a275346 100644 --- a/src/core/devices/wifi/nm-iwd-manager.h +++ b/src/core/devices/wifi/nm-iwd-manager.h @@ -63,4 +63,6 @@ gboolean nm_iwd_manager_check_wfd_info_compatible(NMIwdManager *self, const NMIw gboolean nm_iwd_manager_register_wfd(NMIwdManager *self, const NMIwdWfdInfo *wfd_info); void nm_iwd_manager_unregister_wfd(NMIwdManager *self); +bool nm_iwd_manager_is_recently_mirrored(NMIwdManager *self, const GBytes *ssid); + #endif /* __NETWORKMANAGER_IWD_MANAGER_H__ */