summary refs log tree commit diff
path: root/src/core/devices/wifi
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/devices/wifi')
-rw-r--r--src/core/devices/wifi/nm-device-iwd.c33
-rw-r--r--src/core/devices/wifi/nm-device-wifi.c174
-rw-r--r--src/core/devices/wifi/nm-wifi-ap.c18
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.c40
-rw-r--r--src/core/devices/wifi/nm-wifi-utils.h4
-rw-r--r--src/core/devices/wifi/tests/test-devices-wifi.c46
6 files changed, 268 insertions, 47 deletions
diff --git a/src/core/devices/wifi/nm-device-iwd.c b/src/core/devices/wifi/nm-device-iwd.c
index fa6e2f9d..94b9a7d7 100644
--- a/src/core/devices/wifi/nm-device-iwd.c
+++ b/src/core/devices/wifi/nm-device-iwd.c
@@ -2270,6 +2270,37 @@ add_new:
     return NM_ACT_STAGE_RETURN_SUCCESS;
 }
 
+static void
+set_powersave(NMDevice *device)
+{
+    NMDeviceIwd               *self = NM_DEVICE_IWD(device);
+    NMSettingWireless         *s_wireless;
+    NMSettingWirelessPowersave val;
+
+    s_wireless = nm_device_get_applied_setting(device, NM_TYPE_SETTING_WIRELESS);
+
+    g_return_if_fail(s_wireless);
+
+    val = nm_setting_wireless_get_powersave(s_wireless);
+    if (val == NM_SETTING_WIRELESS_POWERSAVE_DEFAULT) {
+        val = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
+                                                          "wifi.powersave",
+                                                          device,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_ENABLE,
+                                                          NM_SETTING_WIRELESS_POWERSAVE_IGNORE);
+    }
+
+    _LOGT(LOGD_WIFI, "powersave is set to %u", (unsigned) val);
+
+    if (val == NM_SETTING_WIRELESS_POWERSAVE_IGNORE)
+        return;
+
+    nm_platform_wifi_set_powersave(nm_device_get_platform(device),
+                                   nm_device_get_ifindex(device),
+                                   val == NM_SETTING_WIRELESS_POWERSAVE_ENABLE);
+}
+
 static NMActStageReturn
 act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 {
@@ -2297,6 +2328,8 @@ act_stage2_config(NMDevice *device, NMDeviceStateReason *out_failure_reason)
             goto out_fail;
         }
 
+        set_powersave(device);
+
         /* With priv->iwd_autoconnect we have to let IWD handle retries for
          * infrastructure networks.  IWD will not necessarily retry the same
          * network after a failure but it will likely go into an autoconnect
diff --git a/src/core/devices/wifi/nm-device-wifi.c b/src/core/devices/wifi/nm-device-wifi.c
index b41ed5e1..b836c1e8 100644
--- a/src/core/devices/wifi/nm-device-wifi.c
+++ b/src/core/devices/wifi/nm-device-wifi.c
@@ -194,6 +194,9 @@ static void supplicant_iface_notify_p2p_available(NMSupplicantInterface *iface,
 static void supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface,
                                                         NMDeviceWifi          *self);
 
+static void supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface,
+                                                        NMDeviceWifi          *self);
+
 static void periodic_update(NMDeviceWifi *self);
 
 static void ap_add_remove(NMDeviceWifi *self,
@@ -631,6 +634,10 @@ supplicant_interface_acquire_cb(NMSupplicantManager         *supplicant_manager,
                      NM_SUPPLICANT_INTERFACE_PSK_MISMATCH,
                      G_CALLBACK(supplicant_iface_notify_wpa_psk_mismatch_cb),
                      self);
+    g_signal_connect(priv->sup_iface,
+                     NM_SUPPLICANT_INTERFACE_SAE_MISMATCH,
+                     G_CALLBACK(supplicant_iface_notify_wpa_sae_mismatch_cb),
+                     self);
 
     _scan_notify_is_scanning(self);
 
@@ -2191,21 +2198,16 @@ supplicant_iface_wps_credentials_cb(NMSupplicantInterface *iface,
 
     val_key = g_variant_lookup_value(credentials, "Key", G_VARIANT_TYPE_BYTESTRING);
     if (val_key) {
-        char psk[64];
+        char psk[65];
 
         array = g_variant_get_fixed_array(val_key, &psk_len, 1);
-        if (psk_len >= 8 && psk_len <= 63) {
-            memcpy(psk, array, psk_len);
-            psk[psk_len] = '\0';
-            if (g_utf8_validate(psk, psk_len, NULL)) {
-                secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]",
-                                               NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
-                                               NM_SETTING_WIRELESS_SECURITY_PSK,
-                                               psk);
-                g_variant_ref_sink(secrets);
-            }
-        }
-        if (!secrets)
+        if (nm_wifi_utils_wps_key_to_psk((const guint8 *) array, psk_len, &psk)) {
+            secrets = g_variant_new_parsed("[{%s, [{%s, <%s>}]}]",
+                                           NM_SETTING_WIRELESS_SECURITY_SETTING_NAME,
+                                           NM_SETTING_WIRELESS_SECURITY_PSK,
+                                           psk);
+            g_variant_ref_sink(secrets);
+        } else
             _LOGW(LOGD_DEVICE | LOGD_WIFI, "WPS: ignore invalid PSK");
     }
 
@@ -2244,6 +2246,26 @@ wps_timeout_cb(gpointer user_data)
     return G_SOURCE_REMOVE;
 }
 
+static gboolean
+wifi_connection_is_new(NMDeviceWifi *self)
+{
+    NMDevice             *device = NM_DEVICE(self);
+    NMActRequest         *req;
+    NMSettingsConnection *connection;
+    guint64               timestamp = 0;
+
+    req = nm_device_get_act_request(device);
+    g_return_val_if_fail(NM_IS_ACT_REQUEST(req), TRUE);
+
+    connection = nm_act_request_get_settings_connection(req);
+    g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(connection), TRUE);
+
+    if (nm_settings_connection_get_timestamp(connection, &timestamp) && timestamp != 0)
+        return FALSE;
+
+    return TRUE;
+}
+
 static void
 wifi_secrets_get_secrets(NMDeviceWifi                *self,
                          const char                  *setting_name,
@@ -2398,10 +2420,11 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
                               NMSupplicantInterfaceState old_state,
                               int                        disconnect_reason)
 {
-    NMDevice     *device = NM_DEVICE(self);
-    NMActRequest *req;
-    const char   *setting_name = NULL;
-    gboolean      handled      = FALSE;
+    NMDevice                    *device = NM_DEVICE(self);
+    NMActRequest                *req;
+    const char                  *setting_name = NULL;
+    NMSecretAgentGetSecretsFlags secret_flags = NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                                | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
 
     g_return_val_if_fail(new_state == NM_SUPPLICANT_INTERFACE_STATE_DISCONNECTED, FALSE);
 
@@ -2411,8 +2434,7 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
     req = nm_device_get_act_request(NM_DEVICE(self));
     g_return_val_if_fail(req != NULL, FALSE);
 
-    if (need_new_8021x_secrets(self, old_state, &setting_name)
-        || need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) {
+    if (need_new_8021x_secrets(self, old_state, &setting_name)) {
         nm_act_request_clear_secrets(req);
 
         _LOGI(LOGD_DEVICE | LOGD_WIFI,
@@ -2422,14 +2444,54 @@ handle_8021x_or_psk_auth_fail(NMDeviceWifi              *self,
         nm_device_state_changed(device,
                                 NM_DEVICE_STATE_NEED_AUTH,
                                 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
-        wifi_secrets_get_secrets(self,
-                                 setting_name,
-                                 NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
-                                     | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
-        handled = TRUE;
+        wifi_secrets_get_secrets(self, setting_name, secret_flags);
+        return TRUE;
+    }
+
+    if (need_new_wpa_psk(self, old_state, disconnect_reason, &setting_name)) {
+        nm_act_request_clear_secrets(req);
+        cleanup_association_attempt(self, TRUE);
+
+        if (wifi_connection_is_new(self)) {
+            _LOGI(LOGD_DEVICE | LOGD_WIFI,
+                  "Activation: (wifi) new connection disconnected during association, asking for "
+                  "new key");
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_NEED_AUTH,
+                                    NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+            wifi_secrets_get_secrets(self, setting_name, secret_flags);
+            return TRUE;
+        }
+
+        if (!nm_device_auth_retries_try_next(device)) {
+            nm_device_state_changed(device,
+                                    NM_DEVICE_STATE_FAILED,
+                                    NM_DEVICE_STATE_REASON_NO_SECRETS);
+            return TRUE;
+        }
+
+        if (nm_device_auth_retries_has_next(device)) {
+            secret_flags &= ~NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW;
+            _LOGI(
+                LOGD_DEVICE | LOGD_WIFI,
+                "Activation: (wifi) disconnected during association, reauthenticating connection");
+        } else {
+            _LOGI(LOGD_DEVICE | LOGD_WIFI,
+                  "Activation: (wifi) disconnected during association, asking for new key");
+        }
+
+        nm_device_state_changed(device,
+                                NM_DEVICE_STATE_NEED_AUTH,
+                                NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+        wifi_secrets_get_secrets(self, setting_name, secret_flags);
+
+        return TRUE;
     }
 
-    return handled;
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) disconnected during association, retrying connection");
+
+    return FALSE;
 }
 
 static gboolean
@@ -2861,6 +2923,12 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi
     if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
         return;
 
+    if (!wifi_connection_is_new(self) && nm_device_auth_retries_has_next(device)) {
+        _LOGI(LOGD_DEVICE | LOGD_WIFI,
+              "Activation: (wifi) psk mismatch reported by supplicant, retrying connection");
+        return;
+    }
+
     _LOGI(LOGD_DEVICE | LOGD_WIFI,
           "Activation: (wifi) psk mismatch reported by supplicant, asking for new key");
 
@@ -2879,6 +2947,34 @@ supplicant_iface_notify_wpa_psk_mismatch_cb(NMSupplicantInterface *iface, NMDevi
                                  | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
 }
 
+static void
+supplicant_iface_notify_wpa_sae_mismatch_cb(NMSupplicantInterface *iface, NMDeviceWifi *self)
+{
+    NMDevice     *device = NM_DEVICE(self);
+    NMActRequest *req;
+    const char   *setting_name = NM_SETTING_WIRELESS_SECURITY_SETTING_NAME;
+
+    if (nm_device_get_state(device) != NM_DEVICE_STATE_CONFIG)
+        return;
+
+    _LOGI(LOGD_DEVICE | LOGD_WIFI,
+          "Activation: (wifi) SAE password mismatch reported by supplicant, asking for new key");
+
+    req = nm_device_get_act_request(NM_DEVICE(self));
+    g_return_if_fail(req != NULL);
+
+    nm_act_request_clear_secrets(req);
+
+    cleanup_association_attempt(self, TRUE);
+    nm_device_state_changed(device,
+                            NM_DEVICE_STATE_NEED_AUTH,
+                            NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
+    wifi_secrets_get_secrets(self,
+                             setting_name,
+                             NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
+                                 | NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
+}
+
 /*
  * supplicant_connection_timeout_cb
  *
@@ -3222,8 +3318,19 @@ act_stage1_prepare(NMDevice *device, NMDeviceStateReason *out_failure_reason)
 static void
 ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP *ap)
 {
-    guint32     a_freqs[]  = {5180, 5200, 5220, 5745, 5765, 5785, 5805, 0};
-    guint32     bg_freqs[] = {2412, 2437, 2462, 2472, 0};
+    guint32     freqs_a[]    = {5180, /* only U-NII-1 channels: non-DFS and available everywhere */
+                                5200,
+                                5220,
+                                5240,
+                                0};
+    guint32     freqs_bg[]   = {2412, 2437, 2462, 2472, 0};
+    guint32     freqs_6ghz[] = {5975, /* only U-NII-5 PSC channels, for better compatibility */
+                                6055,
+                                6135,
+                                6215,
+                                6295,
+                                6375,
+                                0};
     guint32    *rnd_freqs;
     guint       rnd_freqs_len;
     NMDevice   *device = NM_DEVICE(self);
@@ -3234,7 +3341,7 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
     guint       l;
 
     nm_assert(ap);
-    nm_assert(NM_IN_STRSET(band, NULL, "a", "bg"));
+    nm_assert(NM_IN_STRSET(band, NULL, "a", "bg", "6GHz"));
 
     if (nm_wifi_ap_get_freq(ap))
         return;
@@ -3268,11 +3375,14 @@ ensure_hotspot_frequency(NMDeviceWifi *self, NMSettingWireless *s_wifi, NMWifiAP
     }
 
     if (nm_streq0(band, "a")) {
-        rnd_freqs     = a_freqs;
-        rnd_freqs_len = G_N_ELEMENTS(a_freqs) - 1;
+        rnd_freqs     = freqs_a;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_a) - 1;
+    } else if (nm_streq0(band, "6GHz")) {
+        rnd_freqs     = freqs_6ghz;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_6ghz) - 1;
     } else {
-        rnd_freqs     = bg_freqs;
-        rnd_freqs_len = G_N_ELEMENTS(bg_freqs) - 1;
+        rnd_freqs     = freqs_bg;
+        rnd_freqs_len = G_N_ELEMENTS(freqs_bg) - 1;
     }
 
     /* shuffle the frequencies (inplace). The idea is to choose
diff --git a/src/core/devices/wifi/nm-wifi-ap.c b/src/core/devices/wifi/nm-wifi-ap.c
index ceb954b7..3e758972 100644
--- a/src/core/devices/wifi/nm-wifi-ap.c
+++ b/src/core/devices/wifi/nm-wifi-ap.c
@@ -574,16 +574,6 @@ nm_wifi_ap_to_string(const NMWifiAP *self, char *str_buf, gulong buf_len, gint64
     return str_buf;
 }
 
-static guint
-freq_to_band(guint32 freq)
-{
-    if (freq >= 4915 && freq <= 5825)
-        return 5;
-    else if (freq >= 2412 && freq <= 2484)
-        return 2;
-    return 0;
-}
-
 gboolean
 nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 {
@@ -631,12 +621,12 @@ nm_wifi_ap_check_compatible(NMWifiAP *self, NMConnection *connection)
 
     band = nm_setting_wireless_get_band(s_wireless);
     if (band) {
-        guint ap_band = freq_to_band(priv->freq);
+        const char *ap_band = nm_wifi_freq_to_band_prop(priv->freq);
 
-        if (!strcmp(band, "a") && ap_band != 5)
-            return FALSE;
-        else if (!strcmp(band, "bg") && ap_band != 2)
+        if (!nm_streq(band, ap_band))
             return FALSE;
+
+        return TRUE;
     }
 
     channel = nm_setting_wireless_get_channel(s_wireless);
diff --git a/src/core/devices/wifi/nm-wifi-utils.c b/src/core/devices/wifi/nm-wifi-utils.c
index 332352ab..2ee4ec2e 100644
--- a/src/core/devices/wifi/nm-wifi-utils.c
+++ b/src/core/devices/wifi/nm-wifi-utils.c
@@ -639,7 +639,7 @@ nm_wifi_utils_complete_connection(GBytes       *ap_ssid,
                 chan_valid = FALSE;
             }
 
-            band = nm_utils_wifi_freq_to_band(ap_freq);
+            band = nm_wifi_freq_to_band_prop(ap_freq);
             if (band) {
                 g_object_set(s_wifi, NM_SETTING_WIRELESS_BAND, band, NULL);
             } else {
@@ -890,6 +890,28 @@ nm_wifi_utils_is_manf_default_ssid(GBytes *ssid)
     return FALSE;
 }
 
+/* Convert a WPS "Key" credential into a PSK string. The key is either an
+ * 8..63 character passphrase or a 64 character hexadecimal PSK. The actual
+ * WPA-PSK validity check is shared with nm_utils_wpa_psk_valid(). */
+gboolean
+nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65])
+{
+    if (key_len > 64)
+        return FALSE;
+    if (key_len < 64 && !g_utf8_validate((const char *) key, key_len, NULL))
+        return FALSE;
+
+    memcpy(*out_psk, key, key_len);
+    (*out_psk)[key_len] = '\0';
+
+    /* An embedded NUL would make nm_utils_wpa_psk_valid() see a truncated
+     * string, so reject it explicitly. */
+    if (strlen(*out_psk) != key_len)
+        return FALSE;
+
+    return nm_utils_wpa_psk_valid(*out_psk);
+}
+
 /* To be used for connections where the SSID has been validated before */
 gboolean
 nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
@@ -1929,3 +1951,19 @@ nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b)
     return a->source == b->source && a->sink == b->sink && a->port == b->port
            && a->has_audio == b->has_audio && a->has_uibc == b->has_uibc && a->has_cp == b->has_cp;
 }
+
+const char *
+nm_wifi_freq_to_band_prop(guint32 freq)
+{
+    switch (nm_utils_wifi_freq_to_band(freq)) {
+    case NM_WIFI_BAND_2_4_GHZ:
+        return "bg";
+    case NM_WIFI_BAND_5_GHZ:
+        return "a";
+    case NM_WIFI_BAND_6_GHZ:
+        return "6GHz";
+    default:
+    case NM_WIFI_BAND_UNKNOWN:
+        return NULL;
+    }
+}
diff --git a/src/core/devices/wifi/nm-wifi-utils.h b/src/core/devices/wifi/nm-wifi-utils.h
index 1d46a900..30920396 100644
--- a/src/core/devices/wifi/nm-wifi-utils.h
+++ b/src/core/devices/wifi/nm-wifi-utils.h
@@ -42,6 +42,8 @@ gboolean nm_wifi_utils_complete_connection(GBytes       *ssid,
 
 gboolean nm_wifi_utils_is_manf_default_ssid(GBytes *ssid);
 
+gboolean nm_wifi_utils_wps_key_to_psk(const guint8 *key, gsize key_len, char (*out_psk)[65]);
+
 gboolean nm_wifi_connection_get_iwd_ssid_and_security(NMConnection         *connection,
                                                       char                **ssid,
                                                       NMIwdNetworkSecurity *security);
@@ -56,4 +58,6 @@ bool    nm_wifi_utils_parse_wfd_ies(GBytes *ies, NMIwdWfdInfo *out_wfd);
 GBytes *nm_wifi_utils_build_wfd_ies(const NMIwdWfdInfo *wfd);
 bool    nm_wifi_utils_wfd_info_eq(const NMIwdWfdInfo *a, const NMIwdWfdInfo *b);
 
+const char *nm_wifi_freq_to_band_prop(guint32 freq);
+
 #endif /* __NM_WIFI_UTILS_H__ */
diff --git a/src/core/devices/wifi/tests/test-devices-wifi.c b/src/core/devices/wifi/tests/test-devices-wifi.c
index a52696ea..b50e2626 100644
--- a/src/core/devices/wifi/tests/test-devices-wifi.c
+++ b/src/core/devices/wifi/tests/test-devices-wifi.c
@@ -1459,6 +1459,50 @@ test_ssids_options_to_ptrarray(void)
 
 /*****************************************************************************/
 
+static void
+test_wps_key_to_psk(void)
+{
+    static const struct {
+        const char *key;
+        gsize       key_len;
+        gboolean    valid;
+    } cases[] = {
+        {"12345678", 8, TRUE},
+        {"supersecret123", 14, TRUE},
+        {"caf\xc3\xa9_key", 9, TRUE},
+        {"123456789012345678901234567890123456789012345678901234567890123", 63, TRUE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef", 64, TRUE},
+        {"0123456789ABCDEF0123456789abcdef0123456789abcdef0123456789abcdeF", 64, TRUE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdeg", 64, FALSE},
+        {"", 0, FALSE},
+        {"1234567", 7, FALSE},
+        {"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0", 65, FALSE},
+        {"abc\xff"
+         "def123",
+         10,
+         FALSE},
+        {"0123456789abcdef0123456789abcdef\0"
+         "23456789abcdef0123456789abcdef0",
+         64,
+         FALSE},
+    };
+    gsize i;
+
+    for (i = 0; i < G_N_ELEMENTS(cases); i++) {
+        char     psk[65];
+        gboolean ok;
+
+        ok = nm_wifi_utils_wps_key_to_psk((const guint8 *) cases[i].key, cases[i].key_len, &psk);
+        g_assert_cmpint(ok, ==, cases[i].valid);
+        if (ok) {
+            g_assert_cmpmem(psk, cases[i].key_len, cases[i].key, cases[i].key_len);
+            g_assert_cmpint(psk[cases[i].key_len], ==, '\0');
+        }
+    }
+}
+
+/*****************************************************************************/
+
 NMTST_DEFINE();
 
 int
@@ -1605,5 +1649,7 @@ main(int argc, char **argv)
 
     g_test_add_func("/wifi/ssids_options_to_ptrarray", test_ssids_options_to_ptrarray);
 
+    g_test_add_func("/wifi/wps_key_to_psk", test_wps_key_to_psk);
+
     return g_test_run();
 }