From 95847189abb91d9da44164403608cf8a146c5d78 Mon Sep 17 00:00:00 2001 From: Andrew Zaborowski Date: Wed, 18 Jan 2017 12:36:32 +0100 Subject: [PATCH] device: Start a roaming attempt on low RSSI detection Trigger a roam attempt when the RSSI level has been low for at least 5 seconds using the netdev RSSI LOW/HIGH events. See if neighbor reports are supported and if so, request and process the neighbor reports list to restrict the number of channels to be scanned. The scanning part is not included in this patch for readability. --- src/device.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/device.h | 1 + src/wsc.c | 1 + 3 files changed, 194 insertions(+) diff --git a/src/device.c b/src/device.c index e164a082..2bd9b45f 100644 --- a/src/device.c +++ b/src/device.c @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -72,12 +73,14 @@ struct device { struct l_dbus_message *disconnect_pending; uint32_t netdev_watch_id; struct watchlist state_watches; + struct l_timeout *roam_trigger_timeout; struct wiphy *wiphy; struct netdev *netdev; bool scanning : 1; bool autoconnect : 1; + bool preparing_roam : 1; }; static struct watchlist device_watches; @@ -140,6 +143,8 @@ static const char *device_state_to_string(enum device_state state) return "connected"; case DEVICE_STATE_DISCONNECTING: return "disconnecting"; + case DEVICE_STATE_ROAMING: + return "roaming"; } return "invalid"; @@ -473,6 +478,8 @@ static void device_enter_state(struct device *device, enum device_state state) break; case DEVICE_STATE_DISCONNECTING: break; + case DEVICE_STATE_ROAMING: + break; } disconnected = device->state <= DEVICE_STATE_AUTOCONNECT; @@ -499,6 +506,10 @@ static void device_reset_connection_state(struct device *device) if (device->state == DEVICE_STATE_CONNECTED) network_disconnected(network); + l_timeout_remove(device->roam_trigger_timeout); + device->roam_trigger_timeout = NULL; + device->preparing_roam = false; + device->connected_bss = NULL; device->connected_network = NULL; @@ -547,6 +558,169 @@ static void device_disconnect_by_ap(struct device *device) device_disassociated(device); } +static void device_roam_scan(struct device *device, + struct scan_freq_set *freq_set) +{ +} + +static void device_neighbor_report_cb(struct netdev *netdev, + const uint8_t *reports, + size_t reports_len, void *user_data) +{ + struct device *device = user_data; + 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(device->netdev); + + /* + * Check if we're still attempting to roam -- if dbus Disconnect + * had been called in the meantime we just abort the attempt. + */ + if (!device->preparing_roam) + return; + + if (!reports) { + /* Have to do a full scan */ + device_roam_scan(device, NULL); + + return; + } + + 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; + enum scan_band band; + uint32_t freq; + 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 (device->connected_bss->cc_present) + cc = device->connected_bss->cc; + + band = scan_oper_class_to_band(cc, info.oper_class); + if (!band) { + l_debug("Ignored: unsupported oper class"); + + continue; + } + + freq = scan_channel_to_freq(info.channel_num, band); + if (!freq) { + l_debug("Ignored: unsupported channel"); + + continue; + } + + if (!memcmp(info.addr, device->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 = device->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); + + device_roam_scan(device, freq_set_md); + } else if (count_no_md) { + scan_freq_set_add(freq_set_no_md, current_freq); + + device_roam_scan(device, freq_set_no_md); + } else + device_roam_scan(device, NULL); + + scan_freq_set_free(freq_set_md); + scan_freq_set_free(freq_set_no_md); +} + +static void device_roam_trigger_cb(struct l_timeout *timeout, void *user_data) +{ + struct device *device = user_data; + + l_debug("%d", device->index); + + l_timeout_remove(device->roam_trigger_timeout); + device->roam_trigger_timeout = NULL; + + device->preparing_roam = true; + + /* + * If current BSS supports Neighbor Reports, narrow the scan down + * to channels occupied by known neighbors in the ESS. This isn't + * 100% reliable as the neighbor lists are not required to be + * complete or current. It is likely still better than doing a + * full scan. 10.11.10.1: "A neighbor report may not be exhaustive + * either by choice, or due to the fact that there may be neighbor + * APs not known to the AP." + */ + if (device->connected_bss->cap_rm_neighbor_report) + if (netdev_neighbor_report_req(device->netdev, + device_neighbor_report_cb) == 0) + return; + + /* Otherwise do a full scan for target BSS candidates */ + device_roam_scan(device, NULL); +} + static void device_connect_cb(struct netdev *netdev, enum netdev_result result, void *user_data) { @@ -621,7 +795,20 @@ static void device_netdev_event(struct netdev *netdev, enum netdev_event event, device_disassociated(device); break; case NETDEV_EVENT_RSSI_THRESHOLD_LOW: + if (device->roam_trigger_timeout || + device->preparing_roam || + device->state == DEVICE_STATE_ROAMING) + break; + + /* Set a 5-second timeout */ + device->roam_trigger_timeout = + l_timeout_create(5, device_roam_trigger_cb, + device, NULL); + + break; case NETDEV_EVENT_RSSI_THRESHOLD_HIGH: + l_timeout_remove(device->roam_trigger_timeout); + device->roam_trigger_timeout = NULL; break; }; } @@ -1131,6 +1318,9 @@ static bool device_property_get_state(struct l_dbus *dbus, case DEVICE_STATE_AUTOCONNECT: statestr = "disconnected"; break; + case DEVICE_STATE_ROAMING: + statestr = "roaming"; + break; } l_dbus_message_builder_append_basic(builder, 's', statestr); @@ -1318,6 +1508,8 @@ static void device_free(void *user) netdev_watch_remove(device->netdev, device->netdev_watch_id); + l_timeout_remove(device->roam_trigger_timeout); + scan_ifindex_remove(device->index); l_free(device); } diff --git a/src/device.h b/src/device.h index 1e9e2081..1181ed31 100644 --- a/src/device.h +++ b/src/device.h @@ -40,6 +40,7 @@ enum device_state { DEVICE_STATE_CONNECTING, /* Connecting */ DEVICE_STATE_CONNECTED, DEVICE_STATE_DISCONNECTING, + DEVICE_STATE_ROAMING, }; typedef void (*device_watch_func_t)(struct device *device, diff --git a/src/wsc.c b/src/wsc.c index ec36254f..fc2c4c0e 100644 --- a/src/wsc.c +++ b/src/wsc.c @@ -477,6 +477,7 @@ static void wsc_check_can_connect(struct wsc *wsc, struct scan_bss *target) return; case DEVICE_STATE_AUTOCONNECT: case DEVICE_STATE_OFF: + case DEVICE_STATE_ROAMING: l_warn("wsc_check_can_connect: invalid device state"); break; }