netdev: handle multiple concurrent FT-over-DS action frames

The beauty of FT-over-DS is that a station can send and receive
action frames to many APs to prepare for a future roam. Each
AP authenticates the station and when a roam happens the station
can immediately move to reassociation.

To handle this a queue of netdev_ft_over_ds_info structs is used
instead of a single entry. Using the new ft.c parser APIs these
info structs can be looked up when responses come in. For now
the timeouts/callbacks are kept but these will be removed as it
really does not matter if the AP sends a response (keeps station
happy until the next patch).
This commit is contained in:
James Prestwood 2021-05-12 16:01:41 -07:00 committed by Denis Kenzior
parent ff333a112b
commit 9b7d761db5
1 changed files with 73 additions and 43 deletions

View File

@ -169,7 +169,7 @@ struct netdev {
struct l_genl_msg *auth_cmd;
struct wiphy_radio_work_item work;
struct netdev_ft_over_ds_info *ft_ds_info;
struct l_queue *ft_ds_list;
bool connected : 1;
bool associated : 1;
@ -687,6 +687,13 @@ static void netdev_preauth_destroy(void *data)
l_free(state);
}
static void netdev_ft_ds_entry_free(void *data)
{
struct netdev_ft_over_ds_info *info = data;
ft_ds_info_free(&info->super);
}
static void netdev_connect_free(struct netdev *netdev)
{
if (netdev->work.id)
@ -754,8 +761,10 @@ static void netdev_connect_free(struct netdev *netdev)
netdev->disconnect_cmd_id = 0;
}
if (netdev->ft_ds_info)
ft_ds_info_free(&netdev->ft_ds_info->super);
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
}
static void netdev_connect_failed(struct netdev *netdev,
@ -859,6 +868,11 @@ static void netdev_free(void *data)
netdev->disconnect_idle = NULL;
}
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
if (netdev->events_ready)
WATCHLIST_NOTIFY(&netdev_watches, netdev_watch_func_t,
netdev, NETDEV_WATCH_EVENT_DEL);
@ -1291,6 +1305,11 @@ static void netdev_connect_ok(struct netdev *netdev)
netdev->fw_roam_bss = NULL;
}
if (netdev->ft_ds_list) {
l_queue_destroy(netdev->ft_ds_list, netdev_ft_ds_entry_free);
netdev->ft_ds_list = NULL;
}
if (netdev->connect_cb) {
netdev->connect_cb(netdev, NETDEV_RESULT_OK, NULL,
netdev->user_data);
@ -3447,12 +3466,6 @@ int netdev_reassociate(struct netdev *netdev, struct scan_bss *target_bss,
if (err < 0)
return err;
/* In case of a previous failed over-DS attempt */
if (netdev->ft_ds_info) {
ft_ds_info_free(&netdev->ft_ds_info->super);
netdev->ft_ds_info = NULL;
}
memcpy(netdev->prev_bssid, orig_bss->addr, ETH_ALEN);
netdev->associated = false;
@ -3681,12 +3694,6 @@ static int netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len,
return -EIO;
}
/* No need to keep this around at this point */
if (netdev->ft_ds_info) {
ft_ds_info_free(&netdev->ft_ds_info->super);
netdev->ft_ds_info = NULL;
}
return 0;
}
@ -3761,9 +3768,26 @@ static void netdev_ft_over_ds_auth_failed(struct netdev_ft_over_ds_info *info,
if (info->cb)
info->cb(info->netdev, status, info->super.aa, info->user_data);
l_queue_remove(info->netdev->ft_ds_list, info);
ft_ds_info_free(&info->super);
}
info->netdev->ft_ds_info = NULL;
struct ft_ds_finder {
const uint8_t *spa;
const uint8_t *aa;
};
static bool match_ft_ds_info(const void *a, const void *b)
{
const struct netdev_ft_over_ds_info *info = a;
const struct ft_ds_finder *finder = b;
if (memcmp(info->super.spa, finder->spa, 6))
return false;
if (memcmp(info->super.aa, finder->aa, 6))
return false;
return true;
}
static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr,
@ -3771,41 +3795,41 @@ static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr,
int rssi, void *user_data)
{
struct netdev *netdev = user_data;
struct netdev_ft_over_ds_info *info = netdev->ft_ds_info;
struct netdev_ft_over_ds_info *info;
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
const uint8_t *aa;
const uint8_t *spa;
const uint8_t *ies;
size_t ies_len;
if (!info)
return;
struct ft_ds_finder finder;
ret = ft_over_ds_parse_action_response(body, body_len, &spa, &aa,
&ies, &ies_len);
if (ret != 0)
return;
if (memcmp(spa, info->super.spa, 6))
return;
if (memcmp(aa, info->super.aa, 6))
return;
ret = ft_over_ds_parse_action_ies(&info->super, netdev->handshake,
ies, ies_len);
if (ret < 0)
return;
l_timeout_remove(info->timeout);
info->timeout = NULL;
finder.spa = spa;
finder.aa = aa;
/* Now make sure the packet contained a success status code */
info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder);
if (!info)
return;
/* Lookup successful, now check the status code */
if (ret > 0) {
status_code = (uint16_t)ret;
goto ft_error;
}
ret = ft_over_ds_parse_action_ies(&info->super, netdev->handshake,
ies, ies_len);
if (ret < 0)
goto ft_error;
l_timeout_remove(info->timeout);
info->timeout = NULL;
info->parsed = true;
if (info->cb)
@ -3814,7 +3838,8 @@ static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr,
return;
ft_error:
l_debug("FT-over-DS to "MAC" failed", MAC_STR(info->super.aa));
l_debug("FT-over-DS to "MAC" failed (%d)", MAC_STR(info->super.aa),
status_code);
netdev_ft_over_ds_auth_failed(info, status_code);
}
@ -3892,10 +3917,8 @@ int netdev_fast_transition_over_ds(struct netdev *netdev,
struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
struct netdev_ft_over_ds_info *info = netdev->ft_ds_info;
if (!info || !info->parsed)
return -ENOENT;
struct netdev_ft_over_ds_info *info;
struct ft_ds_finder finder;
if (!netdev->operational)
return -ENOTCONN;
@ -3905,6 +3928,14 @@ int netdev_fast_transition_over_ds(struct netdev *netdev,
l_get_le16(target_bss->mde))
return -EINVAL;
finder.spa = netdev->addr;
finder.aa = target_bss->addr;
info = l_queue_find(netdev->ft_ds_list, match_ft_ds_info, &finder);
if (!info || !info->parsed)
return -ENOENT;
prepare_ft(netdev, target_bss);
ft_over_ds_prepare_handshake(&info->super, netdev->handshake);
@ -3968,10 +3999,6 @@ int netdev_fast_transition_over_ds_action(struct netdev *netdev,
uint8_t buf[512];
size_t len;
/* TODO: Just allow single outstanding action frame for now */
if (netdev->ft_ds_info)
return -EALREADY;
if (!netdev->operational)
return -ENOTCONN;
@ -4010,7 +4037,10 @@ int netdev_fast_transition_over_ds_action(struct netdev *netdev,
iovs[2].iov_base = NULL;
netdev->ft_ds_info = info;
if (!netdev->ft_ds_list)
netdev->ft_ds_list = l_queue_new();
l_queue_push_head(netdev->ft_ds_list, info);
info->timeout = l_timeout_create_ms(300, netdev_ft_over_ds_timeout,
info, NULL);