ft: netdev: refactor FT-over-DS into two stages

FT-over-DS followed the same pattern as FT-over-Air which worked,
but really limited how the protocol could be used. FT-over-DS is
unique in that we can authenticate to many APs by sending out
FT action frames and parsing the results. Once parsed IWD can
immediately Reassociate, or do so at a later time.

To take advantage of this IWD need to separate FT-over-DS into
two stages: action frame and reassociation.

The initial action frame stage is started by netdev. The target
BSS is sent an FT action frame and a new cache entry is created
in ft.c. Once the response is received the entry is updated
with all the needed data to Reassociate. To limit the record
keeping on netdev each FT-over-DS entry holds a userdata pointer
so netdev doesn't need to maintain its own list of data for
callbacks.

Once the action response is parsed netdev will call back signalling
the action frame sequence was completed (either successfully or not).
At this point the 'normal' FT procedure can start using the
FT-over-DS auth-proto.
This commit is contained in:
James Prestwood 2021-04-30 10:47:02 -07:00 committed by Denis Kenzior
parent 184b19c992
commit c10b8d42e3
4 changed files with 184 additions and 110 deletions

View File

@ -156,40 +156,6 @@ static bool ft_parse_authentication_resp_frame(const uint8_t *data, size_t len,
return true;
}
static bool ft_parse_action_resp_frame(const uint8_t *frame, size_t frame_len,
const uint8_t *spa, const uint8_t *aa,
uint16_t *out_status,
const uint8_t **out_ies,
size_t *out_ies_len)
{
uint16_t status = 0;
/* Category FT */
if (frame[0] != 6)
return false;
/* FT Action */
if (frame[1] != 2)
return false;
if (memcmp(frame + 2, spa, 6))
return false;
if (memcmp(frame + 8, aa, 6))
return false;
status = l_get_le16(frame + 14);
if (out_status)
*out_status = status;
if (status == 0 && out_ies) {
*out_ies = frame + 16;
*out_ies_len = frame_len - 16;
}
return true;
}
static bool ft_parse_associate_resp_frame(const uint8_t *frame, size_t frame_len,
uint16_t *out_status, const uint8_t **rsne,
const uint8_t **mde, const uint8_t **fte)
@ -612,34 +578,6 @@ bool ft_over_ds_prepare_handshake(struct ft_ds_info *info,
return true;
}
static int ft_rx_action(struct auth_proto *ap, const uint8_t *frame,
size_t frame_len)
{
struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap);
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
const uint8_t *ies = NULL;
size_t ies_len;
int ret;
if (!ft_parse_action_resp_frame(frame, frame_len, ft->hs->spa,
ft->hs->aa, &status_code,
&ies, &ies_len))
return -EBADMSG;
/* AP Rejected the authenticate / associate */
if (status_code != 0)
goto auth_error;
ret = ft_process_ies(ft->hs, ies, ies_len);
if (ret < 0)
goto auth_error;
return ft_tx_reassociate(ft);
auth_error:
return (int)status_code;
}
void ft_ds_info_free(struct ft_ds_info *info)
{
__typeof__(info->free) destroy = info->free;
@ -827,6 +765,13 @@ static void ft_sm_free(struct auth_proto *ap)
l_free(ft);
}
static bool ft_over_ds_start(struct auth_proto *ap)
{
struct ft_sm *ft = l_container_of(ap, struct ft_sm, ap);
return ft_tx_reassociate(ft) == 0;
}
bool ft_build_authenticate_ies(struct handshake_state *hs,
const uint8_t *new_snonce, uint8_t *buf,
size_t *len)
@ -938,20 +883,17 @@ struct auth_proto *ft_over_air_sm_new(struct handshake_state *hs,
}
struct auth_proto *ft_over_ds_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
void *user_data)
{
struct ft_sm *ft = l_new(struct ft_sm, 1);
ft->tx_assoc = tx_assoc;
ft->tx_auth = tx_auth;
ft->hs = hs;
ft->user_data = user_data;
ft->ap.rx_authenticate = ft_rx_action;
ft->ap.rx_associate = ft_rx_associate;
ft->ap.start = ft_start;
ft->ap.start = ft_over_ds_start;
ft->ap.free = ft_sm_free;
return &ft->ap;

View File

@ -57,7 +57,6 @@ struct auth_proto *ft_over_air_sm_new(struct handshake_state *hs,
void *user_data);
struct auth_proto *ft_over_ds_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
void *user_data);

View File

@ -92,6 +92,16 @@ struct netdev_handshake_state {
enum connection_type type;
};
struct netdev_ft_over_ds_info {
struct ft_ds_info super;
struct netdev *netdev;
struct l_timeout *timeout;
netdev_ft_over_ds_cb_t cb;
void *user_data;
bool parsed : 1;
};
struct netdev {
uint32_t index;
uint64_t wdev_id;
@ -159,6 +169,8 @@ struct netdev {
struct l_genl_msg *auth_cmd;
struct wiphy_radio_work_item work;
struct netdev_ft_over_ds_info *ft_ds_info;
bool connected : 1;
bool associated : 1;
bool operational : 1;
@ -741,6 +753,9 @@ static void netdev_connect_free(struct netdev *netdev)
l_genl_family_cancel(nl80211, netdev->disconnect_cmd_id);
netdev->disconnect_cmd_id = 0;
}
if (netdev->ft_ds_info)
ft_ds_info_free(&netdev->ft_ds_info->super);
}
static void netdev_connect_failed(struct netdev *netdev,
@ -3424,6 +3439,12 @@ 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;
@ -3652,6 +3673,12 @@ static void netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len,
MMPDU_STATUS_CODE_UNSPECIFIED);
return;
}
/* 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;
}
}
static void prepare_ft(struct netdev *netdev, struct scan_bss *target_bss)
@ -3719,44 +3746,54 @@ static void prepare_ft(struct netdev *netdev, struct scan_bss *target_bss)
}
}
static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data)
static void netdev_ft_over_ds_auth_failed(struct netdev_ft_over_ds_info *info,
uint16_t status)
{
struct netdev *netdev = user_data;
int err = l_genl_msg_get_error(msg);
if (info->cb)
info->cb(info->netdev, status, info->super.aa, info->user_data);
if (err < 0) {
l_error("Could not send CMD_FRAME (%d)", err);
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
ft_ds_info_free(&info->super);
info->netdev->ft_ds_info = NULL;
}
static void netdev_ft_response_frame_event(const struct mmpdu_header *hdr,
const void *body, size_t body_len,
int rssi, void *user_data)
{
struct netdev *netdev = user_data;
struct netdev_ft_over_ds_info *info = netdev->ft_ds_info;
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
struct netdev *netdev = user_data;
if (!netdev->ap || !netdev->in_ft)
if (!info)
return;
ret = auth_proto_rx_authenticate(netdev->ap, body, body_len);
ret = ft_over_ds_parse_action_response(&info->super, netdev->handshake,
body, body_len);
if (ret < 0)
goto ft_error;
else if (ret > 0) {
return;
l_timeout_remove(info->timeout);
info->timeout = NULL;
/* Now make sure the packet contained a success status code */
if (ret > 0) {
status_code = (uint16_t)ret;
goto ft_error;
}
info->parsed = true;
if (info->cb)
info->cb(netdev, 0, info->super.aa, info->user_data);
return;
ft_error:
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
status_code);
return;
l_debug("FT-over-DS to "MAC" failed", MAC_STR(info->super.aa));
netdev_ft_over_ds_auth_failed(info, status_code);
}
static void netdev_qos_map_frame_event(const struct mmpdu_header *hdr,
@ -3781,29 +3818,6 @@ static void netdev_qos_map_frame_event(const struct mmpdu_header *hdr,
netdev_send_qos_map_set(netdev, body + 4, body_len - 4);
}
static void netdev_ft_over_ds_tx_authenticate(struct iovec *iov,
size_t iov_len, void *user_data)
{
struct netdev *netdev = user_data;
uint8_t ft_req[14];
struct handshake_state *hs = netdev->handshake;
struct iovec iovs[iov_len + 1];
ft_req[0] = 6; /* FT category */
ft_req[1] = 1; /* FT Request action */
memcpy(ft_req + 2, netdev->addr, 6);
memcpy(ft_req + 8, hs->aa, 6);
iovs[0].iov_base = ft_req;
iovs[0].iov_len = sizeof(ft_req);
memcpy(iovs + 1, iov, sizeof(*iov) * iov_len);
netdev_send_action_framev(netdev, netdev->prev_bssid, iovs, iov_len + 1,
netdev->prev_frequency,
netdev_ft_request_cb,
netdev);
}
static bool netdev_ft_work_ready(struct wiphy_radio_work_item *item)
{
struct netdev *netdev = l_container_of(item, struct netdev, work);
@ -3855,6 +3869,11 @@ 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;
if (!netdev->operational)
return -ENOTCONN;
@ -3865,13 +3884,13 @@ int netdev_fast_transition_over_ds(struct netdev *netdev,
prepare_ft(netdev, target_bss);
handshake_state_new_snonce(netdev->handshake);
ft_over_ds_prepare_handshake(&info->super, netdev->handshake);
netdev->connect_cb = cb;
netdev->ap = ft_over_ds_sm_new(netdev->handshake,
netdev_ft_over_ds_tx_authenticate,
netdev_ft_tx_associate, netdev);
netdev_ft_tx_associate,
netdev);
wiphy_radio_work_insert(netdev->wiphy, &netdev->work, 1,
&ft_work_ops);
@ -3879,6 +3898,112 @@ int netdev_fast_transition_over_ds(struct netdev *netdev,
return 0;
}
static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev_ft_over_ds_info *info = user_data;
if (l_genl_msg_get_error(msg) < 0) {
l_error("Could not send CMD_FRAME for FT-over-DS");
netdev_ft_over_ds_auth_failed(info,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_ft_over_ds_timeout(struct l_timeout *timeout,
void *user_data)
{
struct netdev_ft_over_ds_info *info = user_data;
l_timeout_remove(info->timeout);
info->timeout = NULL;
l_debug("");
netdev_ft_over_ds_auth_failed(info, MMPDU_STATUS_CODE_UNSPECIFIED);
}
static void netdev_ft_ds_info_free(struct ft_ds_info *ft)
{
struct netdev_ft_over_ds_info *info = l_container_of(ft,
struct netdev_ft_over_ds_info, super);
if (info->timeout)
l_timeout_remove(info->timeout);
l_free(info);
}
int netdev_fast_transition_over_ds_action(struct netdev *netdev,
const struct scan_bss *target_bss,
netdev_ft_over_ds_cb_t cb,
void *user_data)
{
struct netdev_ft_over_ds_info *info;
uint8_t ft_req[14];
struct handshake_state *hs = netdev->handshake;
struct iovec iovs[5];
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;
if (!netdev->handshake->mde || !target_bss->mde_present ||
l_get_le16(netdev->handshake->mde + 2) !=
l_get_le16(target_bss->mde))
return -EINVAL;
l_debug("");
info = l_new(struct netdev_ft_over_ds_info, 1);
info->netdev = netdev;
memcpy(info->super.spa, hs->spa, ETH_ALEN);
memcpy(info->super.aa, target_bss->addr, ETH_ALEN);
memcpy(info->super.mde, target_bss->mde, sizeof(info->super.mde));
l_getrandom(info->super.snonce, 32);
info->super.free = netdev_ft_ds_info_free;
info->cb = cb;
info->user_data = user_data;
ft_req[0] = 6; /* FT category */
ft_req[1] = 1; /* FT Request action */
memcpy(ft_req + 2, netdev->addr, 6);
memcpy(ft_req + 8, info->super.aa, 6);
iovs[0].iov_base = ft_req;
iovs[0].iov_len = sizeof(ft_req);
if (!ft_build_authenticate_ies(hs, info->super.snonce, buf, &len))
goto failed;
iovs[1].iov_base = buf;
iovs[1].iov_len = len;
iovs[2].iov_base = NULL;
netdev->ft_ds_info = info;
info->timeout = l_timeout_create_ms(300, netdev_ft_over_ds_timeout,
info, NULL);
netdev_send_action_framev(netdev, netdev->handshake->aa, iovs, 2,
netdev->frequency,
netdev_ft_request_cb,
info);
return 0;
failed:
l_free(info);
return -EIO;
}
static void netdev_preauth_cb(const uint8_t *pmk, void *user_data)
{
struct netdev_preauth_state *preauth = user_data;

View File

@ -125,6 +125,10 @@ typedef void (*netdev_get_station_cb_t)(
const char *netdev_iftype_to_string(uint32_t iftype);
typedef void (*netdev_ft_over_ds_cb_t)(struct netdev *netdev,
uint16_t status, const uint8_t *bssid,
void *user_data);
struct wiphy *netdev_get_wiphy(struct netdev *netdev);
const uint8_t *netdev_get_address(struct netdev *netdev);
uint32_t netdev_get_ifindex(struct netdev *netdev);
@ -158,6 +162,10 @@ int netdev_reassociate(struct netdev *netdev, struct scan_bss *target_bss,
netdev_connect_cb_t cb, void *user_data);
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_connect_cb_t cb);
int netdev_fast_transition_over_ds_action(struct netdev *netdev,
const struct scan_bss *target_bss,
netdev_ft_over_ds_cb_t cb,
void *user_data);
int netdev_fast_transition_over_ds(struct netdev *netdev,
struct scan_bss *target_bss,
netdev_connect_cb_t cb);