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 e59dafdefe..f6029ecfa6 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"
@@ -57,7 +59,8 @@ typedef struct {
guint periodic_update_id;
bool enabled : 1;
bool can_scan : 1;
- bool can_connect : 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,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 && !priv->iwd_autoconnect)
+ nm_device_emit_recheck_auto_activate(NM_DEVICE(self));
+
if (recheck_available_connections)
nm_device_recheck_available_connections(NM_DEVICE(self));
}
@@ -169,7 +177,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
@@ -184,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));
}
@@ -223,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
@@ -293,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)
@@ -381,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));
}
}
@@ -485,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
@@ -492,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);
@@ -520,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)
{
@@ -529,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;
@@ -604,27 +671,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
@@ -656,7 +709,9 @@ 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;
if (!NM_DEVICE_CLASS(nm_device_iwd_parent_class)
->check_connection_compatible(device, connection, error))
@@ -664,6 +719,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) {
@@ -693,8 +765,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");
@@ -780,11 +852,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);
@@ -838,7 +911,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,
@@ -851,6 +925,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,
@@ -863,20 +949,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,
@@ -886,16 +970,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) {
@@ -924,35 +1016,22 @@ 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) {
- 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;
}
- 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,
@@ -970,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)
{
@@ -1022,7 +1075,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
@@ -1202,7 +1255,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 +1264,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;
}
@@ -1219,6 +1272,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
*
@@ -1326,6 +1400,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,
@@ -1389,6 +1482,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;
@@ -1402,12 +1509,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
@@ -1443,11 +1559,13 @@ 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;
+ 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) {
@@ -1491,42 +1609,36 @@ 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;
- 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) {
- 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 (!priv->can_connect && nm_streq0(get_variant_state(value), "disconnected")) {
- priv->can_connect = true;
- nm_device_emit_recheck_auto_activate(device);
+ if (!priv->iwd_autoconnect && 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);
}
@@ -1561,10 +1673,7 @@ 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;
- const char * mode;
+ gs_free char * ssid = NULL;
variant = g_dbus_proxy_call_finish(G_DBUS_PROXY(source), res, &error);
if (!variant) {
@@ -1583,27 +1692,16 @@ 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);
- 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:
@@ -1617,14 +1715,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;
@@ -1646,44 +1743,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:
@@ -1820,6 +1919,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)
{
@@ -1918,6 +2248,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.
@@ -1935,7 +2301,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,
@@ -1945,11 +2311,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,
@@ -2058,21 +2428,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
@@ -2094,7 +2467,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
@@ -2105,6 +2479,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:
@@ -2121,15 +2497,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;
@@ -2261,51 +2644,149 @@ 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 = priv->current_ap || priv->assumed_ac;
gboolean iwd_connection = FALSE;
- gboolean can_connect = priv->can_connect;
+ 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)
- iwd_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->can_connect = FALSE;
+ 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
* 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");
+ _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
+ || nm_device_autoconnect_blocked_get(device, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL))
+ && !priv->wifi_secrets_id && !priv->pending_agent_request)
+ 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 (iwd_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;
@@ -2314,8 +2795,8 @@ 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")) {
- priv->can_connect = TRUE;
+ if (!priv->iwd_autoconnect && NM_IN_STRSET(new_state, "disconnected")) {
+ priv->nm_autoconnect = TRUE;
if (!can_connect)
nm_device_emit_recheck_auto_activate(device);
}
@@ -2336,7 +2817,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);
}
}
@@ -2470,11 +2951,21 @@ 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;
priv->scan_requested = FALSE;
- priv->can_connect = FALSE;
+ priv->nm_autoconnect = FALSE;
cleanup_association_attempt(self, FALSE);
remove_all_aps(self);
}
@@ -2493,6 +2984,36 @@ 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
+ && !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");
+ state_changed(self, get_variant_state(value));
+ }
+}
+
void
nm_device_iwd_set_dbus_object(NMDeviceIwd *self, GDBusObject *object)
{
@@ -2517,6 +3038,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)
@@ -2579,6 +3102,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");
@@ -2600,22 +3130,118 @@ 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;
- 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 (!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;
+
+ _LOGI(LOGD_WIFI, "IWD agent request is being cancelled");
+ wifi_secrets_cancel(self);
+
+ 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. 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;
+
+ 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);
+ return TRUE;
+ }
+
+ 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,
@@ -2635,7 +3261,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);
@@ -2649,16 +3275,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
@@ -2669,7 +3290,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) {
@@ -2696,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 *
@@ -2715,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();
}
@@ -2744,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);
@@ -2785,6 +3430,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/devices/wifi/nm-iwd-manager.c b/src/devices/wifi/nm-iwd-manager.c
index 402480d279..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"
/*****************************************************************************/
@@ -39,6 +40,7 @@ typedef struct {
guint agent_id;
char * agent_path;
GHashTable * known_networks;
+ NMDeviceIwd * last_agent_call_device;
} NMIwdManagerPrivate;
struct _NMIwdManager {
@@ -87,7 +89,8 @@ 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,
+ KnownNetworkData * data);
/*****************************************************************************/
@@ -117,6 +120,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)
{
@@ -178,6 +196,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 +234,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 +268,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)
@@ -329,7 +370,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, network);
g_slice_free(KnownNetworkData, network);
}
@@ -370,14 +411,72 @@ 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.
+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
+ * 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;
@@ -385,43 +484,126 @@ 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;
+ 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;
- gs_free char * ssid_name = NULL;
NMSettingWireless * s_wifi;
- NMSetting8021x * s_8021x;
- gboolean external = FALSE;
- guint i;
+ const guint8 * ssid_bytes;
+ gsize ssid_len2;
- security = nm_wifi_connection_get_iwd_security(conn, NULL);
- if (security != NM_IWD_NETWORK_SECURITY_8021X)
+ if (!nm_wifi_connection_get_iwd_ssid_and_security(conn, NULL, &security))
+ continue;
+
+ if (security != id->security)
continue;
s_wifi = nm_connection_get_setting_wireless(conn);
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 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, 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.
+ */
+ 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);
+ } else {
+ KnownNetworkData data = {known_network, settings_connection};
+ sett_conn_changed(settings_connection, 0, &data);
+ }
+ }
+
+ 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
@@ -438,58 +620,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, strlen(name));
+ 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;
}
@@ -497,7 +703,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, KnownNetworkData *data)
{
NMSettingsConnectionIntFlags flags;
@@ -511,6 +717,7 @@ mirror_8021x_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);
}
@@ -540,6 +747,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;
@@ -561,28 +769,33 @@ 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, data);
+
+ g_signal_connect(sett_conn,
+ NM_SETTINGS_CONNECTION_UPDATED_INTERNAL,
+ G_CALLBACK(sett_conn_changed),
+ data);
+ }
return;
}
@@ -685,42 +898,47 @@ 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;
- gboolean mapped;
- KnownNetworkData * data;
- KnownNetworkId id;
- gs_free char * ssid_str = NULL;
+ 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;
- 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;
- 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)
@@ -820,7 +1038,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;
@@ -839,6 +1078,73 @@ 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;
+}
+
+/* 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)
{
@@ -894,6 +1200,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);
@@ -930,6 +1237,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)
{
@@ -955,6 +1303,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 +1346,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);
}
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-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);
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..e6cec37d4a 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__ */
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"