diff --git a/src/station.c b/src/station.c index db069981..e373b03b 100644 --- a/src/station.c +++ b/src/station.c @@ -1821,36 +1821,87 @@ static void station_roam_timeout_rearm(struct station *station, int seconds); static int station_roam_scan(struct station *station, struct scan_freq_set *freq_set); -static uint32_t station_freq_from_neighbor_report(const uint8_t *country, - struct ie_neighbor_report_info *info, enum band_freq *out_band) +static bool is_freq_band_supported(struct station *station, uint32_t freq, + enum band_freq band) +{ + const struct band_freq_attrs *attr; + + if (!(band & wiphy_get_supported_bands(station->wiphy))) + return false; + + /* Skip if frequency is not supported or disabled */ + attr = wiphy_get_frequency_info(station->wiphy, freq); + if (!attr || attr->disabled) + return false; + + return true; +} + +/* + * Some Cisco APs report all operating class values as 0 in the Neighbor + * Report Responses. Historically we could be very sure what band the + * channel was associated with since there was no overlap between 2.4 + * and 5GHz. With the addition of 6GHz this becomes more difficult as + * the 6GHz channel range overlaps both 2.4 and 5ghz. + * + * To cover all 3 bands, calculate a frequency for each and see what sticks. + */ +static void station_parse_zero_oper_class(struct station *station, + struct ie_neighbor_report_info *info, + struct scan_freq_set *freq_set) +{ + uint32_t freq_2g; + uint32_t freq_5g; + uint32_t freq_6g; + + freq_2g = band_channel_to_freq(info->channel_num, BAND_FREQ_2_4_GHZ); + freq_5g = band_channel_to_freq(info->channel_num, BAND_FREQ_5_GHZ); + freq_6g = band_channel_to_freq(info->channel_num, BAND_FREQ_6_GHZ); + + if (!freq_2g && !freq_5g && !freq_6g) { + l_debug("Ignored: 0 oper class with an unusual " + "channel number %u", info->channel_num); + return; + } + + if (freq_2g && is_freq_band_supported(station, freq_2g, + BAND_FREQ_2_4_GHZ)) { + l_debug("adding frequency %u from invalid oper class", freq_2g); + scan_freq_set_add(freq_set, freq_2g); + } + + if (freq_5g && is_freq_band_supported(station, freq_5g, + BAND_FREQ_5_GHZ)) { + l_debug("adding frequency %u from invalid oper class", freq_5g); + scan_freq_set_add(freq_set, freq_5g); + } + + if (freq_6g && is_freq_band_supported(station, freq_6g, + BAND_FREQ_6_GHZ)) { + l_debug("adding frequency %u from invalid oper class", freq_6g); + scan_freq_set_add(freq_set, freq_6g); + } +} + +static void station_add_neighbor_report_freqs(struct station *station, + struct ie_neighbor_report_info *info, + struct scan_freq_set *freq_set) { enum band_freq band; uint32_t freq; - if (info->oper_class == 0) { - /* - * Some Cisco APs report all operating class values as 0 - * in the Neighbor Report Responses. Work around this by - * using the most likely operating class for the channel - * number as the 2.4GHz and 5GHz bands happen to mostly - * use channels in two disjoint ranges. - */ - if (info->channel_num >= 1 && info->channel_num <= 14) - band = BAND_FREQ_2_4_GHZ; - else if (info->channel_num >= 36 && info->channel_num <= 169) - band = BAND_FREQ_5_GHZ; - else { - l_debug("Ignored: 0 oper class with an unusual " - "channel number %u", info->channel_num); + const uint8_t *country = station->connected_bss->cc_present ? + station->connected_bss->cc : NULL; - return 0; - } + if (info->oper_class == 0) { + station_parse_zero_oper_class(station, info, freq_set); + return; } else { band = band_oper_class_to_band(country, info->oper_class); if (!band) { l_debug("Ignored: unsupported oper class"); - return 0; + return; } } @@ -1858,13 +1909,14 @@ static uint32_t station_freq_from_neighbor_report(const uint8_t *country, if (!freq) { l_debug("Ignored: unsupported channel"); - return 0; + return; } - if (out_band) - *out_band = band; + /* Skip if the band/frequency is not supported */ + if (!is_freq_band_supported(station, freq, band)) + return; - return freq; + scan_freq_set_add(freq_set, freq); } static void parse_neighbor_report(struct station *station, @@ -1873,9 +1925,8 @@ static void parse_neighbor_report(struct station *station, struct scan_freq_set **set) { struct ie_tlv_iter iter; - int count_md = 0, count_no_md = 0; struct scan_freq_set *freq_set_md, *freq_set_no_md; - uint32_t current_freq = 0; + uint32_t current_freq = station->connected_bss->frequency; struct handshake_state *hs = netdev_get_handshake(station->netdev); freq_set_md = scan_freq_set_new(); @@ -1886,10 +1937,6 @@ static void parse_neighbor_report(struct station *station, /* First see if any of the reports contain the MD bit set */ while (ie_tlv_iter_next(&iter)) { struct ie_neighbor_report_info info; - uint32_t freq; - enum band_freq band; - const uint8_t *cc = NULL; - const struct band_freq_attrs *attr; if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_NEIGHBOR_REPORT) continue; @@ -1903,22 +1950,6 @@ static void parse_neighbor_report(struct station *station, (int) info.channel_num, (int) info.oper_class, info.md ? "MD set" : "MD not set"); - if (station->connected_bss->cc_present) - cc = station->connected_bss->cc; - - freq = station_freq_from_neighbor_report(cc, &info, &band); - if (!freq) - continue; - - /* Skip if the band is not supported */ - if (!(band & wiphy_get_supported_bands(station->wiphy))) - continue; - - /* Skip if frequency is not supported or disabled */ - attr = wiphy_get_frequency_info(station->wiphy, freq); - if (!attr || attr->disabled) - continue; - if (!memcmp(info.addr, station->connected_bss->addr, ETH_ALEN)) { /* @@ -1927,26 +1958,14 @@ static void parse_neighbor_report(struct station *station, * its channel because it may still be the best ranked * or the only visible AP. */ - current_freq = freq; - continue; } - /* Add the frequency to one of the lists */ - if (info.md && hs->mde) { - scan_freq_set_add(freq_set_md, freq); - - count_md += 1; - } else { - scan_freq_set_add(freq_set_no_md, freq); - - count_no_md += 1; - } + station_add_neighbor_report_freqs(station, &info, + info.md && hs->mde ? + freq_set_md : freq_set_no_md); } - if (!current_freq) - current_freq = station->connected_bss->frequency; - /* * If there are neighbor reports with the MD bit set then the bit * is probably valid so scan only the frequencies of the neighbors @@ -1968,11 +1987,11 @@ static void parse_neighbor_report(struct station *station, * automatically fall back to the non-FT candidates and then to * full scan. */ - if (count_md) { + if (!scan_freq_set_isempty(freq_set_md)) { scan_freq_set_add(freq_set_md, current_freq); *set = freq_set_md; scan_freq_set_free(freq_set_no_md); - } else if (count_no_md) { + } else if (!scan_freq_set_isempty(freq_set_no_md)) { scan_freq_set_add(freq_set_no_md, current_freq); *set = freq_set_no_md; scan_freq_set_free(freq_set_md);