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"