From 7d9b37feaf247d7d456d3dc86e5fbec78b164284 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 22 Oct 2020 15:59:04 +0200 Subject: [PATCH 01/13] iwd: Handle the net.connman.iwd.Agent.Cancel() method Implement a Cancel method on our IWD secrets agent DBus object. This results in a call to nm_device_iwd_agent_query() for the device currently handling the request and the @invocation parameter is NULL to signal that the current query is being cancelled. nm_device_iwd_agent_query doesn't do much with this call just yet but the handling will be necessary when IWD autoconnect is used by NM. --- src/devices/wifi/nm-device-iwd.c | 17 +++++++++++ src/devices/wifi/nm-iwd-manager.c | 48 ++++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index e59dafdefe..064ca4e53b 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -2607,6 +2607,23 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + if (!invocation) { + NMActRequest *act_req = nm_device_get_act_request(device); + + if (!act_req) + return FALSE; + + wifi_secrets_cancel(self); + + if (nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH) + nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); + + /* The secrets request is being cancelled. Let the Network.Connect + * method call's callback handle the failure. + */ + return TRUE; + } + req = nm_device_get_act_request(device); if (!req || nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG) { _LOGI(LOGD_WIFI, "IWD asked for secrets without explicit connect request"); diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index 402480d279..2ab023492a 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -39,6 +39,7 @@ typedef struct { guint agent_id; char * agent_path; GHashTable * known_networks; + NMDeviceIwd * last_agent_call_device; } NMIwdManagerPrivate; struct _NMIwdManager { @@ -178,6 +179,25 @@ agent_dbus_method_cb(GDBusConnection * connection, if (!nm_streq0(name_owner, sender)) goto return_error; + if (!strcmp(method_name, "Cancel")) { + const char *reason = NULL; + + g_variant_get(parameters, "(&s)", &reason); + _LOGD("agent-request: Cancel reason: %s", reason); + + if (!priv->last_agent_call_device) + goto return_error; + + if (nm_device_iwd_agent_query(priv->last_agent_call_device, NULL)) { + priv->last_agent_call_device = NULL; + g_dbus_method_invocation_return_value(invocation, NULL); + return; + } + + priv->last_agent_call_device = NULL; + goto return_error; + } + if (!strcmp(method_name, "RequestUserPassword")) g_variant_get(parameters, "(&os)", &network_path, NULL); else @@ -197,8 +217,10 @@ agent_dbus_method_cb(GDBusConnection * connection, goto return_error; } - if (nm_device_iwd_agent_query(device, invocation)) + if (nm_device_iwd_agent_query(device, invocation)) { + priv->last_agent_call_device = device; return; + } _LOGD("agent-request: device %s did not handle the IWD Agent request", nm_device_get_iface(NM_DEVICE(device))); @@ -229,10 +251,12 @@ static const GDBusInterfaceInfo iwd_agent_iface_info = NM_DEFINE_GDBUS_INTERFACE NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), NM_DEFINE_GDBUS_METHOD_INFO( "RequestUserPassword", - .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), + .in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("network", "o"), NM_DEFINE_GDBUS_ARG_INFO("user", "s"), ), - .out_args = - NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), ), ); + .out_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("password", "s"), ), ), + NM_DEFINE_GDBUS_METHOD_INFO("Cancel", + .in_args = NM_DEFINE_GDBUS_ARG_INFOS( + NM_DEFINE_GDBUS_ARG_INFO("reason", "s"), ), ), ), ); static guint iwd_agent_export(GDBusConnection *connection, gpointer user_data, char **agent_path, GError **error) @@ -839,6 +863,19 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data) g_list_free_full(objects, g_object_unref); } +static void +device_removed(NMManager *manager, NMDevice *device, gpointer user_data) +{ + NMIwdManager * self = user_data; + NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); + + if (!NM_IS_DEVICE_IWD(device)) + return; + + if (priv->last_agent_call_device == NM_DEVICE_IWD(device)) + priv->last_agent_call_device = NULL; +} + static void got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) { @@ -955,6 +992,7 @@ nm_iwd_manager_init(NMIwdManager *self) priv->manager = g_object_ref(NM_MANAGER_GET); g_signal_connect(priv->manager, NM_MANAGER_DEVICE_ADDED, G_CALLBACK(device_added), self); + g_signal_connect(priv->manager, NM_MANAGER_DEVICE_REMOVED, G_CALLBACK(device_removed), self); priv->settings = g_object_ref(NM_SETTINGS_GET); g_signal_connect(priv->settings, @@ -997,6 +1035,8 @@ dispose(GObject *object) g_clear_object(&priv->manager); } + priv->last_agent_call_device = NULL; + G_OBJECT_CLASS(nm_iwd_manager_parent_class)->dispose(object); } From 08bf75f58e520b6e049d59afd8b638380609b946 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 22 Oct 2020 16:14:32 +0200 Subject: [PATCH 02/13] iwd: Allow scanning in NM_DEVICE_STATE_NEED_AUTH In this state, same as in DISCONNECTED or ACTIVATED, allow scanning if IWD is in the "connected" or "disconnected" states as there's no reason not to scan. --- src/devices/wifi/nm-device-iwd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index 064ca4e53b..a41db2a99a 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -1202,7 +1202,6 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic) case NM_DEVICE_STATE_UNAVAILABLE: case NM_DEVICE_STATE_PREPARE: case NM_DEVICE_STATE_CONFIG: - case NM_DEVICE_STATE_NEED_AUTH: case NM_DEVICE_STATE_IP_CONFIG: case NM_DEVICE_STATE_IP_CHECK: case NM_DEVICE_STATE_SECONDARIES: @@ -1212,6 +1211,7 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic) case NM_DEVICE_STATE_DISCONNECTED: case NM_DEVICE_STATE_FAILED: case NM_DEVICE_STATE_ACTIVATED: + case NM_DEVICE_STATE_NEED_AUTH: break; } From 221c8d75f3e2208d14093ffd3006226fe18f7957 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 22 Oct 2020 16:25:16 +0200 Subject: [PATCH 03/13] iwd: Rename can_connect and iwd_connection Rename NMDeviceIwdPrivate.can_connect to .nm_autoconnect in preparation to also add .iwd_autoconnect. Rename misnamed local variable iwd_connection to nm_connection, we'll need a new iwd_connection variable later. --- src/devices/wifi/nm-device-iwd.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index a41db2a99a..b11a272e81 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -57,7 +57,7 @@ typedef struct { guint periodic_update_id; bool enabled : 1; bool can_scan : 1; - bool can_connect : 1; + bool nm_autoconnect : 1; bool scanning : 1; bool scan_requested : 1; bool act_mode_switch : 1; @@ -1022,7 +1022,7 @@ get_autoconnect_allowed(NMDevice *device) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(NM_DEVICE_IWD(device)); - return priv->can_connect; + return priv->nm_autoconnect; } static gboolean @@ -1524,8 +1524,8 @@ failed: nm_device_queue_state(device, NM_DEVICE_STATE_FAILED, reason); value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); - if (!priv->can_connect && nm_streq0(get_variant_state(value), "disconnected")) { - priv->can_connect = true; + if (!priv->nm_autoconnect && nm_streq(get_variant_state(value), "disconnected")) { + priv->nm_autoconnect = true; nm_device_emit_recheck_auto_activate(device); } g_variant_unref(value); @@ -2258,21 +2258,21 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) static void state_changed(NMDeviceIwd *self, const char *new_state) { - NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); - NMDevice * device = NM_DEVICE(self); - NMDeviceState dev_state = nm_device_get_state(device); - gboolean iwd_connection = FALSE; - gboolean can_connect = priv->can_connect; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState dev_state = nm_device_get_state(device); + gboolean nm_connection = FALSE; + gboolean can_connect = priv->nm_autoconnect; _LOGI(LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state); if (dev_state >= NM_DEVICE_STATE_CONFIG && dev_state <= NM_DEVICE_STATE_ACTIVATED) - iwd_connection = TRUE; + nm_connection = TRUE; /* Don't allow scanning while connecting, disconnecting or roaming */ set_can_scan(self, NM_IN_STRSET(new_state, "connected", "disconnected")); - priv->can_connect = FALSE; + priv->nm_autoconnect = FALSE; if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) { /* If we were connecting, do nothing, the confirmation of @@ -2281,7 +2281,7 @@ state_changed(NMDeviceIwd *self, const char *new_state) * without Network Manager's will so for simplicity force a * disconnect. */ - if (iwd_connection) + if (nm_connection) return; _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection success, asking IWD to disconnect"); @@ -2302,7 +2302,7 @@ state_changed(NMDeviceIwd *self, const char *new_state) if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH)) return; - if (iwd_connection) + if (nm_connection) nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); @@ -2315,7 +2315,7 @@ state_changed(NMDeviceIwd *self, const char *new_state) * Connect callback is pending. */ if (NM_IN_STRSET(new_state, "disconnected")) { - priv->can_connect = TRUE; + priv->nm_autoconnect = TRUE; if (!can_connect) nm_device_emit_recheck_auto_activate(device); } @@ -2474,7 +2474,7 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered) set_can_scan(self, FALSE); priv->scanning = FALSE; priv->scan_requested = FALSE; - priv->can_connect = FALSE; + priv->nm_autoconnect = FALSE; cleanup_association_attempt(self, FALSE); remove_all_aps(self); } From 61e516bc203722efdad3a041d2f460b7b9c4eef0 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 22 Oct 2020 16:31:57 +0200 Subject: [PATCH 04/13] iwd: Move scheduling periodic scan out of set_current_ap() set_current_ap() would always call schedule_periodic_scan() but: first it would do nothing when current_ap was non-NULL because we schedule_periodic_scan makes sure not to auto-scan when connected. Secondly state_changed() already calls schedule_periodic_scan indirectly through set_can_scan() so normally when we disconnect and current_ap becomes NULL we already do trigger a scan. The only situation where we didn't is when a connection is cancelled during NEED_AUTH because IWD's state doesn't change, so we add a schedule_periodic_scan() call in network_connect_cb() on error. --- src/devices/wifi/nm-device-iwd.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index b11a272e81..e766238121 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -169,7 +169,6 @@ set_current_ap(NMDeviceIwd *self, NMWifiAP *new_ap, gboolean recheck_available_c memset(priv->current_ap_bssid, 0, ETH_ALEN); _notify(self, PROP_ACTIVE_ACCESS_POINT); _notify(self, PROP_MODE); - schedule_periodic_scan(self, TRUE); } static void @@ -1524,9 +1523,13 @@ failed: nm_device_queue_state(device, NM_DEVICE_STATE_FAILED, reason); value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); - if (!priv->nm_autoconnect && nm_streq(get_variant_state(value), "disconnected")) { - priv->nm_autoconnect = true; - nm_device_emit_recheck_auto_activate(device); + if (nm_streq(get_variant_state(value), "disconnected")) { + schedule_periodic_scan(self, TRUE); + + if (!priv->nm_autoconnect) { + priv->nm_autoconnect = true; + nm_device_emit_recheck_auto_activate(device); + } } g_variant_unref(value); } From ca820d5f65b7280542f340a99617fd2b472269d8 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Fri, 23 Oct 2020 18:34:49 +0200 Subject: [PATCH 05/13] iwd: Never lock to BSSID in complete_connection The AP BSSIDs created by the iwd backend are made up so never lock the connections to them. It probably wouldn't matter as long as the iwd backend is used but the fake BSSID could stay in the connection properties even if the user switches to wpa_supplicant. --- src/devices/wifi/nm-device-iwd.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index e766238121..30adaaf045 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -944,10 +944,7 @@ complete_connection(NMDevice * device, } if (ap) { - if (!nm_wifi_ap_complete_connection(ap, - connection, - nm_wifi_utils_is_manf_default_ssid(ssid), - error)) + if (!nm_wifi_ap_complete_connection(ap, connection, FALSE, error)) return FALSE; } From 7cc5ee473e31339fa9bce183deed65a5ede45c07 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Fri, 23 Oct 2020 20:13:01 +0200 Subject: [PATCH 06/13] iwd: Validate UTF-8 SSID early in check_connection_compatible/complete_connection IWD only supports UTF-8 SSIDs internally, any BSS who's SSID doesn't validate as UTF-8 is ignored. There's also no way to ask IWD to connect to such network/start AP/Adhoc etc. because SSIDs are passed as D-Bus strings. So validate that connection SSIDs are UTF-8 early in check_connection_compatible/complete_connection and refactor check_connection slightly to avoid duplication. Since NMWifiAPs are created by us, we already know those have valid SSIDs so once we've also checked new NMConnections in check_connection_compatible there should be no possibility that an SSID anywhere else in the code is not UTF8. We should be able to treat the GBytes values as UTF8 without redundant validation or the complex locale-dependent conversions in _nm_utils_ssid_to_utf8. --- src/devices/wifi/nm-device-iwd.c | 76 +++++++++++++++++++------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index 30adaaf045..d242d887f9 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -656,6 +656,9 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * const char * mode; NMIwdNetworkSecurity security; gboolean mapped; + GBytes * ssid; + const guint8 * ssid_bytes; + gsize ssid_len; if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class) ->check_connection_compatible(device, connection, error)) @@ -663,6 +666,23 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * s_wireless = nm_connection_get_setting_wireless(connection); + /* complete_connection would be called (if at all) before this function + * so an SSID should always be set. IWD doesn't support non-UTF8 SSIDs + * (ignores BSSes with such SSIDs and has no way to represent them on + * DBus) so we can cut it short for connections with a non-UTF8 SSID. + */ + ssid = nm_setting_wireless_get_ssid(s_wireless); + if (!ssid) + return FALSE; + + ssid_bytes = g_bytes_get_data(ssid, &ssid_len); + if (!g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) { + nm_utils_error_set_literal(error, + NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, + "non-UTF-8 connection SSID not supported by IWD backend"); + return FALSE; + } + perm_hw_addr = nm_device_get_permanent_hw_address(device); mac = nm_setting_wireless_get_mac_address(s_wireless); if (perm_hw_addr) { @@ -862,20 +882,18 @@ complete_connection(NMDevice * device, NMSettingWireless * s_wifi; gs_free char * ssid_utf8 = NULL; NMWifiAP * ap; - GBytes * ssid; - GBytes * setting_ssid = NULL; - gboolean hidden = FALSE; + GBytes * ssid = NULL; + gboolean hidden = FALSE; const char * mode; s_wifi = nm_connection_get_setting_wireless(connection); mode = s_wifi ? nm_setting_wireless_get_mode(s_wifi) : NULL; - if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) { - if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) - return FALSE; - ap = NULL; - } else if (!specific_object) { + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP) || !specific_object) { + const guint8 *ssid_bytes; + gsize ssid_len; + /* If not given a specific object, we need at minimum an SSID */ if (!s_wifi) { g_set_error_literal(error, @@ -885,16 +903,24 @@ complete_connection(NMDevice * device, return FALSE; } - setting_ssid = nm_setting_wireless_get_ssid(s_wifi); - if (!setting_ssid || g_bytes_get_size(setting_ssid) == 0) { - g_set_error_literal( - error, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_INVALID_CONNECTION, - "A 'wireless' setting with a valid SSID is required if no AP path was given."); + ssid = nm_setting_wireless_get_ssid(s_wifi); + ssid_bytes = g_bytes_get_data(ssid, &ssid_len); + + if (!ssid || ssid_len == 0 || !g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)) { + g_set_error_literal(error, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "A 'wireless' setting with a valid UTF-8 SSID is required if no AP " + "path was given."); return FALSE; } + } + if (nm_streq0(mode, NM_SETTING_WIRELESS_MODE_AP)) { + if (!nm_setting_verify(NM_SETTING(s_wifi), connection, error)) + return FALSE; + ap = NULL; + } else if (!specific_object) { /* Find a compatible AP in the scan list */ ap = nm_wifi_aps_find_first_compatible(&priv->aps_lst_head, connection); if (!ap) { @@ -923,24 +949,14 @@ complete_connection(NMDevice * device, specific_object); return FALSE; } - } - /* Add a wifi setting if one doesn't exist yet */ - if (!s_wifi) { - s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); - nm_connection_add_setting(connection, NM_SETTING(s_wifi)); - } - - ssid = nm_setting_wireless_get_ssid(s_wifi); - if (!ssid && ap) ssid = nm_wifi_ap_get_ssid(ap); - if (!ssid) { - g_set_error_literal(error, - NM_DEVICE_ERROR, - NM_DEVICE_ERROR_INVALID_CONNECTION, - "A 'wireless' setting with a valid SSID is required."); - return FALSE; + /* Add a wifi setting if one doesn't exist yet */ + if (!s_wifi) { + s_wifi = (NMSettingWireless *) nm_setting_wireless_new(); + nm_connection_add_setting(connection, NM_SETTING(s_wifi)); + } } if (ap) { From bb25112b617d860797d00f37efa411d391dafd33 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Fri, 23 Oct 2020 22:11:01 +0200 Subject: [PATCH 07/13] iwd: Stop using _nm_utils_ssid_to_utf8() _nm_utils_ssid_to_utf8() can be quite heavy and also has this comment: * Again, this function should be used for debugging and display purposes * _only_. In most places that we used it, we have already validated the connection's SSID to be valid UTF-8 so we can simply g_strndup() it now, even in the two places where we actually only needed it for display purposes. And we definitely don't need or want the locale-specific conversions done in _nm_utils_ssid_to_utf8 when the SSID is *not* utf8. In mirror_8021x_connection we also optimize the lookup loop to avoid validating and strdup'ing all the SSID. --- src/devices/wifi/nm-device-iwd.c | 149 ++++++++++++++---------------- src/devices/wifi/nm-iwd-manager.c | 40 +++++--- src/devices/wifi/nm-wifi-utils.c | 49 ++++++---- src/devices/wifi/nm-wifi-utils.h | 5 +- 4 files changed, 132 insertions(+), 111 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index d242d887f9..d3b20636dc 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -603,27 +603,13 @@ deactivate_async(NMDevice * device, static gboolean is_connection_known_network(NMConnection *connection) { - NMSettingWireless * s_wireless; NMIwdNetworkSecurity security; - gboolean security_ok; - GBytes * ssid; - gs_free char * ssid_utf8 = NULL; + gs_free char * ssid = NULL; - s_wireless = nm_connection_get_setting_wireless(connection); - if (!s_wireless) + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, &security)) return FALSE; - ssid = nm_setting_wireless_get_ssid(s_wireless); - if (!ssid) - return FALSE; - - ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); - - security = nm_wifi_connection_get_iwd_security(connection, &security_ok); - if (!security_ok) - return FALSE; - - return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid_utf8, security); + return nm_iwd_manager_is_known_network(nm_iwd_manager_get(), ssid, security); } static gboolean @@ -655,7 +641,6 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * const char * perm_hw_addr; const char * mode; NMIwdNetworkSecurity security; - gboolean mapped; GBytes * ssid; const guint8 * ssid_bytes; gsize ssid_len; @@ -712,8 +697,8 @@ check_connection_compatible(NMDevice *device, NMConnection *connection, GError * return FALSE; } - security = nm_wifi_connection_get_iwd_security(connection, &mapped); - if (!mapped) { + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) + || security == NM_IWD_NETWORK_SECURITY_WEP) { nm_utils_error_set_literal(error, NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE, "connection authentication type not supported by IWD backend"); @@ -799,11 +784,12 @@ check_connection_available(NMDevice * device, const char * specific_object, GError ** error) { - NMDeviceIwd * self = NM_DEVICE_IWD(device); - NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); - NMSettingWireless * s_wifi; - const char * mode; - NMWifiAP * ap = NULL; + NMDeviceIwd * self = NM_DEVICE_IWD(device); + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wifi; + const char * mode; + NMWifiAP * ap = NULL; + NMIwdNetworkSecurity security; s_wifi = nm_connection_get_setting_wireless(connection); g_return_val_if_fail(s_wifi, FALSE); @@ -857,7 +843,8 @@ check_connection_available(NMDevice * device, /* 8021x networks can only be used if they've been provisioned on the IWD side and * thus are Known Networks. */ - if (nm_wifi_connection_get_iwd_security(connection, NULL) == NM_IWD_NETWORK_SECURITY_8021X) { + if (nm_wifi_connection_get_iwd_ssid_and_security(connection, NULL, &security) + && security == NM_IWD_NETWORK_SECURITY_8021X) { if (!is_ap_known_network(ap)) { nm_utils_error_set_literal( error, @@ -870,6 +857,18 @@ check_connection_available(NMDevice * device, return TRUE; } +/* To be used where the SSID has been validated before */ +static char * +iwd_ssid_to_str(const GBytes *ssid) +{ + const guint8 *ssid_bytes; + gsize ssid_len; + + ssid_bytes = g_bytes_get_data((GBytes *) ssid, &ssid_len); + nm_assert(ssid && g_utf8_validate((const char *) ssid_bytes, ssid_len, NULL)); + return g_strndup((const char *) ssid_bytes, ssid_len); +} + static gboolean complete_connection(NMDevice * device, NMConnection * connection, @@ -964,7 +963,7 @@ complete_connection(NMDevice * device, return FALSE; } - ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); + ssid_utf8 = iwd_ssid_to_str(ssid); nm_utils_complete_generic( nm_device_get_platform(device), connection, @@ -1455,10 +1454,8 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; NMConnection * connection; - NMSettingWireless * s_wifi; - GBytes * ssid; - gs_free char * ssid_utf8 = NULL; - NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; + gs_free char * ssid = NULL; + NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; GVariant * value; variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); @@ -1507,19 +1504,12 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) if (!connection) goto failed; - s_wifi = nm_connection_get_setting_wireless(connection); - if (!s_wifi) + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL)) goto failed; - ssid = nm_setting_wireless_get_ssid(s_wifi); - if (!ssid) - goto failed; - - ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); - _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Connected to '%s'.", - ssid_utf8); + ssid); nm_device_activate_schedule_stage3_ip_config_start(device); if (!priv->periodic_update_id) { @@ -1577,10 +1567,9 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) NMDevice * device = NM_DEVICE(self); gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; - NMSettingWireless * s_wireless; - GBytes * ssid; - gs_free char * ssid_utf8 = NULL; + gs_free char * ssid = NULL; const char * mode; + NMSettingWireless * s_wireless; variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (!variant) { @@ -1599,21 +1588,18 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); - s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); - if (!s_wireless) + if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device), + &ssid, + NULL)) goto error; - ssid = nm_setting_wireless_get_ssid(s_wireless); - if (!ssid) - goto error; - - ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); - _LOGI(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Stage 2 of 5 (Device Configure) successful. Started '%s'.", - ssid_utf8); + ssid); nm_device_activate_schedule_stage3_ip_config_start(device); + s_wireless = + (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); mode = nm_setting_wireless_get_mode(s_wireless); if (!priv->periodic_update_id && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); @@ -1633,14 +1619,13 @@ error: static void act_check_interface(NMDeviceIwd *self) { - NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); - NMDevice * device = NM_DEVICE(self); - NMSettingWireless * s_wireless; - NMSettingWirelessSecurity *s_wireless_sec; - GDBusProxy * proxy = NULL; - GBytes * ssid; - gs_free char * ssid_utf8 = NULL; - const char * mode; + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMSettingWireless * s_wireless; + GDBusProxy * proxy = NULL; + gs_free char * ssid = NULL; + const char * mode; + NMIwdNetworkSecurity security; if (!priv->act_mode_switch) return; @@ -1662,44 +1647,46 @@ act_check_interface(NMDeviceIwd *self) if (!NM_IN_SET(nm_device_get_state(device), NM_DEVICE_STATE_CONFIG)) return; - ssid = nm_setting_wireless_get_ssid(s_wireless); - if (!ssid) + if (!nm_wifi_connection_get_iwd_ssid_and_security(nm_device_get_applied_connection(device), + &ssid, + &security)) goto failed; - ssid_utf8 = _nm_utils_ssid_to_utf8(ssid); - - s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting( - device, - NM_TYPE_SETTING_WIRELESS_SECURITY); - - if (!s_wireless_sec) { + if (security == NM_IWD_NETWORK_SECURITY_NONE) { g_dbus_proxy_call(proxy, "StartOpen", - g_variant_new("(s)", ssid_utf8), + g_variant_new("(s)", ssid), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, act_start_cb, self); - } else { - const char *psk = nm_setting_wireless_security_get_psk(s_wireless_sec); + } else if (security == NM_IWD_NETWORK_SECURITY_PSK) { + NMSettingWirelessSecurity *s_wireless_sec; + const char * psk; + + s_wireless_sec = (NMSettingWirelessSecurity *) nm_device_get_applied_setting( + device, + NM_TYPE_SETTING_WIRELESS_SECURITY); + psk = nm_setting_wireless_security_get_psk(s_wireless_sec); if (!psk) { - _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) No PSK for '%s'.", ssid_utf8); + _LOGE(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) No PSK for '%s'.", ssid); goto failed; } g_dbus_proxy_call(proxy, "Start", - g_variant_new("(ss)", ssid_utf8, psk), + g_variant_new("(ss)", ssid, psk), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, act_start_cb, self); - } + } else + goto failed; - _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid_utf8); + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Activation: (wifi) Called Start('%s').", ssid); return; failed: @@ -1951,7 +1938,7 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) priv->secrets_failed = FALSE; if (nm_wifi_ap_get_fake(ap)) { - gs_free char *ssid_str = NULL; + gs_free char *ssid = NULL; if (!nm_setting_wireless_get_hidden(s_wireless)) { _LOGW(LOGD_DEVICE | LOGD_WIFI, @@ -1961,11 +1948,15 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) goto out_fail; } + if (!nm_wifi_connection_get_iwd_ssid_and_security(connection, &ssid, NULL)) { + NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + goto out_fail; + } + /* Use Station.ConnectHiddenNetwork method instead of Network proxy. */ - ssid_str = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless)); g_dbus_proxy_call(priv->dbus_station_proxy, "ConnectHiddenNetwork", - g_variant_new("(s)", ssid_str), + g_variant_new("(s)", ssid), G_DBUS_CALL_FLAGS_NONE, G_MAXINT, priv->cancellable, diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index 2ab023492a..15b88f8115 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -411,18 +411,22 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne NMSetting * setting; GError * error = NULL; gs_unref_bytes GBytes *new_ssid = NULL; + gsize ssid_len = strlen(name); for (iter = nm_settings_get_connections(priv->settings, NULL); *iter; iter++) { NMSettingsConnection *sett_conn = *iter; NMConnection * conn = nm_settings_connection_get_connection(sett_conn); NMIwdNetworkSecurity security; - gs_free char * ssid_name = NULL; NMSettingWireless * s_wifi; NMSetting8021x * s_8021x; gboolean external = FALSE; guint i; + const guint8 * ssid_bytes; + gsize ssid_len2; + + if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security)) + continue; - security = nm_wifi_connection_get_iwd_security(conn, NULL); if (security != NM_IWD_NETWORK_SECURITY_8021X) continue; @@ -430,9 +434,11 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne if (!s_wifi) continue; - ssid_name = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wifi)); - - if (!nm_streq(ssid_name, name)) + /* The SSID must be UTF-8 if it matches since name is known to be + * valid UTF-8, so just memcmp them. + */ + ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wifi), &ssid_len2); + if (!ssid_bytes || ssid_len2 != ssid_len || memcmp(ssid_bytes, name, ssid_len)) continue; s_8021x = nm_connection_get_setting_802_1x(conn); @@ -468,7 +474,7 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne NULL)); nm_connection_add_setting(connection, setting); - new_ssid = g_bytes_new(name, strlen(name)); + new_ssid = g_bytes_new(name, ssid_len); setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS, NM_SETTING_WIRELESS_SSID, new_ssid, @@ -713,19 +719,27 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); NMConnection * conn = nm_settings_connection_get_connection(sett_conn); NMSettingWireless * s_wireless; - gboolean mapped; KnownNetworkData * data; KnownNetworkId id; - gs_free char * ssid_str = NULL; + char ssid_buf[33]; + const guint8 * ssid_bytes; + gsize ssid_len; - id.security = nm_wifi_connection_get_iwd_security(conn, &mapped); - if (!mapped) + if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security)) return; s_wireless = nm_connection_get_setting_wireless(conn); - ssid_str = _nm_utils_ssid_to_utf8(nm_setting_wireless_get_ssid(s_wireless)); - id.name = ssid_str; - data = g_hash_table_lookup(priv->known_networks, &id); + if (!s_wireless) + return; + + ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wireless), &ssid_len); + if (!ssid_bytes || ssid_len > 32 || memchr(ssid_bytes, 0, ssid_len)) + return; + + memcpy(ssid_buf, ssid_bytes, ssid_len); + ssid_buf[ssid_len] = '\0'; + id.name = ssid_buf; + data = g_hash_table_lookup(priv->known_networks, &id); if (!data) return; diff --git a/src/devices/wifi/nm-wifi-utils.c b/src/devices/wifi/nm-wifi-utils.c index e371d19755..3fa33256ad 100644 --- a/src/devices/wifi/nm-wifi-utils.c +++ b/src/devices/wifi/nm-wifi-utils.c @@ -886,34 +886,49 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid) return FALSE; } -NMIwdNetworkSecurity -nm_wifi_connection_get_iwd_security(NMConnection *connection, gboolean *mapped) +/* To be used for connections where the SSID has been validated before */ +gboolean +nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, + char ** ssid, + NMIwdNetworkSecurity *security) { + NMSettingWireless * s_wireless; NMSettingWirelessSecurity *s_wireless_sec; const char * key_mgmt = NULL; - if (!nm_connection_get_setting_wireless(connection)) - goto error; + s_wireless = nm_connection_get_setting_wireless(connection); + if (!s_wireless) + return FALSE; - NM_SET_OUT(mapped, TRUE); + if (ssid) { + GBytes * bytes = nm_setting_wireless_get_ssid(s_wireless); + gsize ssid_len; + const char *ssid_str = (const char *) g_bytes_get_data(bytes, &ssid_len); + + nm_assert(bytes && g_utf8_validate(ssid_str, ssid_len, NULL)); + NM_SET_OUT(ssid, g_strndup(ssid_str, ssid_len)); + } + + if (!security) + return TRUE; s_wireless_sec = nm_connection_get_setting_wireless_security(connection); - if (!s_wireless_sec) - return NM_IWD_NETWORK_SECURITY_NONE; + if (!s_wireless_sec) { + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_NONE); + return TRUE; + } key_mgmt = nm_setting_wireless_security_get_key_mgmt(s_wireless_sec); nm_assert(key_mgmt); if (NM_IN_STRSET(key_mgmt, "none", "ieee8021x")) - return NM_IWD_NETWORK_SECURITY_WEP; + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_WEP); + else if (nm_streq(key_mgmt, "wpa-psk")) + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_PSK); + else if (nm_streq(key_mgmt, "wpa-eap")) + NM_SET_OUT(security, NM_IWD_NETWORK_SECURITY_8021X); + else + return FALSE; - if (nm_streq(key_mgmt, "wpa-psk")) - return NM_IWD_NETWORK_SECURITY_PSK; - - if (nm_streq(key_mgmt, "wpa-eap")) - return NM_IWD_NETWORK_SECURITY_8021X; - -error: - NM_SET_OUT(mapped, FALSE); - return NM_IWD_NETWORK_SECURITY_NONE; + return TRUE; } diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h index 678b353b1e..5c0474fb85 100644 --- a/src/devices/wifi/nm-wifi-utils.h +++ b/src/devices/wifi/nm-wifi-utils.h @@ -32,7 +32,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes * ssid, gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid); -NMIwdNetworkSecurity nm_wifi_connection_get_iwd_security(NMConnection *connection, - gboolean * mapped); +gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *connection, + char **ssid, + NMIwdNetworkSecurity *security); #endif /* __NM_WIFI_UTILS_H__ */ From 2198bb8293e94be01af3302781995fb855a8edf8 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Wed, 18 Nov 2020 15:42:47 +0100 Subject: [PATCH 08/13] wifi: Add NMWifiAp getters for wpa_flags/rsn_flags --- src/devices/wifi/nm-wifi-ap.c | 12 ++++++++++++ src/devices/wifi/nm-wifi-ap.h | 34 ++++++++++++++++++---------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/devices/wifi/nm-wifi-ap.c b/src/devices/wifi/nm-wifi-ap.c index 31b58978d7..fe42c0044b 100644 --- a/src/devices/wifi/nm-wifi-ap.c +++ b/src/devices/wifi/nm-wifi-ap.c @@ -342,6 +342,18 @@ nm_wifi_ap_get_metered(const NMWifiAP *self) return NM_WIFI_AP_GET_PRIVATE(self)->metered; } +NM80211ApSecurityFlags +nm_wifi_ap_get_wpa_flags(const NMWifiAP *self) +{ + return NM_WIFI_AP_GET_PRIVATE(self)->wpa_flags; +} + +NM80211ApSecurityFlags +nm_wifi_ap_get_rsn_flags(const NMWifiAP *self) +{ + return NM_WIFI_AP_GET_PRIVATE(self)->rsn_flags; +} + /*****************************************************************************/ gboolean diff --git a/src/devices/wifi/nm-wifi-ap.h b/src/devices/wifi/nm-wifi-ap.h index 5bb9dc6250..4ff5665fdb 100644 --- a/src/devices/wifi/nm-wifi-ap.h +++ b/src/devices/wifi/nm-wifi-ap.h @@ -64,22 +64,24 @@ nm_wifi_ap_get_supplicant_path(NMWifiAP *ap) return ap->_supplicant_path; } -GBytes * nm_wifi_ap_get_ssid(const NMWifiAP *ap); -gboolean nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid); -const char * nm_wifi_ap_get_address(const NMWifiAP *ap); -gboolean nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr); -NM80211Mode nm_wifi_ap_get_mode(NMWifiAP *ap); -gboolean nm_wifi_ap_is_hotspot(NMWifiAP *ap); -gint8 nm_wifi_ap_get_strength(NMWifiAP *ap); -gboolean nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength); -guint32 nm_wifi_ap_get_freq(NMWifiAP *ap); -gboolean nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq); -guint32 nm_wifi_ap_get_max_bitrate(NMWifiAP *ap); -gboolean nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate); -gboolean nm_wifi_ap_get_fake(const NMWifiAP *ap); -gboolean nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake); -NM80211ApFlags nm_wifi_ap_get_flags(const NMWifiAP *self); -gboolean nm_wifi_ap_get_metered(const NMWifiAP *self); +GBytes * nm_wifi_ap_get_ssid(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_ssid(NMWifiAP *ap, GBytes *ssid); +const char * nm_wifi_ap_get_address(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_address(NMWifiAP *ap, const char *addr); +NM80211Mode nm_wifi_ap_get_mode(NMWifiAP *ap); +gboolean nm_wifi_ap_is_hotspot(NMWifiAP *ap); +gint8 nm_wifi_ap_get_strength(NMWifiAP *ap); +gboolean nm_wifi_ap_set_strength(NMWifiAP *ap, gint8 strength); +guint32 nm_wifi_ap_get_freq(NMWifiAP *ap); +gboolean nm_wifi_ap_set_freq(NMWifiAP *ap, guint32 freq); +guint32 nm_wifi_ap_get_max_bitrate(NMWifiAP *ap); +gboolean nm_wifi_ap_set_max_bitrate(NMWifiAP *ap, guint32 bitrate); +gboolean nm_wifi_ap_get_fake(const NMWifiAP *ap); +gboolean nm_wifi_ap_set_fake(NMWifiAP *ap, gboolean fake); +NM80211ApFlags nm_wifi_ap_get_flags(const NMWifiAP *self); +gboolean nm_wifi_ap_get_metered(const NMWifiAP *self); +NM80211ApSecurityFlags nm_wifi_ap_get_wpa_flags(const NMWifiAP *self); +NM80211ApSecurityFlags nm_wifi_ap_get_rsn_flags(const NMWifiAP *self); const char * nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64 now_msec); From abc61774487269e455303fc490bd0a8c4168317b Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Fri, 30 Oct 2020 12:57:26 +0100 Subject: [PATCH 09/13] iwd: Create mirror connections for non-802.1X IWD known networks Until now we'd only create mirror NMSettingsConnection objects for IWD KnownNetwork objects of the "8021x" type in the NMIwdManager class. Now create mirror connections, or track existing matching NMSettingsConnections, for every Known Network, for three reasons: * to allow NMDeviceIwd to easily look up the NMSettingsConnection matching an externally-triggered connection, specifically when we let IWD autoconnect, * to allow users to "forget" those Known Networks, * to allow us to synchronize the autoconnectable property between NM and IWD to later allow users toggling it (not done yet). --- src/devices/wifi/nm-iwd-manager.c | 344 ++++++++++++++++++++++-------- src/devices/wifi/nm-iwd-manager.h | 3 + src/devices/wifi/nm-wifi-utils.h | 4 +- 3 files changed, 256 insertions(+), 95 deletions(-) diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index 15b88f8115..d2e17245e0 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -88,7 +88,7 @@ G_DEFINE_TYPE(NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT) /*****************************************************************************/ -static void mirror_8021x_connection_take_and_delete(NMSettingsConnection *sett_conn); +static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn); /*****************************************************************************/ @@ -118,6 +118,21 @@ get_property_string_or_null(GDBusProxy *proxy, const char *property) return get_variant_string_or_null(value); } +static gboolean +get_property_bool(GDBusProxy *proxy, const char *property, gboolean default_val) +{ + gs_unref_variant GVariant *value = NULL; + + if (!proxy || !property) + return default_val; + + value = g_dbus_proxy_get_cached_property(proxy, property); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_BOOLEAN)) + return default_val; + + return g_variant_get_boolean(value); +} + static NMDeviceIwd * get_device_from_network(NMIwdManager *self, GDBusProxy *network) { @@ -353,7 +368,7 @@ known_network_data_free(KnownNetworkData *network) return; g_object_unref(network->known_network); - mirror_8021x_connection_take_and_delete(network->mirror_connection); + mirror_connection_take_and_delete(network->mirror_connection); g_slice_free(KnownNetworkData, network); } @@ -394,14 +409,20 @@ set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *objec nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object); } -/* Look up an existing NMSettingsConnection for a WPA2-Enterprise network - * that has been preprovisioned with an IWD config file, or create a new - * in-memory connection object so that NM autoconnect mechanism and the - * clients know this networks needs no additional EAP configuration from - * the user. +/* Look up an existing NMSettingsConnection for a network that has been + * preprovisioned with an IWD config file or has been connected to before, + * or create a new in-memory NMSettingsConnection object. This will let + * users control the few supported properties (mainly make it + * IWD-autoconnectable or not), remove/forget the network, or, for a + * WPA2-Enterprise type network it will inform the NM autoconnect mechanism + * and the clients that this networks needs no additional EAP configuration + * from the user. */ static NMSettingsConnection * -mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_new) +mirror_connection(NMIwdManager * self, + const KnownNetworkId *id, + gboolean create_new, + GDBusProxy * known_network) { NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); NMSettingsConnection *const *iter; @@ -409,49 +430,123 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne NMSettingsConnection * settings_connection = NULL; char uuid[37]; NMSetting * setting; - GError * error = NULL; - gs_unref_bytes GBytes *new_ssid = NULL; - gsize ssid_len = strlen(name); + gs_free_error GError *error = NULL; + gs_unref_bytes GBytes *new_ssid = NULL; + gsize ssid_len = strlen(id->name); + gboolean autoconnectable = TRUE; + gboolean hidden = FALSE; + gboolean exact_match = TRUE; + const char * key_mgmt = NULL; + + if (known_network) { + autoconnectable = get_property_bool(known_network, "AutoConnect", TRUE); + hidden = get_property_bool(known_network, "Hidden", FALSE); + } for (iter = nm_settings_get_connections(priv->settings, NULL); *iter; iter++) { NMSettingsConnection *sett_conn = *iter; NMConnection * conn = nm_settings_connection_get_connection(sett_conn); NMIwdNetworkSecurity security; NMSettingWireless * s_wifi; - NMSetting8021x * s_8021x; - gboolean external = FALSE; - guint i; const guint8 * ssid_bytes; gsize ssid_len2; if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security)) continue; - if (security != NM_IWD_NETWORK_SECURITY_8021X) + if (security != id->security) continue; s_wifi = nm_connection_get_setting_wireless(conn); if (!s_wifi) continue; - /* The SSID must be UTF-8 if it matches since name is known to be + /* The SSID must be UTF-8 if it matches since id->name is known to be * valid UTF-8, so just memcmp them. */ ssid_bytes = g_bytes_get_data(nm_setting_wireless_get_ssid(s_wifi), &ssid_len2); - if (!ssid_bytes || ssid_len2 != ssid_len || memcmp(ssid_bytes, name, ssid_len)) + if (!ssid_bytes || ssid_len2 != ssid_len || memcmp(ssid_bytes, id->name, ssid_len)) continue; - s_8021x = nm_connection_get_setting_802_1x(conn); - for (i = 0; i < nm_setting_802_1x_get_num_eap_methods(s_8021x); i++) { - if (nm_streq(nm_setting_802_1x_get_eap_method(s_8021x, i), "external")) { - external = TRUE; - break; - } + exact_match = TRUE; + + if (known_network) { + NMSettingConnection *s_conn = nm_connection_get_setting_connection(conn); + + if (nm_setting_connection_get_autoconnect(s_conn) != autoconnectable + || nm_setting_wireless_get_hidden(s_wifi) != hidden) + exact_match = FALSE; } - /* Prefer returning connections for EAP method "external" */ - if (!settings_connection || external) + switch (id->security) { + case NM_IWD_NETWORK_SECURITY_WEP: + case NM_IWD_NETWORK_SECURITY_NONE: + case NM_IWD_NETWORK_SECURITY_PSK: + break; + case NM_IWD_NETWORK_SECURITY_8021X: + { + NMSetting8021x *s_8021x = nm_connection_get_setting_802_1x(conn); + gboolean external = FALSE; + guint i; + + for (i = 0; i < nm_setting_802_1x_get_num_eap_methods(s_8021x); i++) { + if (nm_streq(nm_setting_802_1x_get_eap_method(s_8021x, i), "external")) { + external = TRUE; + break; + } + } + + /* Prefer returning connections with EAP method "external" */ + if (!external) + exact_match = FALSE; + } + } + + if (!settings_connection || exact_match) settings_connection = sett_conn; + + if (exact_match) + break; + } + + if (settings_connection && known_network && !exact_match) { + NMSettingsConnectionIntFlags flags = nm_settings_connection_get_flags(settings_connection); + + /* If we found a connection and it's generated (likely by ourselves) + * it may have been created on a request by + * nm_iwd_manager_get_ap_mirror_connection() when no Known Network + * was available so we didn't have access to its properties other + * than Name and Security. Copy their values to the generated + * NMConnection. + * TODO: avoid notify signals triggering our own watch. + * + * If on the other hand this is a user-created NMConnection we + * should try to copy the properties from it to IWD's Known Network + * using the Properties DBus interface in case the user created an + * NM connection before IWD appeared on the bus, or before IWD + * created its Known Network object. (TODO) + */ + if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { + NMConnection *tmp_conn = nm_settings_connection_get_connection(settings_connection); + NMSettingConnection *s_conn = nm_connection_get_setting_connection(tmp_conn); + NMSettingWireless * s_wifi = nm_connection_get_setting_wireless(tmp_conn); + + g_object_set(G_OBJECT(s_conn), + NM_SETTING_CONNECTION_AUTOCONNECT, + autoconnectable, + NULL); + g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_HIDDEN, hidden, NULL); + } + } + + if (settings_connection && known_network) { + /* Reset NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL now that the + * connection is going to be referenced by a known network, we don't + * want it to be deleted when activation fails anymore. + */ + nm_settings_connection_set_flags_full(settings_connection, + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL, + 0); } /* If we already have an NMSettingsConnection matching this @@ -468,58 +563,82 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne NM_SETTING_CONNECTION_TYPE, NM_SETTING_WIRELESS_SETTING_NAME, NM_SETTING_CONNECTION_ID, - name, + id->name, NM_SETTING_CONNECTION_UUID, nm_utils_uuid_generate_buf(uuid), + NM_SETTING_CONNECTION_AUTOCONNECT, + autoconnectable, NULL)); nm_connection_add_setting(connection, setting); - new_ssid = g_bytes_new(name, ssid_len); + new_ssid = g_bytes_new(id->name, ssid_len); setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS, NM_SETTING_WIRELESS_SSID, new_ssid, NM_SETTING_WIRELESS_MODE, NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_HIDDEN, + hidden, NULL)); nm_connection_add_setting(connection, setting); - setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY, - NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, - "open", - NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, - "wpa-eap", - NULL)); - nm_connection_add_setting(connection, setting); + switch (id->security) { + case NM_IWD_NETWORK_SECURITY_WEP: + key_mgmt = "none"; + break; + case NM_IWD_NETWORK_SECURITY_NONE: + key_mgmt = NULL; + break; + case NM_IWD_NETWORK_SECURITY_PSK: + key_mgmt = "wpa-psk"; + break; + case NM_IWD_NETWORK_SECURITY_8021X: + key_mgmt = "wpa-eap"; + break; + } - /* "password" and "private-key-password" may be requested by the IWD agent - * from NM and IWD will implement a specific secret cache policy so by - * default respect that policy and don't save copies of those secrets in - * NM settings. The saved values can not be used anyway because of our - * use of NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW. - */ - setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_802_1X, - NM_SETTING_802_1X_PASSWORD_FLAGS, - NM_SETTING_SECRET_FLAG_NOT_SAVED, - NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, - NM_SETTING_SECRET_FLAG_NOT_SAVED, - NULL)); - nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), "external"); - nm_connection_add_setting(connection, setting); + if (key_mgmt) { + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_WIRELESS_SECURITY, + NM_SETTING_WIRELESS_SECURITY_AUTH_ALG, + "open", + NM_SETTING_WIRELESS_SECURITY_KEY_MGMT, + key_mgmt, + NULL)); + nm_connection_add_setting(connection, setting); + } + + if (id->security == NM_IWD_NETWORK_SECURITY_8021X) { + /* "password" and "private-key-password" may be requested by the IWD agent + * from NM and IWD will implement a specific secret cache policy so by + * default respect that policy and don't save copies of those secrets in + * NM settings. The saved values can not be used anyway because of our + * use of NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW. + */ + setting = NM_SETTING(g_object_new(NM_TYPE_SETTING_802_1X, + NM_SETTING_802_1X_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NM_SETTING_802_1X_PRIVATE_KEY_PASSWORD_FLAGS, + NM_SETTING_SECRET_FLAG_NOT_SAVED, + NULL)); + nm_setting_802_1x_add_eap_method(NM_SETTING_802_1X(setting), "external"); + nm_connection_add_setting(connection, setting); + } if (!nm_connection_normalize(connection, NULL, NULL, NULL)) return NULL; - if (!nm_settings_add_connection(priv->settings, - connection, - NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, - NM_SETTINGS_CONNECTION_ADD_REASON_NONE, - NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED, - &settings_connection, - &error)) { + if (!nm_settings_add_connection( + priv->settings, + connection, + NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY_ONLY, + NM_SETTINGS_CONNECTION_ADD_REASON_NONE, + NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED + | (known_network ? 0 : NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL), + &settings_connection, + &error)) { _LOGW("failed to add a mirror NMConnection for IWD's Known Network '%s': %s", - name, + id->name, error->message); - g_error_free(error); return NULL; } @@ -527,7 +646,7 @@ mirror_8021x_connection(NMIwdManager *self, const char *name, gboolean create_ne } static void -mirror_8021x_connection_take_and_delete(NMSettingsConnection *sett_conn) +mirror_connection_take_and_delete(NMSettingsConnection *sett_conn) { NMSettingsConnectionIntFlags flags; @@ -570,6 +689,7 @@ interface_added(GDBusObjectManager *object_manager, if (nm_streq(iface_name, NM_IWD_KNOWN_NETWORK_INTERFACE)) { KnownNetworkId * id; + KnownNetworkId * orig_id; KnownNetworkData * data; NMIwdNetworkSecurity security; const char * type_str, *name; @@ -591,28 +711,28 @@ interface_added(GDBusObjectManager *object_manager, id = known_network_id_new(name, security); - data = g_hash_table_lookup(priv->known_networks, id); - if (data) { + if (g_hash_table_lookup_extended(priv->known_networks, + id, + (void **) &orig_id, + (void **) &data)) { _LOGW("DBus error: KnownNetwork already exists ('%s', %s)", name, type_str); - g_free(id); nm_g_object_ref_set(&data->known_network, proxy); + g_free(id); + id = orig_id; } else { data = g_slice_new0(KnownNetworkData); data->known_network = g_object_ref(proxy); g_hash_table_insert(priv->known_networks, id, data); } - if (security == NM_IWD_NETWORK_SECURITY_8021X) { - sett_conn = mirror_8021x_connection(self, name, TRUE); + sett_conn = mirror_connection(self, id, TRUE, proxy); - if (sett_conn && sett_conn != data->mirror_connection) { - NMSettingsConnection *sett_conn_old = data->mirror_connection; + if (sett_conn && sett_conn != data->mirror_connection) { + NMSettingsConnection *sett_conn_old = data->mirror_connection; - data->mirror_connection = nm_g_object_ref(sett_conn); - mirror_8021x_connection_take_and_delete(sett_conn_old); - } - } else - mirror_8021x_connection_take_and_delete(g_steal_pointer(&data->mirror_connection)); + data->mirror_connection = nm_g_object_ref(sett_conn); + mirror_connection_take_and_delete(sett_conn_old); + } return; } @@ -715,15 +835,16 @@ object_removed(GDBusObjectManager *object_manager, GDBusObject *object, gpointer static void connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data) { - NMIwdManager * self = user_data; - NMIwdManagerPrivate *priv = NM_IWD_MANAGER_GET_PRIVATE(self); - NMConnection * conn = nm_settings_connection_get_connection(sett_conn); - NMSettingWireless * s_wireless; - KnownNetworkData * data; - KnownNetworkId id; - char ssid_buf[33]; - const guint8 * ssid_bytes; - gsize ssid_len; + NMIwdManager * self = user_data; + NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); + NMConnection * conn = nm_settings_connection_get_connection(sett_conn); + NMSettingWireless * s_wireless; + KnownNetworkData * data; + KnownNetworkId id; + char ssid_buf[33]; + const guint8 * ssid_bytes; + gsize ssid_len; + NMSettingsConnection *new_mirror_conn; if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &id.security)) return; @@ -743,22 +864,18 @@ connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpoint if (!data) return; - if (id.security == NM_IWD_NETWORK_SECURITY_8021X) { - NMSettingsConnection *new_mirror_conn; + if (data->mirror_connection != sett_conn) + return; - if (data->mirror_connection != sett_conn) - return; + g_clear_object(&data->mirror_connection); - g_clear_object(&data->mirror_connection); - - /* Don't call Forget for an 8021x network until there's no - * longer *any* matching NMSettingsConnection (debatable) - */ - new_mirror_conn = mirror_8021x_connection(self, id.name, FALSE); - if (new_mirror_conn) { - data->mirror_connection = g_object_ref(new_mirror_conn); - return; - } + /* Don't call Forget on the Known Network until there's no longer *any* + * matching NMSettingsConnection (debatable) + */ + new_mirror_conn = mirror_connection(self, &id, FALSE, NULL); + if (new_mirror_conn) { + data->mirror_connection = g_object_ref(new_mirror_conn); + return; } if (!priv->running) @@ -981,6 +1098,47 @@ nm_iwd_manager_is_known_network(NMIwdManager *self, const char *name, NMIwdNetwo return g_hash_table_contains(priv->known_networks, &kn_id); } +NMSettingsConnection * +nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap) +{ + NMIwdManagerPrivate * priv = NM_IWD_MANAGER_GET_PRIVATE(self); + KnownNetworkData * data; + char name_buf[33]; + KnownNetworkId kn_id = {name_buf, NM_IWD_NETWORK_SECURITY_NONE}; + const guint8 * ssid_bytes; + gsize ssid_len; + NM80211ApFlags flags = nm_wifi_ap_get_flags(ap); + NM80211ApSecurityFlags sec_flags = nm_wifi_ap_get_wpa_flags(ap) | nm_wifi_ap_get_rsn_flags(ap); + + ssid_bytes = g_bytes_get_data(nm_wifi_ap_get_ssid(ap), &ssid_len); + ssid_len = MIN(ssid_len, 32); + memcpy(name_buf, ssid_bytes, ssid_len); + name_buf[ssid_len] = '\0'; + + if (flags & NM_802_11_AP_FLAGS_PRIVACY) + kn_id.security = NM_IWD_NETWORK_SECURITY_WEP; + + if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_PSK) + kn_id.security = NM_IWD_NETWORK_SECURITY_PSK; + else if (sec_flags & NM_802_11_AP_SEC_KEY_MGMT_802_1X) + kn_id.security = NM_IWD_NETWORK_SECURITY_8021X; + + /* Right now it's easier for us to do a name+security lookup than to use + * the Network.KnownNetwork property to look up by path. + */ + data = g_hash_table_lookup(priv->known_networks, &kn_id); + if (data) + return data->mirror_connection; + + /* We have no KnownNetwork for this AP, we're probably connecting to it for + * the first time. This is not a usual/supported scenario so we don't need + * to bother too much about creating a great mirror connection, we don't + * even have any more information than the Name & Type properties on the + * Network interface. This *should* never happen for an 8021x type network. + */ + return mirror_connection(self, &kn_id, TRUE, NULL); +} + GDBusProxy * nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name) { diff --git a/src/devices/wifi/nm-iwd-manager.h b/src/devices/wifi/nm-iwd-manager.h index 00f0c30411..5a0f66680c 100644 --- a/src/devices/wifi/nm-iwd-manager.h +++ b/src/devices/wifi/nm-iwd-manager.h @@ -8,6 +8,7 @@ #include "devices/nm-device.h" #include "nm-wifi-utils.h" +#include "nm-wifi-ap.h" #define NM_IWD_BUS_TYPE G_BUS_TYPE_SYSTEM #define NM_IWD_SERVICE "net.connman.iwd" @@ -44,6 +45,8 @@ gboolean nm_iwd_manager_is_known_network(NMIwdManager * self, const char * name, NMIwdNetworkSecurity security); +NMSettingsConnection *nm_iwd_manager_get_ap_mirror_connection(NMIwdManager *self, NMWifiAP *ap); + GDBusProxy * nm_iwd_manager_get_dbus_interface(NMIwdManager *self, const char *path, const char *name); diff --git a/src/devices/wifi/nm-wifi-utils.h b/src/devices/wifi/nm-wifi-utils.h index 5c0474fb85..e6cec37d4a 100644 --- a/src/devices/wifi/nm-wifi-utils.h +++ b/src/devices/wifi/nm-wifi-utils.h @@ -32,8 +32,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes * ssid, gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid); -gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection *connection, - char **ssid, +gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection * connection, + char ** ssid, NMIwdNetworkSecurity *security); #endif /* __NM_WIFI_UTILS_H__ */ From 43fd93d8f485d710215717bce8928520e3744f7a Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Wed, 4 Nov 2020 01:35:21 +0100 Subject: [PATCH 10/13] iwd: Order objects from g_dbus_object_manager_get_objects Before we call interface_added for all interfaces and objects returned from g_dbus_object_manager_get_objects(), order the objects based on the interfaces present on them. This is to avoid processing Network.KnownNetwork properties referring to KnownNetwork objects that we haven't processed yet, and new Station.ConnectedNetwork properties referring to Network objects we haven't processed yet. In NMDeviceIwd make sure we don't emit unnecessary re-checks if device is not yet enabled because now we're always going to be adding the APs (representing IWD Network objects) before the device is enabled, i.e. before the nm_device_iwd_set_dbus_object() call, when NM first connects to IWD. --- src/devices/wifi/nm-device-iwd.c | 6 ++- src/devices/wifi/nm-iwd-manager.c | 76 +++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index d3b20636dc..ddccdf6f27 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -136,7 +136,9 @@ ap_add_remove(NMDeviceIwd *self, nm_dbus_object_clear_and_unexport(&ap); } - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + if (priv->enabled) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + if (recheck_available_connections) nm_device_recheck_available_connections(NM_DEVICE(self)); } @@ -2693,7 +2695,7 @@ nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool ad * can skip recheck-available if we're currently scanning or in the middle * of a GetOrderedNetworks() call as that will trigger the recheck too. */ - recheck = !priv->scanning && !priv->networks_requested; + recheck = priv->enabled && !priv->scanning && !priv->networks_requested; if (!add) { if (ap) { diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index d2e17245e0..6651d9761a 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -975,7 +975,28 @@ device_added(NMManager *manager, NMDevice *device, gpointer user_data) if (!priv->running) return; + /* Here we handle a potential scenario where IWD's DBus objects for the + * new device popped up before the NMDevice. The + * interface_added/object_added signals have been received already and + * the handlers couldn't do much because the NMDevice wasn't there yet + * so now we go over the Network and Device interfaces again. In this + * exact order for "object path" property consistency -- see reasoning + * in object_compare_interfaces. + */ objects = g_dbus_object_manager_get_objects(priv->object_manager); + + for (iter = objects; iter; iter = iter->next) { + GDBusObject * object = G_DBUS_OBJECT(iter->data); + gs_unref_object GDBusInterface *interface = NULL; + + interface = g_dbus_object_get_interface(object, NM_IWD_NETWORK_INTERFACE); + if (!interface) + continue; + + if (NM_DEVICE_IWD(device) == get_device_from_network(self, (GDBusProxy *) interface)) + nm_device_iwd_network_add_remove(NM_DEVICE_IWD(device), (GDBusProxy *) interface, TRUE); + } + for (iter = objects; iter; iter = iter->next) { GDBusObject * object = G_DBUS_OBJECT(iter->data); gs_unref_object GDBusInterface *interface = NULL; @@ -1007,6 +1028,60 @@ device_removed(NMManager *manager, NMDevice *device, gpointer user_data) priv->last_agent_call_device = NULL; } +/* This is used to sort the list of objects returned by GetManagedObjects() + * based on the DBus interfaces available on these objects in such a way that + * the interface_added calls happen in the right order. The order is defined + * by how some DBus interfaces point to interfaces on other objects using + * DBus properties of the type "object path" ("o" signature). This creates + * "dependencies" between objects. + * + * When NM and IWD are running, the InterfacesAdded signals should come in + * an order that ensures consistency of those object paths. For example + * when a Network interface is added with a KnownNetwork property, or that + * property is assigned a new value, the KnownNetwork object pointed to by + * it will have been added in an earlier InterfacesAdded signal. Similarly + * Station.ConnectedNetwork and Station.GetOrdereNetworks() only point to + * existing Network objects. (There may be circular dependencies but during + * initialization we only need a subset of those properties that doesn't + * have this problem.) + * + * But GetManagedObjects doesn't guarantee this kind of consistency so we + * order the returned object list ourselves to simplify the job of + * interface_added(). Objects that don't have any interfaces listed in + * interface_order are moved to the end of the list. + */ +static int +object_compare_interfaces(gconstpointer a, gconstpointer b) +{ + static const char *interface_order[] = { + NM_IWD_KNOWN_NETWORK_INTERFACE, + NM_IWD_NETWORK_INTERFACE, + NM_IWD_DEVICE_INTERFACE, + }; + int rank_a = G_N_ELEMENTS(interface_order); + int rank_b = G_N_ELEMENTS(interface_order); + guint pos; + + for (pos = 0; interface_order[pos]; pos++) { + GDBusInterface *iface_a; + GDBusInterface *iface_b; + + if (rank_a == G_N_ELEMENTS(interface_order) + && (iface_a = g_dbus_object_get_interface(G_DBUS_OBJECT(a), interface_order[pos]))) { + rank_a = pos; + g_object_unref(iface_a); + } + + if (rank_b == G_N_ELEMENTS(interface_order) + && (iface_b = g_dbus_object_get_interface(G_DBUS_OBJECT(b), interface_order[pos]))) { + rank_b = pos; + g_object_unref(iface_b); + } + } + + return rank_a - rank_b; +} + static void got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) { @@ -1062,6 +1137,7 @@ got_object_manager(GObject *object, GAsyncResult *result, gpointer user_data) g_hash_table_remove_all(priv->known_networks); objects = g_dbus_object_manager_get_objects(object_manager); + objects = g_list_sort(objects, object_compare_interfaces); for (iter = objects; iter; iter = iter->next) object_added(NULL, G_DBUS_OBJECT(iter->data), self); From dc0e31fb7014d8a97aca96bf97a8f86f8236300c Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Fri, 30 Oct 2020 13:27:41 +0100 Subject: [PATCH 11/13] iwd: Add the wifi.iwd.autoconnect setting If this setting it true (or missing) we skip most of the D-Bus Disconnect() calls whoe purpose was to keep IWD's internal autoconnect mechanism always disabled. We use the IWD's Station.State property updates, and secrets requets through our IWD agent, to find out when IWD is trying to connect and create "assumed" activations on the NM side to mirror the IWD state. This is quite complicated due to the many possible combinations of NMDevice's state and IWD's state. A lot of them are "impossible" but we try to be careful to consider all the different possibilities. NM has a nice API for "assuming connections" but it's designed for slightly different use cases than what we have here and for now we created normal "managed"-type activations when assuming an IWD automatic connection. --- man/NetworkManager.conf.xml | 19 +- src/devices/wifi/nm-device-iwd.c | 741 +++++++++++++++++++++++++++---- src/nm-config.c | 1 + src/nm-config.h | 1 + 4 files changed, 678 insertions(+), 84 deletions(-) diff --git a/man/NetworkManager.conf.xml b/man/NetworkManager.conf.xml index a235cf2553..1af436bbab 100644 --- a/man/NetworkManager.conf.xml +++ b/man/NetworkManager.conf.xml @@ -1090,8 +1090,25 @@ managed=1 + + wifi.iwd.autoconnect + + + If wifi.backend is iwd, setting this to + false forces IWD's autoconnect mechanism to be disabled for + this device and connections will only be initiated by NetworkManager whether + commaned by a client or automatically. Leaving it true (default) + stops NetworkManager from automatically initiating connections and allows + IWD to use its network ranking and scanning logic to decide the best networks + to autoconnect to next. Connections' autoconnect-priority, + autoconnect-retries settings will be ignored. Other settings + like permissions or multi-connect may interfere + with IWD connection attempts. + + + - , sriov-num-vfs + sriov-num-vfs Specify the number of virtual functions (VF) to enable diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index ddccdf6f27..b54b42f51d 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -27,6 +27,8 @@ #include "settings/nm-settings-connection.h" #include "settings/nm-settings.h" #include "supplicant/nm-supplicant-types.h" +#include "nm-auth-utils.h" +#include "nm-manager.h" #define _NMLOG_DEVICE_TYPE NMDeviceIwd #include "devices/nm-device-logging.h" @@ -58,6 +60,7 @@ typedef struct { bool enabled : 1; bool can_scan : 1; bool nm_autoconnect : 1; + bool iwd_autoconnect : 1; bool scanning : 1; bool scan_requested : 1; bool act_mode_switch : 1; @@ -68,6 +71,9 @@ typedef struct { uint32_t ap_id; guint32 rate; uint8_t current_ap_bssid[ETH_ALEN]; + GDBusMethodInvocation * pending_agent_request; + NMActiveConnection * assumed_ac; + guint assumed_ac_timeout; } NMDeviceIwdPrivate; struct _NMDeviceIwd { @@ -136,7 +142,7 @@ ap_add_remove(NMDeviceIwd *self, nm_dbus_object_clear_and_unexport(&ap); } - if (priv->enabled) + if (priv->enabled && !priv->iwd_autoconnect) nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); if (recheck_available_connections) @@ -185,7 +191,9 @@ remove_all_aps(NMDeviceIwd *self) c_list_for_each_entry_safe (ap, ap_safe, &priv->aps_lst_head, aps_lst) ap_add_remove(self, FALSE, ap, FALSE); - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + if (!priv->iwd_autoconnect) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_available_connections(NM_DEVICE(self)); } @@ -224,6 +232,8 @@ ap_from_network(NMDeviceIwd *self, NMWifiAP * ap; NMSupplicantBssInfo bss_info; + g_return_val_if_fail(network, NULL); + name_value = g_dbus_proxy_get_cached_property(network, "Name"); type_value = g_dbus_proxy_get_cached_property(network, "Type"); if (!name_value || !g_variant_is_of_type(name_value, G_VARIANT_TYPE_STRING) || !type_value @@ -294,8 +304,6 @@ insert_ap_from_network(NMDeviceIwd *self, network_proxy = nm_iwd_manager_get_dbus_interface(nm_iwd_manager_get(), path, NM_IWD_NETWORK_INTERFACE); - if (!network_proxy) - return; ap = ap_from_network(self, network_proxy, bss_path, last_seen_msec, signal); if (!ap) @@ -382,7 +390,9 @@ get_ordered_networks_cb(GObject *source, GAsyncResult *res, gpointer user_data) } if (changed) { - nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + if (!priv->iwd_autoconnect) + nm_device_emit_recheck_auto_activate(NM_DEVICE(self)); + nm_device_recheck_available_connections(NM_DEVICE(self)); } } @@ -486,6 +496,26 @@ wifi_secrets_cancel(NMDeviceIwd *self) if (priv->wifi_secrets_id) nm_act_request_cancel_secrets(NULL, priv->wifi_secrets_id); nm_assert(!priv->wifi_secrets_id); + + if (priv->pending_agent_request) { + g_dbus_method_invocation_return_error_literal(priv->pending_agent_request, + NM_DEVICE_ERROR, + NM_DEVICE_ERROR_INVALID_CONNECTION, + "NM secrets request cancelled"); + g_clear_object(&priv->pending_agent_request); + } +} + +static void +cleanup_assumed_connect(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + if (!priv->assumed_ac) + return; + + g_signal_handlers_disconnect_by_data(priv->assumed_ac, self); + g_clear_object(&priv->assumed_ac); } static void @@ -493,10 +523,12 @@ cleanup_association_attempt(NMDeviceIwd *self, gboolean disconnect) { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + cleanup_assumed_connect(self); wifi_secrets_cancel(self); set_current_ap(self, NULL, TRUE); nm_clear_g_source(&priv->periodic_update_id); + nm_clear_g_source(&priv->assumed_ac_timeout); if (disconnect && priv->dbus_station_proxy) send_disconnect(self); @@ -1232,6 +1264,27 @@ check_scanning_prohibited(NMDeviceIwd *self, gboolean periodic) return !priv->can_scan; } +static const char * +get_agent_request_network_path(GDBusMethodInvocation *invocation) +{ + const char *method_name = g_dbus_method_invocation_get_method_name(invocation); + GVariant * params = g_dbus_method_invocation_get_parameters(invocation); + const char *network_path = NULL; + + if (nm_streq(method_name, "RequestPassphrase")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestPrivateKeyPassphrase")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestUserNameAndPassword")) + g_variant_get(params, "(s)", &network_path); + else if (nm_streq(method_name, "RequestUserPassword")) { + const char *user; + g_variant_get(params, "(ss)", &network_path, &user); + } + + return network_path; +} + /* * try_reply_agent_request * @@ -1339,6 +1392,25 @@ try_reply_agent_request(NMDeviceIwd * self, return FALSE; } +static gboolean +assumed_ac_timeout_cb(gpointer user_data) +{ + NMDeviceIwd * self = user_data; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + + nm_assert(priv->assumed_ac); + + priv->assumed_ac_timeout = 0; + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT); + /* NMDevice's state change -> NMActRequests/NMActiveConnection's state + * change -> assumed_connection_state_changed_before_managed() -> + * cleanup_association_attempt() so no need to call it explicitly. + */ + return G_SOURCE_REMOVE; +} + static void wifi_secrets_get_one(NMDeviceIwd * self, const char * setting_name, NMSecretAgentGetSecretsFlags flags, @@ -1402,6 +1474,20 @@ wifi_secrets_cb(NMActRequest * req, goto secrets_error; if (replied) { + /* If we replied to the secrets request from IWD in the "disconnected" + * state and IWD doesn't move to a new state within 1 second, assume + * something went wrong (shouldn't happen). If a state change arrives + * after that nothing is lost, state_changed() will try to assume the + * connection again. + */ + if (priv->assumed_ac) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (nm_streq(get_variant_state(value), "disconnected")) + priv->assumed_ac_timeout = g_timeout_add_seconds(1, assumed_ac_timeout_cb, self); + } + /* Change state back to what it was before NEED_AUTH */ nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); return; @@ -1415,12 +1501,21 @@ wifi_secrets_cb(NMActRequest * req, return; secrets_error: - priv->secrets_failed = TRUE; g_dbus_method_invocation_return_error_literal(invocation, NM_DEVICE_ERROR, NM_DEVICE_ERROR_INVALID_CONNECTION, "NM secrets request failed"); - /* Now wait for the Connect callback to update device state */ + + if (priv->assumed_ac) { + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS); + /* NMDevice's state change -> NMActRequests/NMActiveConnection's state + * change -> assumed_connection_state_changed_before_managed() -> + * cleanup_association_attempt() so no need to call it explicitly. + */ + } else { + priv->secrets_failed = TRUE; + /* Now wait for the Connect callback to update device state */ + } } static void @@ -1459,6 +1554,7 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) gs_free char * ssid = NULL; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; GVariant * value; + gboolean disconnect = !priv->iwd_autoconnect; variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (!variant) { @@ -1502,6 +1598,8 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) nm_assert(nm_device_get_state(device) == NM_DEVICE_STATE_CONFIG); + disconnect = TRUE; + connection = nm_device_get_applied_connection(device); if (!connection) goto failed; @@ -1514,21 +1612,16 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) ssid); nm_device_activate_schedule_stage3_ip_config_start(device); - if (!priv->periodic_update_id) { - priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); - periodic_update(self); - } - return; failed: - /* Call Disconnect to make sure IWD's autoconnect is disabled */ - cleanup_association_attempt(self, TRUE); + /* If necessary call Disconnect to make sure IWD's autoconnect is disabled */ + cleanup_association_attempt(self, disconnect); - nm_device_queue_state(device, NM_DEVICE_STATE_FAILED, reason); + nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, reason); value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); - if (nm_streq(get_variant_state(value), "disconnected")) { + if (!priv->iwd_autoconnect && nm_streq(get_variant_state(value), "disconnected")) { schedule_periodic_scan(self, TRUE); if (!priv->nm_autoconnect) { @@ -1570,8 +1663,6 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) gs_unref_variant GVariant *variant = NULL; gs_free_error GError *error = NULL; gs_free char * ssid = NULL; - const char * mode; - NMSettingWireless * s_wireless; variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (!variant) { @@ -1600,14 +1691,6 @@ act_start_cb(GObject *source, GAsyncResult *res, gpointer user_data) ssid); nm_device_activate_schedule_stage3_ip_config_start(device); - s_wireless = - (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); - mode = nm_setting_wireless_get_mode(s_wireless); - if (!priv->periodic_update_id && nm_streq0(mode, NM_SETTING_WIRELESS_MODE_ADHOC)) { - priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); - periodic_update(self); - } - return; error: @@ -1825,6 +1908,237 @@ set_powered(NMDeviceIwd *self, gboolean powered) /*****************************************************************************/ +static NMWifiAP * +find_ap_by_supplicant_path(NMDeviceIwd *self, const NMRefString *path) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMWifiAP * tmp; + + c_list_for_each_entry (tmp, &priv->aps_lst_head, aps_lst) + if (nm_wifi_ap_get_supplicant_path(tmp) == path) + return tmp; + + return NULL; +} + +static void +assumed_connection_state_changed(NMActiveConnection *active, GParamSpec *pspec, NMDeviceIwd *self) +{ + NMSettingsConnection * sett_conn = nm_active_connection_get_settings_connection(active); + NMActiveConnectionState state = nm_active_connection_get_state(active); + + /* Delete the temporary connection created for an external IWD connection + * (triggered by somebody outside of NM, be it IWD autoconnect or a + * parallel client), unless it's been referenced by a Known Network + * object since, which would remove the EXTERNAL flag. + * + * Note we can't do this too early, e.g. at the same time that we're + * setting the device state to FAILED or DISCONNECTING because the + * connection shouldn't disappear while it's still being used. We do + * this on the connection's transition to DEACTIVATED same as as + * NMManager does for external activations. + */ + if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + + g_signal_handlers_disconnect_by_func(active, assumed_connection_state_changed, NULL); + + if (sett_conn + && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + nm_settings_connection_delete(sett_conn, FALSE); +} + +static void +assumed_connection_state_changed_before_managed(NMActiveConnection *active, + GParamSpec * pspec, + NMDeviceIwd * self) +{ + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMActiveConnectionState state = nm_active_connection_get_state(active); + gboolean disconnect; + + if (state != NM_ACTIVE_CONNECTION_STATE_DEACTIVATED) + return; + + /* When an assumed connection fails we always get called, even if the + * activation hasn't reached PREPARE or CONFIG, e.g. because of a policy + * or authorization problem in NMManager. .deactivate would only be + * called starting at some stage so we can't rely on that. + * + * If the error happened before PREPARE (where we set a non-NULL + * priv->current_ap) that will mean NM is somehow blocking autoconnect + * so we want to call IWD's Station.Disconnect() to block its + * autoconnect. If this happens during or after PREPARE, we just + * clean up and wait for a new attempt by IWD. + * + * cleanup_association_attempt will clear priv->assumed_ac, disconnect + * this callback from the signal and also send a Disconnect to IWD if + * needed. + * + * Note this function won't be called after IWD transitions to + * "connected" (and NMDevice to IP_CONFIG) as we disconnect from the + * signal at that point, cleanup_association_attempt() will be + * triggered by an IWD state change instead. + */ + disconnect = !priv->current_ap; + cleanup_association_attempt(self, disconnect); +} + +static void +assume_connection(NMDeviceIwd *self, NMWifiAP *ap) +{ + NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingsConnection *sett_conn; + gs_unref_object NMAuthSubject *subject = NULL; + NMActiveConnection * ac; + gs_free_error GError *error = NULL; + + /* We can use the .update_connection / nm_device_emit_recheck_assume + * API but we can also pass an assumed/external activation type + * directly to nm_manager_activate_connection() and skip the + * complicated process of creating a matching connection, taking + * advantage of the Known Networks pointing directly to a mirror + * connection. The only downside seems to be + * nm_manager_activate_connection() goes through the extra + * authorization. + * + * However for now we implement a similar behaviour using a normal + * "managed" activation. For one, assumed/external + * connection state is not reflected in nm_manager_get_state() until + * fully activated. Secondly setting the device state to FAILED + * is treated as ACTIVATED so we'd have to find another way to signal + * that stage2 is failing asynchronously. Thirdly the connection + * becomes "managed" only when ACTIVATED but for IWD it's really + * managed when IP_CONFIG starts. + */ + sett_conn = nm_iwd_manager_get_ap_mirror_connection(nm_iwd_manager_get(), ap); + if (!sett_conn) + goto error; + + subject = nm_auth_subject_new_internal(); + ac = nm_manager_activate_connection( + NM_MANAGER_GET, + sett_conn, + NULL, + nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)), + NM_DEVICE(self), + subject, + NM_ACTIVATION_TYPE_MANAGED, + NM_ACTIVATION_REASON_ASSUME, + NM_ACTIVATION_STATE_FLAG_LIFETIME_BOUND_TO_PROFILE_VISIBILITY, + &error); + + if (!ac) { + _LOGW(LOGD_WIFI, "Activation: (wifi) assume error: %s", error->message); + goto error; + } + + /* If no Known Network existed for this AP, we generated a temporary + * NMSettingsConnection with the EXTERNAL flag. It is not referenced by + * any Known Network objects at this time so we want to delete it if the + * IWD connection ends up failing or a later part of the activation fails + * before IWD created a Known Network. + * Setting the activation type to EXTERNAL would do this by causing + * NM_ACTIVATION_STATE_FLAG_EXTERNAL to be set on the NMActiveConnection + * but we don't want the connection to be marked EXTERNAL because we + * will be assuming the ownership of it in IP_CONFIG or thereabouts. + * + * This callback stays connected forever while the second one gets + * disconnected when we reset the activation type to managed. + */ + g_signal_connect(ac, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(assumed_connection_state_changed), + NULL); + g_signal_connect(ac, + "notify::" NM_ACTIVE_CONNECTION_STATE, + G_CALLBACK(assumed_connection_state_changed_before_managed), + self); + priv->assumed_ac = g_object_ref(ac); + + return; + +error: + send_disconnect(self); + + if (sett_conn + && NM_FLAGS_HAS(nm_settings_connection_get_flags(sett_conn), + NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)) + nm_settings_connection_delete(sett_conn, FALSE); +} + +static void +assumed_connection_progress_to_ip_config(NMDeviceIwd *self, gboolean was_postponed) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState dev_state = nm_device_get_state(device); + + wifi_secrets_cancel(self); + nm_clear_g_source(&priv->assumed_ac_timeout); + + /* NM takes over the activation from this point on so clear the assumed + * activation state and if we were using NM_ACTIVATION_TYPE_ASSUMED or + * _EXTERNAL we'd need to reset the activation type to _MANAGED at this + * point instead of waiting for the ACTIVATED state (as done in + * nm_active_connection_set_state). + */ + cleanup_assumed_connect(self); + + if (dev_state == NM_DEVICE_STATE_NEED_AUTH) + nm_device_state_changed(NM_DEVICE(self), + NM_DEVICE_STATE_CONFIG, + NM_DEVICE_STATE_REASON_NONE); + + /* If stage2 had returned NM_ACT_STAGE_RETURN_POSTPONE, we tell NMDevice + * that stage2 is done. + */ + if (was_postponed) + nm_device_activate_schedule_stage3_ip_config_start(NM_DEVICE(self)); +} + +static void +initial_check_assume(NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + const char * network_path_str; + nm_auto_ref_string NMRefString *network_path = NULL; + NMWifiAP * ap = NULL; + gs_unref_variant GVariant *state_value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + gs_unref_variant GVariant *cn_value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork"); + + if (!NM_IN_STRSET(get_variant_state(state_value), "connecting", "connected", "roaming")) + return; + + if (!priv->iwd_autoconnect) { + send_disconnect(self); + return; + } + + if (!cn_value || !g_variant_is_of_type(cn_value, G_VARIANT_TYPE_OBJECT_PATH)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork property not cached or not an object path"); + return; + } + + network_path_str = g_variant_get_string(cn_value, NULL); + network_path = nm_ref_string_new(network_path_str); + ap = find_ap_by_supplicant_path(self, network_path); + + if (!ap) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork points to an unknown Network %s", + network_path_str); + return; + } + + _LOGD(LOGD_DEVICE | LOGD_WIFI, "assuming connection in initial_check_assume"); + assume_connection(self, ap); +} + static NMActStageReturn act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason) { @@ -1923,6 +2237,42 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason) goto out_fail; } + /* With priv->iwd_autoconnect, if we're assuming a connection because + * of a state change to "connecting", signal stage 2 is still running. + * If "connected" or "roaming", we can go right to the IP_CONFIG state + * and there's nothing left to do in CONFIG. + * If we're assuming the connection because of an agent request we + * switch to NEED_AUTH and actually send the request now that we + * have an activation request. + * + * This all assumes ConnectedNetwork hasn't changed. + */ + if (priv->assumed_ac) { + gboolean result; + + if (!priv->pending_agent_request) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (nm_streq(get_variant_state(value), "connecting")) { + return NM_ACT_STAGE_RETURN_POSTPONE; + } else { + /* This basically forgets that the connection was "assumed" + * as we can treat it like any connection triggered by a + * Network.Connect() call from now on. + */ + assumed_connection_progress_to_ip_config(self, FALSE); + return NM_ACT_STAGE_RETURN_SUCCESS; + } + } + + result = nm_device_iwd_agent_query(self, priv->pending_agent_request); + g_clear_object(&priv->pending_agent_request); + nm_assert(result); + + return NM_ACT_STAGE_RETURN_POSTPONE; + } + /* 802.1x networks that are not IWD Known Networks will definitely * fail, for other combinations we will let the Connect call fail * or ask us for any missing secrets through the Agent. @@ -2067,21 +2417,24 @@ schedule_periodic_scan(NMDeviceIwd *self, gboolean initial_scan) NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); guint interval; - /* Start scan immediately after a disconnect, mode change or - * device UP, otherwise wait 10 seconds. When connected, update - * AP list mainly on UI requests. + /* Automatically start a scan after a disconnect, mode change or device UP, + * otherwise scan periodically every 10 seconds if needed for NM's + * autoconnect. There's no need to scan When using IWD's autoconnect or + * when connected, we update the AP list on UI requests. * - * (initial_scan && disconnected) override priv->scanning below - * because of an IWD quirk where a device will often be in the - * autoconnect state and scanning at the time of our initial_scan, - * but our logic will then send it a Disconnect() causing IWD to - * exit autoconnect and interrupt the ongoing scan, meaning that - * we still want a new scan ASAP. + * (initial_scan && disconnected && !priv->iwd_autoconnect) override + * priv->scanning below because of an IWD quirk where a device will often + * be in the autoconnect state and scanning at the time of our initial_scan, + * but our logic will then send it a Disconnect() causing IWD to exit + * autoconnect and interrupt the ongoing scan, meaning that we still want + * a new scan ASAP. */ - if (!priv->can_scan || priv->scan_requested || priv->scanning || priv->current_ap) + if (!priv->can_scan || priv->scan_requested || priv->current_ap || priv->iwd_autoconnect) interval = -1; - else if (initial_scan) + else if (initial_scan && priv->scanning) interval = 0; + else if (priv->scanning) + interval = -1; else if (!priv->periodic_scan_id) interval = 10; else @@ -2103,7 +2456,8 @@ set_can_scan(NMDeviceIwd *self, gboolean can_scan) priv->can_scan = can_scan; - schedule_periodic_scan(self, TRUE); + if (!priv->iwd_autoconnect) + schedule_periodic_scan(self, TRUE); } static void @@ -2114,6 +2468,8 @@ device_state_changed(NMDevice * device, { NMDeviceIwd * self = NM_DEVICE_IWD(device); NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMSettingWireless * s_wireless; + const char * mode; switch (new_state) { case NM_DEVICE_STATE_UNMANAGED: @@ -2130,15 +2486,22 @@ device_state_changed(NMDevice * device, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); } break; - case NM_DEVICE_STATE_NEED_AUTH: - break; - case NM_DEVICE_STATE_IP_CHECK: - break; - case NM_DEVICE_STATE_ACTIVATED: - break; - case NM_DEVICE_STATE_FAILED: - break; case NM_DEVICE_STATE_DISCONNECTED: + if (old_state == NM_DEVICE_STATE_UNAVAILABLE) + initial_check_assume(self); + break; + case NM_DEVICE_STATE_IP_CONFIG: + s_wireless = + (NMSettingWireless *) nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS); + mode = nm_setting_wireless_get_mode(s_wireless); + if (!priv->periodic_update_id + && NM_IN_STRSET(mode, + NULL, + NM_SETTING_WIRELESS_MODE_INFRA, + NM_SETTING_WIRELESS_MODE_ADHOC)) { + priv->periodic_update_id = g_timeout_add_seconds(6, periodic_update_cb, self); + periodic_update(self); + } break; default: break; @@ -2267,23 +2630,110 @@ get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) static void state_changed(NMDeviceIwd *self, const char *new_state) { - NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); - NMDevice * device = NM_DEVICE(self); - NMDeviceState dev_state = nm_device_get_state(device); - gboolean nm_connection = FALSE; - gboolean can_connect = priv->nm_autoconnect; + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + NMDevice * device = NM_DEVICE(self); + NMDeviceState dev_state = nm_device_get_state(device); + gboolean nm_connection = priv->current_ap || priv->assumed_ac; + gboolean iwd_connection = FALSE; + NMWifiAP * ap = NULL; + gboolean can_connect = priv->nm_autoconnect; _LOGI(LOGD_DEVICE | LOGD_WIFI, "new IWD device state is %s", new_state); - if (dev_state >= NM_DEVICE_STATE_CONFIG && dev_state <= NM_DEVICE_STATE_ACTIVATED) - nm_connection = TRUE; + if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) { + gs_unref_variant GVariant *value = NULL; + const char * network_path_str; + nm_auto_ref_string NMRefString *network_path = NULL; + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "ConnectedNetwork"); + if (!value || !g_variant_is_of_type(value, G_VARIANT_TYPE_OBJECT_PATH)) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork property not cached or not an object path"); + return; + } + + iwd_connection = TRUE; + network_path_str = g_variant_get_string(value, NULL); + network_path = nm_ref_string_new(network_path_str); + ap = find_ap_by_supplicant_path(self, network_path); + + if (!ap) { + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "ConnectedNetwork points to an unknown Network %s", + network_path_str); + return; + } + } /* Don't allow scanning while connecting, disconnecting or roaming */ set_can_scan(self, NM_IN_STRSET(new_state, "connected", "disconnected")); priv->nm_autoconnect = FALSE; - if (NM_IN_STRSET(new_state, "connecting", "connected", "roaming")) { + if (nm_connection && iwd_connection && priv->current_ap && ap != priv->current_ap) { + gboolean switch_ap = priv->iwd_autoconnect && priv->assumed_ac; + + _LOGW(LOGD_DEVICE | LOGD_WIFI, + "IWD is connecting to the wrong AP, %s activation", + switch_ap ? "replacing" : "aborting"); + cleanup_association_attempt(self, !switch_ap); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + + if (switch_ap) + assume_connection(self, ap); + return; + } + + if (priv->iwd_autoconnect && iwd_connection) { + if (dev_state < NM_DEVICE_STATE_DISCONNECTED) + return; + + /* If IWD is in any state other than disconnected and the NMDevice is + * in DISCONNECTED then someone else, possibly IWD's autoconnect, has + * commanded an action and we need to update our NMDevice's state to + * match, including finding the NMSettingsConnection and NMWifiAP + * matching the network pointed to by Station.ConnectedNetwork. + * + * If IWD is in the connected state and we're in CONFIG, we only have + * to signal that the existing connection request has advanced to a new + * state. If the connection request came from NM, we must have used + * Network.Connect() so that method call's callback will update the + * connection request, otherwise we do it here. + * + * If IWD is disconnecting or just disconnected, the common code below + * (independent from priv->iwd_autoconnect) will handle this case. + * If IWD is disconnecting but we never saw a connection request in the + * first place (maybe because we're only startig up) we won't be + * setting up an NMActiveConnection just to put the NMDevice in the + * DEACTIVATING state and we ignore this case. + * + * If IWD was in the disconnected state and transitioned to + * "connecting" but we were already in NEED_AUTH because we handled an + * agent query -- IWD normally stays in "disconnected" until it has all + * the secrets -- we record this fact and remain in NEED_AUTH. + */ + if (!nm_connection) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "This is a new connection, 'assuming' it"); + assume_connection(self, ap); + return; + } + + if (priv->assumed_ac && dev_state >= NM_DEVICE_STATE_PREPARE + && dev_state < NM_DEVICE_STATE_IP_CONFIG + && NM_IN_STRSET(new_state, "connected", "roaming")) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Updating assumed activation state"); + assumed_connection_progress_to_ip_config(self, TRUE); + return; + } + + if (priv->assumed_ac) { + _LOGD(LOGD_DEVICE | LOGD_WIFI, "Clearing assumed activation timeout"); + nm_clear_g_source(&priv->assumed_ac_timeout); + return; + } + } else if (!priv->iwd_autoconnect && iwd_connection) { /* If we were connecting, do nothing, the confirmation of * a connection success is handled in the Device.Connect * method return callback. Otherwise, IWD must have connected @@ -2293,28 +2743,37 @@ state_changed(NMDeviceIwd *self, const char *new_state) if (nm_connection) return; - _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection success, asking IWD to disconnect"); + _LOGW(LOGD_DEVICE | LOGD_WIFI, "Unsolicited connection, asking IWD to disconnect"); send_disconnect(self); } else if (NM_IN_STRSET(new_state, "disconnecting", "disconnected")) { - /* Call Disconnect on the IWD device object to make sure it - * disables its own autoconnect. + /* If necessary, call Disconnect on the IWD device object to make sure + * it disables its autoconnect. */ - send_disconnect(self); + if (!priv->iwd_autoconnect) + send_disconnect(self); /* * If IWD is still handling the Connect call, let our Connect * callback for the dbus method handle the failure. The main - * reason we can't handle the failure here is because the method - * callback will have more information on the specific failure - * reason. + * reason we don't want to handle the failure here is because the + * method callback will have more information on the specific + * failure reason. + * + * If IWD is handling an autoconnect agent call, let the agent's + * Cancel() handler take care of this. */ - if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH)) + if (NM_IN_SET(dev_state, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_NEED_AUTH) + && !priv->assumed_ac) + return; + if (NM_IN_SET(dev_state, NM_DEVICE_STATE_NEED_AUTH) && priv->assumed_ac) return; - if (nm_connection) + if (nm_connection) { + cleanup_association_attempt(self, FALSE); nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT); + } } else if (!nm_streq(new_state, "unknown")) { _LOGE(LOGD_WIFI, "State %s unknown", new_state); return; @@ -2323,7 +2782,7 @@ state_changed(NMDeviceIwd *self, const char *new_state) /* Don't allow new connection until iwd exits disconnecting and no * Connect callback is pending. */ - if (NM_IN_STRSET(new_state, "disconnected")) { + if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) { priv->nm_autoconnect = TRUE; if (!can_connect) nm_device_emit_recheck_auto_activate(device); @@ -2345,7 +2804,7 @@ scanning_changed(NMDeviceIwd *self, gboolean new_scanning) if (!priv->scanning) { update_aps(self); - if (!priv->scan_requested) + if (!priv->scan_requested && !priv->iwd_autoconnect) schedule_periodic_scan(self, FALSE); } } @@ -2502,6 +2961,34 @@ device_properties_changed(GDBusProxy *proxy, powered_changed(self, new_bool); } +static void +config_changed(NMConfig * config, + NMConfigData * config_data, + NMConfigChangeFlags changes, + NMConfigData * old_data, + NMDeviceIwd * self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gboolean old_iwd_ac = priv->iwd_autoconnect; + + priv->iwd_autoconnect = + nm_config_data_get_device_config_boolean(config_data, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, + NM_DEVICE(self), + TRUE, + TRUE); + + if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) { + gs_unref_variant GVariant *value = NULL; + + if (!priv->iwd_autoconnect) + send_disconnect(self); + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + state_changed(self, get_variant_state(value)); + } +} + void nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object) { @@ -2526,6 +3013,8 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object) powered_changed(self, FALSE); priv->act_mode_switch = FALSE; + + g_signal_handlers_disconnect_by_func(nm_config_get(), config_changed, self); } if (!object) @@ -2588,6 +3077,13 @@ nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object) _notify(self, PROP_CAPABILITIES); } + /* Update iwd_autoconnect before any state_changed call */ + g_signal_connect(nm_config_get(), + NM_CONFIG_SIGNAL_CONFIG_CHANGED, + G_CALLBACK(config_changed), + self); + config_changed(NULL, NM_CONFIG_GET_DATA, 0, NULL, self); + g_variant_unref(value); value = g_dbus_proxy_get_cached_property(priv->dbus_device_proxy, "Powered"); powered = get_variant_boolean(value, "Powered"); @@ -2609,39 +3105,116 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) { NMDevice * device = NM_DEVICE(self); NMDeviceIwdPrivate * priv = NM_DEVICE_IWD_GET_PRIVATE(self); - NMActRequest * req; + NMDeviceState state = nm_device_get_state(device); const char * setting_name; const char * setting_key; gboolean replied; + NMWifiAP * ap; NMSecretAgentGetSecretsFlags get_secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION; + nm_auto_ref_string NMRefString *network_path = NULL; if (!invocation) { - NMActRequest *act_req = nm_device_get_act_request(device); + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); - if (!act_req) + if (!priv->wifi_secrets_id && !priv->pending_agent_request) return FALSE; + _LOGI(LOGD_WIFI, "IWD agent request is being cancelled"); wifi_secrets_cancel(self); - if (nm_device_get_state(device) == NM_DEVICE_STATE_NEED_AUTH) + if (state == NM_DEVICE_STATE_NEED_AUTH) nm_device_state_changed(device, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE); - /* The secrets request is being cancelled. Let the Network.Connect - * method call's callback handle the failure. + /* The secrets request is being cancelled. If we don't have an assumed + * connection than we've probably called Network.Connect and that method + * call's callback is going to handle the failure. And if the state was + * not "disconnected" then let the state change handler process the + * failure. */ + if (!priv->assumed_ac) + return TRUE; + + if (!nm_streq(get_variant_state(value), "disconnected")) + return TRUE; + + cleanup_association_attempt(self, FALSE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); return TRUE; } - req = nm_device_get_act_request(device); - if (!req || nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG) { - _LOGI(LOGD_WIFI, "IWD asked for secrets without explicit connect request"); - send_disconnect(self); + if (state > NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) { + _LOGW(LOGD_WIFI, "Can't handle the IWD agent request in current device state"); return FALSE; } + if (priv->wifi_secrets_id || priv->pending_agent_request) { + _LOGW(LOGD_WIFI, "There's already a pending agent request for this device"); + return FALSE; + } + + network_path = nm_ref_string_new(get_agent_request_network_path(invocation)); + ap = find_ap_by_supplicant_path(self, network_path); + if (!ap) { + _LOGW(LOGD_WIFI, "IWD Network object not found for the agent request"); + return FALSE; + } + + if (priv->assumed_ac) { + const char *ac_ap_path = nm_active_connection_get_specific_object(priv->assumed_ac); + + if (!nm_streq(ac_ap_path, nm_dbus_object_get_path(NM_DBUS_OBJECT(ap)))) { + _LOGW(LOGD_WIFI, + "Dropping an existing assumed connection to create a new one based on the IWD " + "agent request network parameter"); + + if (priv->current_ap) + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + + cleanup_association_attempt(self, FALSE); + priv->pending_agent_request = g_object_ref(invocation); + assume_connection(self, ap); + return TRUE; + } + + if (state != NM_DEVICE_STATE_CONFIG) { + _LOGI(LOGD_WIFI, "IWD agent request deferred until in CONFIG"); + priv->pending_agent_request = g_object_ref(invocation); + return TRUE; + } + + /* Otherwise handle as usual */ + } else if (!priv->current_ap) { + _LOGI(LOGD_WIFI, "IWD is asking for secrets without explicit connect request"); + + if (priv->iwd_autoconnect) { + priv->pending_agent_request = g_object_ref(invocation); + assume_connection(self, ap); + return TRUE; + } + + send_disconnect(self); + return FALSE; + } else if (priv->current_ap) { + if (priv->current_ap != ap) { + _LOGW(LOGD_WIFI, "IWD agent request for a wrong network object"); + cleanup_association_attempt(self, TRUE); + nm_device_state_changed(device, + NM_DEVICE_STATE_FAILED, + NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); + return FALSE; + } + + /* Otherwise handle as usual */ + } + if (!try_reply_agent_request(self, - nm_act_request_get_applied_connection(req), + nm_device_get_applied_connection(device), invocation, &setting_name, &setting_key, @@ -2661,7 +3234,7 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) * Connection timestamp is set after activation or after first * activation failure (to 0). */ - if (nm_settings_connection_get_timestamp(nm_act_request_get_settings_connection(req), NULL)) + if (nm_settings_connection_get_timestamp(nm_device_get_settings_connection(device), NULL)) get_secret_flags |= NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW; nm_device_state_changed(device, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NO_SECRETS); @@ -2675,16 +3248,11 @@ nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool ad { NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); NMWifiAP * ap = NULL; - NMWifiAP * tmp; bool recheck; nm_auto_ref_string NMRefString *bss_path = NULL; bss_path = nm_ref_string_new(g_dbus_proxy_get_object_path(network)); - c_list_for_each_entry (tmp, &priv->aps_lst_head, aps_lst) - if (nm_wifi_ap_get_supplicant_path(tmp) == bss_path) { - ap = tmp; - break; - } + ap = find_ap_by_supplicant_path(self, bss_path); /* We could schedule an update_aps(self) idle call here but up to IWD 1.9 * when a hidden network connection is attempted, that network is initially @@ -2811,6 +3379,13 @@ nm_device_iwd_class_init(NMDeviceIwdClass *klass) device_class->deactivate_async = deactivate_async; device_class->can_reapply_change = can_reapply_change; + /* Stage 1 needed only for the set_current_ap() call. Stage 2 is + * needed if we're assuming a connection still in the "connecting" + * state or on an agent request. + */ + device_class->act_stage1_prepare_also_for_external_or_assume = TRUE; + device_class->act_stage2_config_also_for_external_or_assume = TRUE; + device_class->state_changed = device_state_changed; obj_properties[PROP_MODE] = g_param_spec_uint(NM_DEVICE_IWD_MODE, diff --git a/src/nm-config.c b/src/nm-config.c index 1d7513439d..e9c88d06f9 100644 --- a/src/nm-config.c +++ b/src/nm-config.c @@ -884,6 +884,7 @@ static const ConfigGroup config_groups[] = { NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND, NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS, + NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT, NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE, NM_CONFIG_KEYFILE_KEY_STOP_MATCH, ), }, diff --git a/src/nm-config.h b/src/nm-config.h index 698482e37d..16701f41df 100644 --- a/src/nm-config.h +++ b/src/nm-config.h @@ -86,6 +86,7 @@ #define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_BACKEND "wifi.backend" #define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_SCAN_RAND_MAC_ADDRESS "wifi.scan-rand-mac-address" #define NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT "carrier-wait-timeout" +#define NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT "wifi.iwd.autoconnect" #define NM_CONFIG_KEYFILE_KEY_MATCH_DEVICE "match-device" #define NM_CONFIG_KEYFILE_KEY_STOP_MATCH "stop-match" From d09ba36f004e67d55ade57194b59bba25bd48222 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 5 Nov 2020 00:51:48 +0100 Subject: [PATCH 12/13] iwd: Roughly respect the NMDevice::autoconnect property Watch the NMDevice::autoconnect property to disable IWD autoconnect if requested by user. We have no way to re-enable it when the device is idle though. Make sure to not disable IWD's autoconnect in .deactivate() if not necessary. There's not much we can do if we have to call Station.Disconnect() but we can avoid calling it if unnecessary -- a slight optimization regardless of the autoconnect block flags. Fortunately NM and IWD block autoconnect on a manual deactivation in the same way (in MANAGED mode) and unblock it on an activation in the same way too (in MANAGED mode). Also if wifi.iwd.autoconnect is in use, unset NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT under the same conditions as IWD normally would. This could be made optional but with wifi.iwd.autoconnect by default we should follow IWD's autoconnect logic. --- src/devices/wifi/nm-device-iwd.c | 111 ++++++++++++++++++++++--------- 1 file changed, 81 insertions(+), 30 deletions(-) diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c index b54b42f51d..f6029ecfa6 100644 --- a/src/devices/wifi/nm-device-iwd.c +++ b/src/devices/wifi/nm-device-iwd.c @@ -553,6 +553,32 @@ reset_mode(NMDeviceIwd * self, user_data); } +static gboolean +get_variant_boolean(GVariant *v, const char *property) +{ + if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, + "Property %s not cached or not boolean type", + property); + + return FALSE; + } + + return g_variant_get_boolean(v); +} + +static const char * +get_variant_state(GVariant *v) +{ + if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) { + nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string"); + + return "unknown"; + } + + return g_variant_get_string(v, NULL); +} + static void deactivate(NMDevice *device) { @@ -562,6 +588,14 @@ deactivate(NMDevice *device) if (!priv->dbus_obj) return; + if (priv->dbus_station_proxy) { + gs_unref_variant GVariant *value = + g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + + if (NM_IN_STRSET(get_variant_state(value), "disconnecting", "disconnected")) + return; + } + cleanup_association_attempt(self, TRUE); priv->act_mode_switch = FALSE; @@ -1015,32 +1049,6 @@ complete_connection(NMDevice * device, return TRUE; } -static gboolean -get_variant_boolean(GVariant *v, const char *property) -{ - if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_BOOLEAN)) { - nm_log_warn(LOGD_DEVICE | LOGD_WIFI, - "Property %s not cached or not boolean type", - property); - - return FALSE; - } - - return g_variant_get_boolean(v); -} - -static const char * -get_variant_state(GVariant *v) -{ - if (!v || !g_variant_is_of_type(v, G_VARIANT_TYPE_STRING)) { - nm_log_warn(LOGD_DEVICE | LOGD_WIFI, "State property not cached or not a string"); - - return "unknown"; - } - - return g_variant_get_string(v, NULL); -} - static gboolean is_available(NMDevice *device, NMDeviceCheckDevAvailableFlags flags) { @@ -1554,7 +1562,10 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data) gs_free char * ssid = NULL; NMDeviceStateReason reason = NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED; GVariant * value; - gboolean disconnect = !priv->iwd_autoconnect; + gboolean disconnect; + + disconnect = !priv->iwd_autoconnect + || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL); variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); if (!variant) { @@ -2749,7 +2760,9 @@ state_changed(NMDeviceIwd *self, const char *new_state) /* If necessary, call Disconnect on the IWD device object to make sure * it disables its autoconnect. */ - if (!priv->iwd_autoconnect) + if ((!priv->iwd_autoconnect + || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)) + && !priv->wifi_secrets_id && !priv->pending_agent_request) send_disconnect(self); /* @@ -2938,6 +2951,16 @@ powered_changed(NMDeviceIwd *self, gboolean new_powered) g_variant_unref(value); update_aps(self); + + /* When a device is brought UP in station mode, including after a mode + * switch, IWD re-enables autoconnect. This is unlike NM's autoconnect + * where a mode change doesn't interfere with the + * BLOCKED_MANUAL_DISCONNECT flag. + */ + if (priv->iwd_autoconnect) { + nm_device_autoconnect_blocked_unset(NM_DEVICE(self), + NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL); + } } else { set_can_scan(self, FALSE); priv->scanning = FALSE; @@ -2981,7 +3004,9 @@ config_changed(NMConfig * config, if (old_iwd_ac != priv->iwd_autoconnect && priv->dbus_station_proxy && !priv->current_ap) { gs_unref_variant GVariant *value = NULL; - if (!priv->iwd_autoconnect) + if (!priv->iwd_autoconnect + && !nm_device_autoconnect_blocked_get(NM_DEVICE(self), + NM_DEVICE_AUTOCONNECT_BLOCKED_ALL)) send_disconnect(self); value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); @@ -3117,6 +3142,7 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) if (!invocation) { gs_unref_variant GVariant *value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + gboolean disconnect; if (!priv->wifi_secrets_id && !priv->pending_agent_request) return FALSE; @@ -3139,7 +3165,8 @@ nm_device_iwd_agent_query(NMDeviceIwd *self, GDBusMethodInvocation *invocation) if (!nm_streq(get_variant_state(value), "disconnected")) return TRUE; - cleanup_association_attempt(self, FALSE); + disconnect = nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL); + cleanup_association_attempt(self, disconnect); nm_device_state_changed(device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED); @@ -3290,6 +3317,27 @@ nm_device_iwd_network_add_remove(NMDeviceIwd *self, GDBusProxy *network, bool ad } } +static void +autoconnect_changed(NMDevice *device, GParamSpec *pspec, NMDeviceIwd *self) +{ + NMDeviceIwdPrivate *priv = NM_DEVICE_IWD_GET_PRIVATE(self); + gs_unref_variant GVariant *value = NULL; + + /* Note IWD normally remains in "disconnected" during a secret request + * and we don't want to interrupt it by calling Station.Disconnect(). + */ + if (!priv->dbus_station_proxy || !priv->iwd_autoconnect + || !nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL) + || priv->wifi_secrets_id || priv->pending_agent_request) + return; + + value = g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State"); + if (!nm_streq(get_variant_state(value), "disconnected")) + return; + + send_disconnect(self); +} + /*****************************************************************************/ static const char * @@ -3309,6 +3357,8 @@ nm_device_iwd_init(NMDeviceIwd *self) c_list_init(&priv->aps_lst_head); + g_signal_connect(self, "notify::" NM_DEVICE_AUTOCONNECT, G_CALLBACK(autoconnect_changed), self); + /* Make sure the manager is running */ (void) nm_iwd_manager_get(); } @@ -3338,6 +3388,7 @@ dispose(GObject *object) nm_clear_g_cancellable(&priv->cancellable); + g_signal_handlers_disconnect_by_func(self, autoconnect_changed, self); nm_device_iwd_set_dbus_object(self, NULL); G_OBJECT_CLASS(nm_device_iwd_parent_class)->dispose(object); From 8215340651f3ecfe8b5534d83db0f4fe7ac15618 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Thu, 5 Nov 2020 03:15:08 +0100 Subject: [PATCH 13/13] iwd: Update KnownNetwork.AutoConnect on NM connection changes Watch NMSettingConnection's changes using the NM_SETTINGS_CONNECTION_UPDATED_INTERNAL signal and update IWD KnownNetwork's AutoConnect property when NMSettingConnection's autoconnect property changes. We will not receive "notify::" NM_SETTING_CONNECTION_AUTOCONNECT signals normally because the NMConnection seems to be replaced with a new one in Update2() rather than its settings changing. --- src/devices/wifi/nm-iwd-manager.c | 73 ++++++++++++++++++++++++++++--- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/src/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c index 6651d9761a..2be208c774 100644 --- a/src/devices/wifi/nm-iwd-manager.c +++ b/src/devices/wifi/nm-iwd-manager.c @@ -16,6 +16,7 @@ #include "nm-wifi-utils.h" #include "nm-glib-aux/nm-random-utils.h" #include "settings/nm-settings.h" +#include "nm-std-aux/nm-dbus-compat.h" /*****************************************************************************/ @@ -88,7 +89,8 @@ G_DEFINE_TYPE(NMIwdManager, nm_iwd_manager, G_TYPE_OBJECT) /*****************************************************************************/ -static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn); +static void mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, + KnownNetworkData * data); /*****************************************************************************/ @@ -368,7 +370,7 @@ known_network_data_free(KnownNetworkData *network) return; g_object_unref(network->known_network); - mirror_connection_take_and_delete(network->mirror_connection); + mirror_connection_take_and_delete(network->mirror_connection, network); g_slice_free(KnownNetworkData, network); } @@ -409,6 +411,58 @@ set_device_dbus_object(NMIwdManager *self, GDBusProxy *proxy, GDBusObject *objec nm_device_iwd_set_dbus_object(NM_DEVICE_IWD(device), object); } +static void +known_network_update_cb(GObject *source, GAsyncResult *res, gpointer user_data) +{ + gs_unref_variant GVariant *variant = NULL; + gs_free_error GError *error = NULL; + + variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error); + if (!variant) { + nm_log_warn(LOGD_WIFI, + "Updating %s on IWD known network %s failed: %s", + (const char *) user_data, + g_dbus_proxy_get_object_path(G_DBUS_PROXY(source)), + error->message); + } +} + +static void +sett_conn_changed(NMSettingsConnection *sett_conn, guint update_reason, KnownNetworkData *data) +{ + NMSettingsConnectionIntFlags flags; + NMConnection * conn = nm_settings_connection_get_connection(sett_conn); + NMSettingConnection * s_conn = nm_connection_get_setting_connection(conn); + gboolean nm_autoconnectable = nm_setting_connection_get_autoconnect(s_conn); + gboolean iwd_autoconnectable = get_property_bool(data->known_network, "AutoConnect", TRUE); + + nm_assert(sett_conn == data->mirror_connection); + + if (iwd_autoconnectable == nm_autoconnectable) + return; + + /* If this is a generated connection it may be ourselves updating it */ + flags = nm_settings_connection_get_flags(data->mirror_connection); + if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) + return; + + nm_log_dbg(LOGD_WIFI, + "Updating AutoConnect on known network at %s based on connection %s", + g_dbus_proxy_get_object_path(data->known_network), + nm_settings_connection_get_id(data->mirror_connection)); + g_dbus_proxy_call(data->known_network, + DBUS_INTERFACE_PROPERTIES ".Set", + g_variant_new("(ssv)", + NM_IWD_KNOWN_NETWORK_INTERFACE, + "AutoConnect", + g_variant_new_boolean(nm_autoconnectable)), + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, + known_network_update_cb, + "AutoConnect"); +} + /* Look up an existing NMSettingsConnection for a network that has been * preprovisioned with an IWD config file or has been connected to before, * or create a new in-memory NMSettingsConnection object. This will let @@ -524,7 +578,7 @@ mirror_connection(NMIwdManager * self, * should try to copy the properties from it to IWD's Known Network * using the Properties DBus interface in case the user created an * NM connection before IWD appeared on the bus, or before IWD - * created its Known Network object. (TODO) + * created its Known Network object. */ if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) { NMConnection *tmp_conn = nm_settings_connection_get_connection(settings_connection); @@ -536,6 +590,9 @@ mirror_connection(NMIwdManager * self, autoconnectable, NULL); g_object_set(G_OBJECT(s_wifi), NM_SETTING_WIRELESS_HIDDEN, hidden, NULL); + } else { + KnownNetworkData data = {known_network, settings_connection}; + sett_conn_changed(settings_connection, 0, &data); } } @@ -646,7 +703,7 @@ mirror_connection(NMIwdManager * self, } static void -mirror_connection_take_and_delete(NMSettingsConnection *sett_conn) +mirror_connection_take_and_delete(NMSettingsConnection *sett_conn, KnownNetworkData *data) { NMSettingsConnectionIntFlags flags; @@ -660,6 +717,7 @@ mirror_connection_take_and_delete(NMSettingsConnection *sett_conn) if (NM_FLAGS_HAS(flags, NM_SETTINGS_CONNECTION_INT_FLAGS_NM_GENERATED)) nm_settings_connection_delete(sett_conn, FALSE); + g_signal_handlers_disconnect_by_data(sett_conn, data); g_object_unref(sett_conn); } @@ -731,7 +789,12 @@ interface_added(GDBusObjectManager *object_manager, NMSettingsConnection *sett_conn_old = data->mirror_connection; data->mirror_connection = nm_g_object_ref(sett_conn); - mirror_connection_take_and_delete(sett_conn_old); + mirror_connection_take_and_delete(sett_conn_old, data); + + g_signal_connect(sett_conn, + NM_SETTINGS_CONNECTION_UPDATED_INTERNAL, + G_CALLBACK(sett_conn_changed), + data); } return;