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"