]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
AP: Always verify and fix channel switch configuration
authorBenjamin Berg <benjamin.berg@intel.com>
Thu, 31 Jul 2025 13:57:00 +0000 (15:57 +0200)
committerJouni Malinen <j@w1.fi>
Mon, 6 Oct 2025 11:00:47 +0000 (14:00 +0300)
On the hostapd control interface, the CHAN_SWITCH command would do some
extra verification and also ensured that HT/VHT/HE/EHT is not being
disabled (as this is correctly supported currently). However, these
checks were missing on the wpa_supplicant side which would be used,
e.g., when a CHAN_SWITCH command is done on a GO interface.

Move these checks into the hostapd_parse_csa_settings() function to
ensure that they are correctly done in both cases. This fixes issues
where for example HT is getting accidentally disabled in a test, causing
a subsequent mac80211 disconnect. Some tests may occasionally trigger
this problem, but usually pass due to timing (this will be addressed by
a later commit).

Signed-off-by: Benjamin Berg <benjamin.berg@intel.com>
Reviewed-by: Andrei Otcheretianski <andrei.otcheretianski@intel.com>
hostapd/ctrl_iface.c
src/ap/ctrl_iface_ap.c
src/ap/ctrl_iface_ap.h
wpa_supplicant/ap.c

index e20609c367f1e441762ff15f3a4c1bccbe030e2d..3f5a50fd3d686e3a9c08e4b78072fe6eb2f4f053 100644 (file)
@@ -38,7 +38,6 @@
 #endif /* CONFIG_DPP */
 #include "common/wpa_ctrl.h"
 #include "common/ptksa_cache.h"
-#include "common/hw_features_common.h"
 #include "common/nan_de.h"
 #include "crypto/tls.h"
 #include "drivers/driver.h"
@@ -2429,255 +2428,11 @@ static int hostapd_ctrl_register_frame(struct hostapd_data *hapd,
 #endif /* CONFIG_TESTING_OPTIONS */
 
 
-#ifdef NEED_AP_MLME
-
-static struct hostapd_hw_modes * get_target_hw_mode(struct hostapd_iface *iface,
-                                                   int freq)
-{
-       int i;
-       enum hostapd_hw_mode target_mode;
-       bool is_6ghz = is_6ghz_freq(freq);
-
-       if (freq < 4000)
-               target_mode = HOSTAPD_MODE_IEEE80211G;
-       else if (freq > 50000)
-               target_mode = HOSTAPD_MODE_IEEE80211AD;
-       else
-               target_mode = HOSTAPD_MODE_IEEE80211A;
-
-       for (i = 0; i < iface->num_hw_features; i++) {
-               struct hostapd_hw_modes *mode;
-
-               mode = &iface->hw_features[i];
-               if (mode->mode == target_mode && mode->is_6ghz == is_6ghz)
-                       return mode;
-       }
-
-       return NULL;
-}
-
-
-static bool
-hostapd_ctrl_is_freq_in_mode(struct hostapd_hw_modes *mode,
-                            struct hostapd_multi_hw_info *current_hw_info,
-                            int freq)
-{
-       struct hostapd_channel_data *chan;
-       int i;
-
-       for (i = 0; i < mode->num_channels; i++) {
-               chan = &mode->channels[i];
-
-               if (chan->flag & HOSTAPD_CHAN_DISABLED)
-                       continue;
-
-               if (!chan_in_current_hw_info(current_hw_info, chan))
-                       continue;
-
-               if (chan->freq == freq)
-                       return true;
-       }
-       return false;
-}
-
-
-static int hostapd_ctrl_check_freq_params(struct hostapd_freq_params *params,
-                                         u16 punct_bitmap)
-{
-       u32 start_freq;
-
-       if (is_6ghz_freq(params->freq)) {
-               const int bw_idx[] = { 20, 40, 80, 160, 320 };
-               int idx, bw;
-
-               /* The 6 GHz band requires HE to be enabled. */
-               params->he_enabled = 1;
-
-               if (params->center_freq1) {
-                       if (params->freq == 5935)
-                               idx = (params->center_freq1 - 5925) / 5;
-                       else
-                               idx = (params->center_freq1 - 5950) / 5;
-
-                       bw = center_idx_to_bw_6ghz(idx);
-                       if (bw < 0 || bw >= (int) ARRAY_SIZE(bw_idx) ||
-                           bw_idx[bw] != params->bandwidth)
-                               return -1;
-               }
-       } else { /* Non-6 GHz channel */
-               /* An EHT STA is also an HE STA as defined in
-                * IEEE P802.11be/D5.0, 4.3.16a. */
-               if (params->he_enabled || params->eht_enabled) {
-                       params->he_enabled = 1;
-                       /* An HE STA is also a VHT STA if operating in the 5 GHz
-                        * band and an HE STA is also an HT STA in the 2.4 GHz
-                        * band as defined in IEEE Std 802.11ax-2021, 4.3.15a.
-                        * A VHT STA is an HT STA as defined in IEEE
-                        * Std 802.11, 4.3.15. */
-                       if (IS_5GHZ(params->freq))
-                               params->vht_enabled = 1;
-
-                       params->ht_enabled = 1;
-               }
-       }
-
-       switch (params->bandwidth) {
-       case 0:
-               /* bandwidth not specified: use 20 MHz by default */
-               /* fall-through */
-       case 20:
-               if (params->center_freq1 &&
-                   params->center_freq1 != params->freq)
-                       return -1;
-
-               if (params->center_freq2 || params->sec_channel_offset)
-                       return -1;
-
-               if (punct_bitmap)
-                       return -1;
-               break;
-       case 40:
-               if (params->center_freq2 || !params->sec_channel_offset)
-                       return -1;
-
-               if (punct_bitmap)
-                       return -1;
-
-               if (!params->center_freq1)
-                       break;
-               switch (params->sec_channel_offset) {
-               case 1:
-                       if (params->freq + 10 != params->center_freq1)
-                               return -1;
-                       break;
-               case -1:
-                       if (params->freq - 10 != params->center_freq1)
-                               return -1;
-                       break;
-               default:
-                       return -1;
-               }
-               break;
-       case 80:
-               if (!params->center_freq1 || !params->sec_channel_offset)
-                       return 1;
-
-               switch (params->sec_channel_offset) {
-               case 1:
-                       if (params->freq - 10 != params->center_freq1 &&
-                           params->freq + 30 != params->center_freq1)
-                               return 1;
-                       break;
-               case -1:
-                       if (params->freq + 10 != params->center_freq1 &&
-                           params->freq - 30 != params->center_freq1)
-                               return -1;
-                       break;
-               default:
-                       return -1;
-               }
-
-               if (params->center_freq2 && punct_bitmap)
-                       return -1;
-
-               /* Adjacent and overlapped are not allowed for 80+80 */
-               if (params->center_freq2 &&
-                   params->center_freq1 - params->center_freq2 <= 80 &&
-                   params->center_freq2 - params->center_freq1 <= 80)
-                       return 1;
-               break;
-       case 160:
-               if (!params->center_freq1 || params->center_freq2 ||
-                   !params->sec_channel_offset)
-                       return -1;
-
-               switch (params->sec_channel_offset) {
-               case 1:
-                       if (params->freq + 70 != params->center_freq1 &&
-                           params->freq + 30 != params->center_freq1 &&
-                           params->freq - 10 != params->center_freq1 &&
-                           params->freq - 50 != params->center_freq1)
-                               return -1;
-                       break;
-               case -1:
-                       if (params->freq + 50 != params->center_freq1 &&
-                           params->freq + 10 != params->center_freq1 &&
-                           params->freq - 30 != params->center_freq1 &&
-                           params->freq - 70 != params->center_freq1)
-                               return -1;
-                       break;
-               default:
-                       return -1;
-               }
-               break;
-       case 320:
-               if (!params->center_freq1 || params->center_freq2 ||
-                   !params->sec_channel_offset)
-                       return -1;
-
-               switch (params->sec_channel_offset) {
-               case 1:
-                       if (params->freq + 150 != params->center_freq1 &&
-                           params->freq + 110 != params->center_freq1 &&
-                           params->freq + 70 != params->center_freq1 &&
-                           params->freq + 30 != params->center_freq1 &&
-                           params->freq - 10 != params->center_freq1 &&
-                           params->freq - 50 != params->center_freq1 &&
-                           params->freq - 90 != params->center_freq1 &&
-                           params->freq - 130 != params->center_freq1)
-                               return -1;
-                       break;
-               case -1:
-                       if (params->freq + 130 != params->center_freq1 &&
-                           params->freq + 90 != params->center_freq1 &&
-                           params->freq + 50 != params->center_freq1 &&
-                           params->freq + 10 != params->center_freq1 &&
-                           params->freq - 30 != params->center_freq1 &&
-                           params->freq - 70 != params->center_freq1 &&
-                           params->freq - 110 != params->center_freq1 &&
-                           params->freq - 150 != params->center_freq1)
-                               return -1;
-                       break;
-               }
-               break;
-       default:
-               return -1;
-       }
-
-       if (!punct_bitmap)
-               return 0;
-
-       if (!params->eht_enabled) {
-               wpa_printf(MSG_ERROR,
-                          "Preamble puncturing supported only in EHT");
-               return -1;
-       }
-
-       if (params->freq >= 2412 && params->freq <= 2484) {
-               wpa_printf(MSG_ERROR,
-                          "Preamble puncturing is not supported in 2.4 GHz");
-               return -1;
-       }
-
-       start_freq = params->center_freq1 - (params->bandwidth / 2);
-       if (!is_punct_bitmap_valid(params->bandwidth,
-                                  (params->freq - start_freq) / 20,
-                                  punct_bitmap)) {
-               wpa_printf(MSG_ERROR, "Invalid preamble puncturing bitmap");
-               return -1;
-       }
-
-       return 0;
-}
-#endif /* NEED_AP_MLME */
-
-
 static int hostapd_ctrl_iface_chan_switch(struct hostapd_iface *iface,
                                          char *pos)
 {
 #ifdef NEED_AP_MLME
        struct csa_settings settings;
-       struct hostapd_hw_modes *target_mode;
        int ret;
        int dfs_range = 0;
        unsigned int i;
@@ -2686,7 +2441,7 @@ static int hostapd_ctrl_iface_chan_switch(struct hostapd_iface *iface,
        unsigned int num_err = 0;
        int err = 0;
 
-       ret = hostapd_parse_csa_settings(pos, &settings);
+       ret = hostapd_parse_csa_settings(iface, pos, &settings);
        if (ret)
                return ret;
 
@@ -2696,29 +2451,6 @@ static int hostapd_ctrl_iface_chan_switch(struct hostapd_iface *iface,
                settings.link_id = iface->bss[0]->mld_link_id;
 #endif /* CONFIG_IEEE80211BE */
 
-       target_mode = get_target_hw_mode(iface, settings.freq_params.freq);
-       if (!target_mode) {
-               wpa_printf(MSG_DEBUG,
-                          "chanswitch: Invalid frequency settings provided for hw mode");
-               return -1;
-       }
-
-       if (iface->num_hw_features > 1 &&
-           !hostapd_ctrl_is_freq_in_mode(target_mode, iface->current_hw_info,
-                                         settings.freq_params.freq)) {
-               wpa_printf(MSG_INFO,
-                          "chanswitch: Invalid frequency settings provided for multi band phy");
-               return -1;
-       }
-
-       ret = hostapd_ctrl_check_freq_params(&settings.freq_params,
-                                            settings.freq_params.punct_bitmap);
-       if (ret) {
-               wpa_printf(MSG_INFO,
-                          "chanswitch: invalid frequency settings provided");
-               return ret;
-       }
-
        switch (settings.freq_params.bandwidth) {
        case 40:
                bandwidth = CHAN_WIDTH_40;
index 2afaa4a6254236cdc215e6ee065711dfabab2b1a..0790abe3dd0c35465a01cda3a69ff2c7f7ad2c2b 100644 (file)
@@ -11,6 +11,7 @@
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
 #include "common/sae.h"
+#include "common/hw_features_common.h"
 #include "eapol_auth/eapol_auth_sm.h"
 #include "fst/fst_ctrl_iface.h"
 #include "hostapd.h"
@@ -1168,10 +1169,253 @@ int hostapd_parse_freq_params(const char *pos,
 }
 
 
-int hostapd_parse_csa_settings(const char *pos,
+static struct hostapd_hw_modes * get_target_hw_mode(struct hostapd_iface *iface,
+                                                   int freq)
+{
+       int i;
+       enum hostapd_hw_mode target_mode;
+       bool is_6ghz = is_6ghz_freq(freq);
+
+       if (freq < 4000)
+               target_mode = HOSTAPD_MODE_IEEE80211G;
+       else if (freq > 50000)
+               target_mode = HOSTAPD_MODE_IEEE80211AD;
+       else
+               target_mode = HOSTAPD_MODE_IEEE80211A;
+
+       for (i = 0; i < iface->num_hw_features; i++) {
+               struct hostapd_hw_modes *mode;
+
+               mode = &iface->hw_features[i];
+               if (mode->mode == target_mode && mode->is_6ghz == is_6ghz)
+                       return mode;
+       }
+
+       return NULL;
+}
+
+
+static bool
+hostapd_ctrl_is_freq_in_mode(struct hostapd_hw_modes *mode,
+                            struct hostapd_multi_hw_info *current_hw_info,
+                            int freq)
+{
+       struct hostapd_channel_data *chan;
+       int i;
+
+       for (i = 0; i < mode->num_channels; i++) {
+               chan = &mode->channels[i];
+
+               if (chan->flag & HOSTAPD_CHAN_DISABLED)
+                       continue;
+
+               if (!chan_in_current_hw_info(current_hw_info, chan))
+                       continue;
+
+               if (chan->freq == freq)
+                       return true;
+       }
+       return false;
+}
+
+
+static int hostapd_ctrl_check_freq_params(struct hostapd_freq_params *params,
+                                         u16 punct_bitmap)
+{
+       u32 start_freq;
+
+       if (is_6ghz_freq(params->freq)) {
+               const int bw_idx[] = { 20, 40, 80, 160, 320 };
+               int idx, bw;
+
+               /* The 6 GHz band requires HE to be enabled. */
+               params->he_enabled = 1;
+
+               if (params->center_freq1) {
+                       if (params->freq == 5935)
+                               idx = (params->center_freq1 - 5925) / 5;
+                       else
+                               idx = (params->center_freq1 - 5950) / 5;
+
+                       bw = center_idx_to_bw_6ghz(idx);
+                       if (bw < 0 || bw >= (int) ARRAY_SIZE(bw_idx) ||
+                           bw_idx[bw] != params->bandwidth)
+                               return -1;
+               }
+       } else { /* Non-6 GHz channel */
+               /* An EHT STA is also an HE STA as defined in
+                * IEEE P802.11be/D5.0, 4.3.16a. */
+               if (params->he_enabled || params->eht_enabled) {
+                       params->he_enabled = 1;
+                       /* An HE STA is also a VHT STA if operating in the 5 GHz
+                        * band and an HE STA is also an HT STA in the 2.4 GHz
+                        * band as defined in IEEE Std 802.11ax-2021, 4.3.15a.
+                        * A VHT STA is an HT STA as defined in IEEE
+                        * Std 802.11, 4.3.15. */
+                       if (IS_5GHZ(params->freq))
+                               params->vht_enabled = 1;
+
+                       params->ht_enabled = 1;
+               }
+       }
+
+       switch (params->bandwidth) {
+       case 0:
+               /* bandwidth not specified: use 20 MHz by default */
+               /* fall-through */
+       case 20:
+               if (params->center_freq1 &&
+                   params->center_freq1 != params->freq)
+                       return -1;
+
+               if (params->center_freq2 || params->sec_channel_offset)
+                       return -1;
+
+               if (punct_bitmap)
+                       return -1;
+               break;
+       case 40:
+               if (params->center_freq2 || !params->sec_channel_offset)
+                       return -1;
+
+               if (punct_bitmap)
+                       return -1;
+
+               if (!params->center_freq1)
+                       break;
+               switch (params->sec_channel_offset) {
+               case 1:
+                       if (params->freq + 10 != params->center_freq1)
+                               return -1;
+                       break;
+               case -1:
+                       if (params->freq - 10 != params->center_freq1)
+                               return -1;
+                       break;
+               default:
+                       return -1;
+               }
+               break;
+       case 80:
+               if (!params->center_freq1 || !params->sec_channel_offset)
+                       return 1;
+
+               switch (params->sec_channel_offset) {
+               case 1:
+                       if (params->freq - 10 != params->center_freq1 &&
+                           params->freq + 30 != params->center_freq1)
+                               return 1;
+                       break;
+               case -1:
+                       if (params->freq + 10 != params->center_freq1 &&
+                           params->freq - 30 != params->center_freq1)
+                               return -1;
+                       break;
+               default:
+                       return -1;
+               }
+
+               if (params->center_freq2 && punct_bitmap)
+                       return -1;
+
+               /* Adjacent and overlapped are not allowed for 80+80 */
+               if (params->center_freq2 &&
+                   params->center_freq1 - params->center_freq2 <= 80 &&
+                   params->center_freq2 - params->center_freq1 <= 80)
+                       return 1;
+               break;
+       case 160:
+               if (!params->center_freq1 || params->center_freq2 ||
+                   !params->sec_channel_offset)
+                       return -1;
+
+               switch (params->sec_channel_offset) {
+               case 1:
+                       if (params->freq + 70 != params->center_freq1 &&
+                           params->freq + 30 != params->center_freq1 &&
+                           params->freq - 10 != params->center_freq1 &&
+                           params->freq - 50 != params->center_freq1)
+                               return -1;
+                       break;
+               case -1:
+                       if (params->freq + 50 != params->center_freq1 &&
+                           params->freq + 10 != params->center_freq1 &&
+                           params->freq - 30 != params->center_freq1 &&
+                           params->freq - 70 != params->center_freq1)
+                               return -1;
+                       break;
+               default:
+                       return -1;
+               }
+               break;
+       case 320:
+               if (!params->center_freq1 || params->center_freq2 ||
+                   !params->sec_channel_offset)
+                       return -1;
+
+               switch (params->sec_channel_offset) {
+               case 1:
+                       if (params->freq + 150 != params->center_freq1 &&
+                           params->freq + 110 != params->center_freq1 &&
+                           params->freq + 70 != params->center_freq1 &&
+                           params->freq + 30 != params->center_freq1 &&
+                           params->freq - 10 != params->center_freq1 &&
+                           params->freq - 50 != params->center_freq1 &&
+                           params->freq - 90 != params->center_freq1 &&
+                           params->freq - 130 != params->center_freq1)
+                               return -1;
+                       break;
+               case -1:
+                       if (params->freq + 130 != params->center_freq1 &&
+                           params->freq + 90 != params->center_freq1 &&
+                           params->freq + 50 != params->center_freq1 &&
+                           params->freq + 10 != params->center_freq1 &&
+                           params->freq - 30 != params->center_freq1 &&
+                           params->freq - 70 != params->center_freq1 &&
+                           params->freq - 110 != params->center_freq1 &&
+                           params->freq - 150 != params->center_freq1)
+                               return -1;
+                       break;
+               }
+               break;
+       default:
+               return -1;
+       }
+
+       if (!punct_bitmap)
+               return 0;
+
+       if (!params->eht_enabled) {
+               wpa_printf(MSG_ERROR,
+                          "Preamble puncturing supported only in EHT");
+               return -1;
+       }
+
+       if (params->freq >= 2412 && params->freq <= 2484) {
+               wpa_printf(MSG_ERROR,
+                          "Preamble puncturing is not supported in 2.4 GHz");
+               return -1;
+       }
+
+       start_freq = params->center_freq1 - (params->bandwidth / 2);
+       if (!is_punct_bitmap_valid(params->bandwidth,
+                                  (params->freq - start_freq) / 20,
+                                  punct_bitmap)) {
+               wpa_printf(MSG_ERROR, "Invalid preamble puncturing bitmap");
+               return -1;
+       }
+
+       return 0;
+}
+
+
+int hostapd_parse_csa_settings(struct hostapd_iface *iface,
+                              const char *pos,
                               struct csa_settings *settings)
 {
+       struct hostapd_hw_modes *target_mode;
        char *end;
+       int ret;
 
        os_memset(settings, 0, sizeof(*settings));
        settings->cs_count = strtol(pos, &end, 10);
@@ -1182,7 +1426,37 @@ int hostapd_parse_csa_settings(const char *pos,
 
        settings->block_tx = !!os_strstr(pos, " blocktx");
 
-       return hostapd_parse_freq_params(end, &settings->freq_params, 0);
+       ret = hostapd_parse_freq_params(end, &settings->freq_params, 0);
+       if (ret < 0) {
+               wpa_printf(MSG_INFO,
+                               "chanswitch: failed to parse frequency parameters");
+               return ret;
+       }
+
+       target_mode = get_target_hw_mode(iface, settings->freq_params.freq);
+       if (!target_mode) {
+               wpa_printf(MSG_DEBUG,
+                          "chanswitch: Invalid frequency settings provided for hw mode");
+               return -1;
+       }
+
+       if (iface->num_hw_features > 1 &&
+           !hostapd_ctrl_is_freq_in_mode(target_mode, iface->current_hw_info,
+                                         settings->freq_params.freq)) {
+               wpa_printf(MSG_INFO,
+                          "chanswitch: Invalid frequency settings provided for multi band phy");
+               return -1;
+       }
+
+       ret = hostapd_ctrl_check_freq_params(&settings->freq_params,
+                                            settings->freq_params.punct_bitmap);
+       if (ret) {
+               wpa_printf(MSG_INFO,
+                          "chanswitch: invalid frequency settings provided");
+               return ret;
+       }
+
+       return 0;
 }
 
 
index 926a51f8784be41fd9651aed8b3ca36b7bd519d9..4de140844f0b4b8514ca8f11a7ceb0a75794337e 100644 (file)
@@ -29,7 +29,8 @@ int hostapd_ctrl_iface_status(struct hostapd_data *hapd, char *buf,
 int hostapd_parse_freq_params(const char *pos,
                              struct hostapd_freq_params *params,
                              unsigned int freq);
-int hostapd_parse_csa_settings(const char *pos,
+int hostapd_parse_csa_settings(struct hostapd_iface *iface,
+                              const char *pos,
                               struct csa_settings *settings);
 int hostapd_ctrl_iface_stop_ap(struct hostapd_data *hapd);
 int hostapd_ctrl_iface_pmksa_list(struct hostapd_data *hapd, char *buf,
index 2d2278c87f45d00972fef8922c628d62bda93fbe..3cb33c49bb2b818e17cb32f0186a7417b104542f 100644 (file)
@@ -1854,8 +1854,22 @@ int ap_switch_channel(struct wpa_supplicant *wpa_s,
 int ap_ctrl_iface_chanswitch(struct wpa_supplicant *wpa_s, const char *pos)
 {
        struct csa_settings settings;
-       int ret = hostapd_parse_csa_settings(pos, &settings);
+       struct hostapd_iface *iface = NULL;
+       int ret;
+
+#ifdef CONFIG_AP
+       if (wpa_s->ap_iface)
+               iface = wpa_s->ap_iface;
+#endif /* CONFIG_AP */
+#ifdef CONFIG_MESH
+       if (!iface && wpa_s->ifmsh)
+               iface = wpa_s->ifmsh;
+#endif /* CONFIG_MESH */
+
+       if (!iface)
+               return -1;
 
+       ret = hostapd_parse_csa_settings(iface, pos, &settings);
        if (ret)
                return ret;