station: try multiple roam candidates

This converts station to using ft_action/ft_authenticate and
ft_associate and dropping the use of the netdev-only/auth-proto
logic.

Doing this allows for more flexibility if FT fails by letting
IWD try another roam candidate instead of disconnecting.
This commit is contained in:
James Prestwood 2022-09-27 12:47:28 -07:00 committed by Denis Kenzior
parent ceebf0dc53
commit 0e6aaea2a9
1 changed files with 144 additions and 83 deletions

View File

@ -59,6 +59,7 @@
#include "src/frame-xchg.h"
#include "src/sysfs.h"
#include "src/band.h"
#include "src/ft.h"
static struct l_queue *station_list;
static uint32_t netdev_watch;
@ -115,6 +116,8 @@ struct station {
uint32_t wiphy_watch;
struct wiphy_radio_work_item ft_work;
bool preparing_roam : 1;
bool roam_scan_full : 1;
bool signal_low : 1;
@ -1676,6 +1679,8 @@ static void station_roam_state_clear(struct station *station)
}
l_queue_clear(station->roam_bss_list, l_free);
ft_clear_authentications(netdev_get_ifindex(station->netdev));
}
static void station_reset_connection_state(struct station *station)
@ -1987,7 +1992,8 @@ static void station_ft_ds_action_start(struct station *station)
* Fire and forget. Netdev will maintain a cache of responses
* and when the time comes these can be referenced for a roam
*/
netdev_fast_transition_over_ds_action(station->netdev, bss);
ft_action(netdev_get_ifindex(station->netdev),
station->connected_bss->frequency, bss);
}
}
@ -2116,42 +2122,26 @@ static void station_reassociate_cb(struct netdev *netdev,
station_roam_failed(station);
}
static void station_fast_transition_cb(struct netdev *netdev,
enum netdev_result result,
void *event_data,
void *user_data)
{
struct station *station = user_data;
l_debug("%u, result: %d", netdev_get_ifindex(station->netdev), result);
if (station->state != STATION_STATE_ROAMING)
return;
if (result == NETDEV_RESULT_OK)
station_roamed(station);
else
station_roam_failed(station);
}
static void station_netdev_event(struct netdev *netdev, enum netdev_event event,
void *event_data, void *user_data);
static void station_transition_reassociate(struct station *station,
static int station_transition_reassociate(struct station *station,
struct scan_bss *bss,
struct handshake_state *new_hs)
{
if (netdev_reassociate(station->netdev, bss, station->connected_bss,
int ret;
ret = netdev_reassociate(station->netdev, bss, station->connected_bss,
new_hs, station_netdev_event,
station_reassociate_cb, station) < 0) {
handshake_state_free(new_hs);
station_roam_failed(station);
return;
}
station_reassociate_cb, station);
if (ret < 0)
return ret;
station->connected_bss = bss;
station->preparing_roam = false;
station_enter_state(station, STATION_STATE_ROAMING);
return 0;
}
static bool bss_match_bssid(const void *a, const void *b)
@ -2223,19 +2213,107 @@ static void station_preauthenticate_cb(struct netdev *netdev,
handshake_state_set_supplicant_ie(new_hs, rsne_buf);
}
station_transition_reassociate(station, bss, new_hs);
if (station_transition_reassociate(station, bss, new_hs) < 0) {
handshake_state_free(new_hs);
station_roam_failed(station);
}
}
static void station_transition_start(struct station *station)
static void station_transition_start(struct station *station);
static bool station_ft_work_ready(struct wiphy_radio_work_item *item)
{
struct station *station = l_container_of(item, struct station, ft_work);
struct roam_bss *rbss = l_queue_pop_head(station->roam_bss_list);
struct scan_bss *bss = network_bss_find_by_addr(
station->connected_network, rbss->addr);
int ret;
l_free(rbss);
/* Very unlikely, but the BSS could have gone away */
if (!bss)
goto try_next;
ret = ft_associate(netdev_get_ifindex(station->netdev), bss->addr);
if (ret == -ENOENT) {
try_next:
station_transition_start(station);
return true;
} else if (ret < 0)
goto assoc_failed;
station->connected_bss = bss;
station->preparing_roam = false;
station_enter_state(station, STATION_STATE_ROAMING);
return true;
assoc_failed:
station_roam_failed(station);
return true;
}
static const struct wiphy_radio_work_item_ops ft_work_ops = {
.do_work = station_ft_work_ready,
};
static bool station_fast_transition(struct station *station,
struct scan_bss *bss)
{
struct handshake_state *hs = netdev_get_handshake(station->netdev);
struct network *connected = station->connected_network;
const struct network_info *info = network_get_info(connected);
const struct iovec *vendor_ies;
size_t iov_elems = 0;
int ret;
/* Rebuild handshake RSN for target AP */
if (station_build_handshake_rsn(hs, station->wiphy,
station->connected_network, bss) < 0)
return false;
/* Reset the vendor_ies in case they're different */
vendor_ies = network_info_get_extra_ies(info, bss, &iov_elems);
handshake_state_set_vendor_ies(hs, vendor_ies, iov_elems);
if ((hs->mde[4] & 1)) {
ret = ft_associate(netdev_get_ifindex(station->netdev),
bss->addr);
/* No action responses from this BSS, try over air */
if (ret == -ENOENT)
goto try_over_air;
else if (ret < 0)
return false;
station->connected_bss = bss;
station->preparing_roam = false;
station_enter_state(station, STATION_STATE_ROAMING);
return true;
} else {
try_over_air:
/*
* Send FT-Authenticate and insert a work item which will be
* gated until authentication completes
*/
ft_authenticate(netdev_get_ifindex(station->netdev), bss);
wiphy_radio_work_insert(station->wiphy, &station->ft_work,
WIPHY_WORK_PRIORITY_CONNECT, &ft_work_ops);
return true;
}
}
static bool station_try_next_transition(struct station *station,
struct scan_bss *bss)
{
struct handshake_state *hs = netdev_get_handshake(station->netdev);
struct network *connected = station->connected_network;
enum security security = network_get_security(connected);
struct handshake_state *new_hs;
struct ie_rsn_info cur_rsne, target_rsne;
int ret;
struct roam_bss *rbss = l_queue_peek_head(station->roam_bss_list);
struct scan_bss *bss = network_bss_find_by_addr(connected, rbss->addr);
l_debug("%u, target %s", netdev_get_ifindex(station->netdev),
util_address_to_string(bss->addr));
@ -2244,54 +2322,8 @@ static void station_transition_start(struct station *station)
station->ap_directed_roaming = false;
/* Can we use Fast Transition? */
if (station_can_fast_transition(hs, bss)) {
const struct network_info *info = network_get_info(connected);
const struct iovec *vendor_ies;
size_t iov_elems = 0;
/* Rebuild handshake RSN for target AP */
if (station_build_handshake_rsn(hs, station->wiphy,
station->connected_network, bss) < 0) {
l_error("rebuilding handshake rsne failed");
station_roam_failed(station);
return;
}
/* Reset the vendor_ies in case they're different */
vendor_ies = network_info_get_extra_ies(info, bss, &iov_elems);
handshake_state_set_vendor_ies(hs, vendor_ies, iov_elems);
if ((hs->mde[4] & 1)) {
ret = netdev_fast_transition_over_ds(station->netdev,
bss, station->connected_bss,
station_fast_transition_cb);
/* No action responses from this BSS, try over air */
if (ret == -ENOENT)
goto try_over_air;
else if (ret < 0) {
/*
* If we are here FT-over-air will not work
* either (identical checks) so try again later.
*/
station_roam_retry(station);
return;
}
} else {
try_over_air:
if (netdev_fast_transition(station->netdev, bss,
station->connected_bss,
station_fast_transition_cb) < 0) {
station_roam_failed(station);
return;
}
}
station->connected_bss = bss;
station->preparing_roam = false;
station_enter_state(station, STATION_STATE_ROAMING);
return;
}
if (station_can_fast_transition(hs, bss))
return station_fast_transition(station, bss);
/* Non-FT transition */
@ -2321,17 +2353,46 @@ try_over_air:
if (netdev_preauthenticate(station->netdev, bss,
station_preauthenticate_cb,
station) >= 0)
return;
return true;
}
new_hs = station_handshake_setup(station, connected, bss);
if (!new_hs) {
l_error("station_handshake_setup failed in reassociation");
station_roam_failed(station);
return;
return false;
}
station_transition_reassociate(station, bss, new_hs);
if (station_transition_reassociate(station, bss, new_hs) < 0) {
handshake_state_free(new_hs);
return false;
}
return true;
}
static void station_transition_start(struct station *station)
{
struct roam_bss *rbss;
bool roaming = false;
/*
* For each failed attempt pop the BSS leaving the head of the queue
* with the current roam candidate.
*/
while ((rbss = l_queue_peek_head(station->roam_bss_list))) {
struct scan_bss *bss = network_bss_find_by_addr(
station->connected_network, rbss->addr);
roaming = station_try_next_transition(station, bss);
if (roaming)
break;
l_queue_pop_head(station->roam_bss_list);
l_free(rbss);
}
if (!roaming)
station_roam_failed(station);
}
static void station_roam_scan_triggered(int err, void *user_data)