summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Zaborowski <andrew.zaborowski@intel.com>2020-10-30 13:27:41 +0100
committerThomas Haller <thaller@redhat.com>2020-11-12 15:38:02 +0100
commit6a9f33e6475c04dbed75b6d6f07883324e97afae (patch)
tree574548767383b677f0826a4b0a0833d38cadb3b0
parentfde2757b2d1a8081c3d13daa02fcc3e0d5a9a618 (diff)
downloadNetworkManager-6a9f33e6475c04dbed75b6d6f07883324e97afae.tar.gz
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, I'm not sure if that was designed only for when NM starts or also when a connection is made "under" it dynamically, but in any case this *could* be used for our use case here. However there were a few minor reasons I didn't want to use it, which are listed in the comment in assume_connection(). Those can be fixed or improved in NMManager / NMDevice and NMDeviceIiwd could then start using that API maybe, though there's really not much difference for NMDeviceIwd if that API was used. For now I think it's fine if we create a normal "managed"-type connection.
-rw-r--r--src/devices/wifi/nm-device-iwd.c662
-rw-r--r--src/nm-config.c1
-rw-r--r--src/nm-config.h1
3 files changed, 608 insertions, 56 deletions
diff --git a/src/devices/wifi/nm-device-iwd.c b/src/devices/wifi/nm-device-iwd.c
index 8cd7091e3a..ae4f2c89cd 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;
@@ -1522,13 +1620,13 @@ network_connect_cb(GObject *source, GAsyncResult *res, gpointer user_data)
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) {
@@ -1825,6 +1923,241 @@ 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;
+ NMAuthSubject * subject;
+ NMActiveConnection * ac;
+ 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);
+ g_object_unref(subject);
+
+ 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);
+ NMActiveConnection *ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(device));
+
+ wifi_secrets_cancel(self);
+ nm_clear_g_source(&priv->assumed_ac_timeout);
+
+ /* Reset the activation type to MANAGED here, don't wait until the
+ * ACTIVATED state because NM is doing the rest of the activation so
+ * logically this is a normal connection from now on.
+ */
+ g_object_set(G_OBJECT(ac),
+ NM_ACTIVE_CONNECTION_INT_ACTIVATION_TYPE,
+ NM_ACTIVATION_TYPE_MANAGED,
+ NULL);
+ 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 *value =
+ g_dbus_proxy_get_cached_property(priv->dbus_station_proxy, "State");
+
+ if (!NM_IN_STRSET(get_variant_state(value), "connecting", "connected", "roaming"))
+ return;
+
+ if (!priv->iwd_autoconnect) {
+ send_disconnect(self);
+ return;
+ }
+
+ g_variant_unref(value);
+ 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;
+ }
+
+ 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;
+ }
+
+ _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 +2256,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, TRUE);
+ 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 +2436,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 +2475,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
@@ -2139,6 +2512,8 @@ device_state_changed(NMDevice * device,
case NM_DEVICE_STATE_FAILED:
break;
case NM_DEVICE_STATE_DISCONNECTED:
+ if (old_state == NM_DEVICE_STATE_UNAVAILABLE)
+ initial_check_assume(self);
break;
default:
break;
@@ -2267,23 +2642,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,13 +2755,14 @@ 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
@@ -2308,13 +2771,16 @@ state_changed(NMDeviceIwd *self, const char *new_state)
* callback will have more information on the specific failure
* reason.
*/
- 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_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 +2789,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 +2811,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);
}
}
@@ -2609,41 +3075,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");
+ 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,
@@ -2663,7 +3204,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);
@@ -2677,16 +3218,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
@@ -2743,6 +3279,13 @@ nm_device_iwd_init(NMDeviceIwd *self)
c_list_init(&priv->aps_lst_head);
+ priv->iwd_autoconnect =
+ nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
+ NM_CONFIG_KEYFILE_KEY_DEVICE_WIFI_IWD_AUTOCONNECT,
+ NM_DEVICE(self),
+ TRUE,
+ TRUE);
+
/* Make sure the manager is running */
(void) nm_iwd_manager_get();
}
@@ -2813,6 +3356,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"