mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-12-22 21:22:37 +01:00
station: get neighbor report after roaming
In the same vein as requesting a neighbor report after connecting for the first time, it should also be done after a roam to obtain the latest neighbor information.
This commit is contained in:
parent
9b682d43db
commit
28a7dd7fba
354
src/station.c
354
src/station.c
@ -1428,6 +1428,180 @@ 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 scan_band *out_band)
|
||||
{
|
||||
enum scan_band 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 = SCAN_BAND_2_4_GHZ;
|
||||
else if (info->channel_num >= 36 && info->channel_num <= 169)
|
||||
band = SCAN_BAND_5_GHZ;
|
||||
else {
|
||||
l_debug("Ignored: 0 oper class with an unusual "
|
||||
"channel number");
|
||||
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
band = scan_oper_class_to_band(country, info->oper_class);
|
||||
if (!band) {
|
||||
l_debug("Ignored: unsupported oper class");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
freq = scan_channel_to_freq(info->channel_num, band);
|
||||
if (!freq) {
|
||||
l_debug("Ignored: unsupported channel");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (out_band)
|
||||
*out_band = band;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static void parse_neighbor_report(struct station *station,
|
||||
const uint8_t *reports,
|
||||
size_t reports_len,
|
||||
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;
|
||||
struct handshake_state *hs = netdev_get_handshake(station->netdev);
|
||||
|
||||
freq_set_md = scan_freq_set_new();
|
||||
freq_set_no_md = scan_freq_set_new();
|
||||
|
||||
ie_tlv_iter_init(&iter, reports, reports_len);
|
||||
|
||||
/* 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 scan_band band;
|
||||
const uint8_t *cc = NULL;
|
||||
|
||||
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_NEIGHBOR_REPORT)
|
||||
continue;
|
||||
|
||||
if (ie_parse_neighbor_report(&iter, &info) < 0)
|
||||
continue;
|
||||
|
||||
l_debug("Neighbor report received for %s: ch %i "
|
||||
"(oper class %i), %s",
|
||||
util_address_to_string(info.addr),
|
||||
(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;
|
||||
|
||||
if (!memcmp(info.addr,
|
||||
station->connected_bss->addr, ETH_ALEN)) {
|
||||
/*
|
||||
* If this report is for the current AP, don't add
|
||||
* it to any of the lists yet. We will need to scan
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* with that bit set, which will allow us to use Fast Transition.
|
||||
* Some APs, such as those based on hostapd do not set the MD bit
|
||||
* even if the neighbor is within the MD.
|
||||
*
|
||||
* In any case we only select the frequencies here and will check
|
||||
* the IEs in the scan results as the authoritative information
|
||||
* on whether we can use Fast Transition, and rank BSSes based on
|
||||
* that.
|
||||
*
|
||||
* TODO: possibly save the neighbors from outside the MD and if
|
||||
* none of the ones in the MD end up working, try a non-FT
|
||||
* transition to those neighbors. We should be using a
|
||||
* blacklisting mechanism (for both initial connection and
|
||||
* transitions) so that cound_md would not count the
|
||||
* BSSes already used and when it goes down to 0 we'd
|
||||
* automatically fall back to the non-FT candidates and then to
|
||||
* full scan.
|
||||
*/
|
||||
if (count_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) {
|
||||
scan_freq_set_add(freq_set_no_md, current_freq);
|
||||
*set = freq_set_no_md;
|
||||
scan_freq_set_free(freq_set_md);
|
||||
} else {
|
||||
scan_freq_set_free(freq_set_no_md);
|
||||
scan_freq_set_free(freq_set_md);
|
||||
*set = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void station_early_neighbor_report_cb(struct netdev *netdev, int err,
|
||||
const uint8_t *reports,
|
||||
size_t reports_len,
|
||||
void *user_data)
|
||||
{
|
||||
struct station *station = user_data;
|
||||
|
||||
l_debug("ifindex: %u, error: %d(%s)",
|
||||
netdev_get_ifindex(station->netdev),
|
||||
err, err < 0 ? strerror(-err) : "");
|
||||
|
||||
if (!reports || err)
|
||||
return;
|
||||
|
||||
parse_neighbor_report(station, reports, reports_len,
|
||||
&station->roam_freqs);
|
||||
}
|
||||
|
||||
static void station_roamed(struct station *station)
|
||||
{
|
||||
station->roam_scan_full = false;
|
||||
@ -1447,6 +1621,12 @@ static void station_roamed(struct station *station)
|
||||
station->roam_freqs = NULL;
|
||||
}
|
||||
|
||||
if (station->connected_bss->cap_rm_neighbor_report) {
|
||||
if (netdev_neighbor_report_req(station->netdev,
|
||||
station_early_neighbor_report_cb) < 0)
|
||||
l_warn("Could not request neighbor report");
|
||||
}
|
||||
|
||||
station_enter_state(station, STATION_STATE_CONNECTED);
|
||||
}
|
||||
|
||||
@ -1920,162 +2100,6 @@ static int station_roam_scan_known_freqs(struct station *station)
|
||||
return r;
|
||||
}
|
||||
|
||||
static uint32_t station_freq_from_neighbor_report(const uint8_t *country,
|
||||
struct ie_neighbor_report_info *info, enum scan_band *out_band)
|
||||
{
|
||||
enum scan_band 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 = SCAN_BAND_2_4_GHZ;
|
||||
else if (info->channel_num >= 36 && info->channel_num <= 169)
|
||||
band = SCAN_BAND_5_GHZ;
|
||||
else {
|
||||
l_debug("Ignored: 0 oper class with an unusual "
|
||||
"channel number");
|
||||
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
band = scan_oper_class_to_band(country, info->oper_class);
|
||||
if (!band) {
|
||||
l_debug("Ignored: unsupported oper class");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
freq = scan_channel_to_freq(info->channel_num, band);
|
||||
if (!freq) {
|
||||
l_debug("Ignored: unsupported channel");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (out_band)
|
||||
*out_band = band;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
static void parse_neighbor_report(struct station *station,
|
||||
const uint8_t *reports,
|
||||
size_t reports_len,
|
||||
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;
|
||||
struct handshake_state *hs = netdev_get_handshake(station->netdev);
|
||||
|
||||
freq_set_md = scan_freq_set_new();
|
||||
freq_set_no_md = scan_freq_set_new();
|
||||
|
||||
ie_tlv_iter_init(&iter, reports, reports_len);
|
||||
|
||||
/* 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 scan_band band;
|
||||
const uint8_t *cc = NULL;
|
||||
|
||||
if (ie_tlv_iter_get_tag(&iter) != IE_TYPE_NEIGHBOR_REPORT)
|
||||
continue;
|
||||
|
||||
if (ie_parse_neighbor_report(&iter, &info) < 0)
|
||||
continue;
|
||||
|
||||
l_debug("Neighbor report received for %s: ch %i "
|
||||
"(oper class %i), %s",
|
||||
util_address_to_string(info.addr),
|
||||
(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;
|
||||
|
||||
if (!memcmp(info.addr,
|
||||
station->connected_bss->addr, ETH_ALEN)) {
|
||||
/*
|
||||
* If this report is for the current AP, don't add
|
||||
* it to any of the lists yet. We will need to scan
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
* with that bit set, which will allow us to use Fast Transition.
|
||||
* Some APs, such as those based on hostapd do not set the MD bit
|
||||
* even if the neighbor is within the MD.
|
||||
*
|
||||
* In any case we only select the frequencies here and will check
|
||||
* the IEs in the scan results as the authoritative information
|
||||
* on whether we can use Fast Transition, and rank BSSes based on
|
||||
* that.
|
||||
*
|
||||
* TODO: possibly save the neighbors from outside the MD and if
|
||||
* none of the ones in the MD end up working, try a non-FT
|
||||
* transition to those neighbors. We should be using a
|
||||
* blacklisting mechanism (for both initial connection and
|
||||
* transitions) so that cound_md would not count the
|
||||
* BSSes already used and when it goes down to 0 we'd
|
||||
* automatically fall back to the non-FT candidates and then to
|
||||
* full scan.
|
||||
*/
|
||||
if (count_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) {
|
||||
scan_freq_set_add(freq_set_no_md, current_freq);
|
||||
*set = freq_set_no_md;
|
||||
scan_freq_set_free(freq_set_md);
|
||||
} else {
|
||||
scan_freq_set_free(freq_set_no_md);
|
||||
scan_freq_set_free(freq_set_md);
|
||||
*set = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void station_neighbor_report_cb(struct netdev *netdev, int err,
|
||||
const uint8_t *reports,
|
||||
size_t reports_len, void *user_data)
|
||||
@ -2461,24 +2485,6 @@ static void station_connect_dbus_reply(struct station *station,
|
||||
dbus_pending_reply(&station->connect_pending, reply);
|
||||
}
|
||||
|
||||
static void station_early_neighbor_report_cb(struct netdev *netdev, int err,
|
||||
const uint8_t *reports,
|
||||
size_t reports_len,
|
||||
void *user_data)
|
||||
{
|
||||
struct station *station = user_data;
|
||||
|
||||
l_debug("ifindex: %u, error: %d(%s)",
|
||||
netdev_get_ifindex(station->netdev),
|
||||
err, err < 0 ? strerror(-err) : "");
|
||||
|
||||
if (!reports || err)
|
||||
return;
|
||||
|
||||
parse_neighbor_report(station, reports, reports_len,
|
||||
&station->roam_freqs);
|
||||
}
|
||||
|
||||
static void station_connect_cb(struct netdev *netdev, enum netdev_result result,
|
||||
void *event_data, void *user_data)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user