ft: netdev: station: support FT-over-DS

FT-over-DS is a way to do a Fast BSS Transition using action frames for
the authenticate step. This allows a station to start a fast transition
to a target AP while still being connected to the original AP. This,
in theory, can result in less carrier downtime.

The existing ft_sm_new was removed, and two new constructors were added;
one for over-air, and another for over-ds. The internals of ft.c mostly
remain the same. A flag to distinguish between air/ds was added along
with a new parser to parse the action frames rather than authenticate
frames. The IE parsing is identical.

Netdev now just initializes the auth-proto differently depending on if
its doing over-air or over-ds. A new TX authenticate function was added
and used for over-ds. This will send out the IEs from ft.c with an
FT Request action frame.

The FT Response action frame is then recieved from the AP and fed into
the auth-proto state machine. After this point ft-over-ds behaves the
same as ft-over-air (associate to the target AP).

Some simple code was added in station.c to determine if over-air or
over-ds should be used. FT-over-DS can be beneficial in cases where the
AP is directing us to roam, or if the RSSI falls below a threshold.
It should not be used if we have lost communication to the AP all
(beacon lost) as it only works while we can still talk to the original
AP.
This commit is contained in:
James Prestwood 2019-05-09 11:16:28 -07:00 committed by Denis Kenzior
parent a432ceeee4
commit c0c8faf32f
5 changed files with 223 additions and 36 deletions

130
src/ft.c
View File

@ -151,6 +151,40 @@ 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)
@ -289,13 +323,8 @@ error:
return -EINVAL;
}
static int ft_rx_authenticate(struct auth_proto *ap, const uint8_t *frame,
size_t frame_len)
static int ft_process_ies(struct ft_sm *ft, const uint8_t *ies, size_t ies_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;
struct ie_tlv_iter iter;
const uint8_t *rsne = NULL;
const uint8_t *mde = NULL;
@ -303,20 +332,6 @@ static int ft_rx_authenticate(struct auth_proto *ap, const uint8_t *frame,
struct handshake_state *hs = ft->hs;
bool is_rsn;
/*
* Parse the Authentication Response and validate the contents
* according to 12.5.2 / 12.5.4: RSN or non-RSN Over-the-air
* FT Protocol.
*/
if (!ft_parse_authentication_resp_frame(frame, frame_len,
hs->spa, hs->aa, hs->aa, 2,
&status_code, &ies, &ies_len))
goto auth_error;
/* AP Rejected the authenticate / associate */
if (status_code != 0)
goto auth_error;
/* Check 802.11r IEs */
if (!ies)
goto ft_error;
@ -449,13 +464,61 @@ static int ft_rx_authenticate(struct auth_proto *ap, const uint8_t *frame,
return ft_tx_reassociate(ft);
auth_error:
return (int)status_code;
ft_error:
return -EBADMSG;
}
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;
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;
return ft_process_ies(ft, ies, ies_len);
auth_error:
return (int)status_code;
}
static int ft_rx_authenticate(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;
/*
* Parse the Authentication Response and validate the contents
* according to 12.5.2 / 12.5.4: RSN or non-RSN Over-the-air
* FT Protocol.
*/
if (!ft_parse_authentication_resp_frame(frame, frame_len, ft->hs->spa,
ft->hs->aa, ft->hs->aa, 2, &status_code,
&ies, &ies_len))
goto auth_error;
/* AP Rejected the authenticate / associate */
if (status_code != 0)
goto auth_error;
return ft_process_ies(ft, ies, ies_len);
auth_error:
return (int)status_code;
}
static int ft_rx_associate(struct auth_proto *ap, const uint8_t *frame,
size_t frame_len)
{
@ -681,9 +744,10 @@ static bool ft_start(struct auth_proto *ap)
return true;
}
struct auth_proto *ft_sm_new(struct handshake_state *hs,
static struct auth_proto *ft_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
bool over_air,
void *user_data)
{
struct ft_sm *ft = l_new(struct ft_sm, 1);
@ -693,10 +757,26 @@ struct auth_proto *ft_sm_new(struct handshake_state *hs,
ft->hs = hs;
ft->user_data = user_data;
ft->ap.rx_authenticate = ft_rx_authenticate;
ft->ap.rx_authenticate = (over_air) ? ft_rx_authenticate : ft_rx_action;
ft->ap.rx_associate = ft_rx_associate;
ft->ap.start = ft_start;
ft->ap.free = ft_sm_free;
return &ft->ap;
}
struct auth_proto *ft_over_air_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
void *user_data)
{
return ft_sm_new(hs, tx_auth, tx_assoc, true, 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)
{
return ft_sm_new(hs, tx_auth, tx_assoc, false, user_data);
}

View File

@ -25,7 +25,12 @@ typedef void (*ft_tx_authenticate_func_t)(struct iovec *iov, size_t iov_len,
typedef void (*ft_tx_associate_func_t)(struct iovec *ie_iov, size_t iov_len,
void *user_data);
struct auth_proto *ft_sm_new(struct handshake_state *hs,
struct auth_proto *ft_over_air_sm_new(struct handshake_state *hs,
ft_tx_authenticate_func_t tx_auth,
ft_tx_associate_func_t tx_assoc,
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

@ -89,6 +89,7 @@ struct netdev {
struct wiphy *wiphy;
unsigned int ifi_flags;
uint32_t frequency;
uint32_t prev_frequency;
netdev_event_func_t event_filter;
netdev_connect_cb_t connect_cb;
@ -2777,7 +2778,69 @@ static void netdev_ft_tx_associate(struct iovec *ie_iov, size_t iov_len,
}
}
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
static void netdev_ft_request_cb(struct l_genl_msg *msg, void *user_data)
{
struct netdev *netdev = user_data;
if (l_genl_msg_get_error(msg) < 0) {
l_error("Could not send CMD_FRAME");
netdev_connect_failed(netdev,
NETDEV_RESULT_AUTHENTICATION_FAILED,
MMPDU_STATUS_CODE_UNSPECIFIED);
}
}
static void netdev_ft_response_frame_event(struct netdev *netdev,
const struct mmpdu_header *hdr,
const void *body, size_t body_len,
void *user_data)
{
int ret;
uint16_t status_code = MMPDU_STATUS_CODE_UNSPECIFIED;
if (!netdev->ap || !netdev->in_ft)
return;
ret = auth_proto_rx_authenticate(netdev->ap, body, body_len);
if (ret < 0)
goto ft_error;
else if (ret > 0) {
status_code = (uint16_t)ret;
goto ft_error;
}
return;
ft_error:
netdev_connect_failed(netdev, NETDEV_RESULT_AUTHENTICATION_FAILED,
status_code);
return;
}
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);
}
static int fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
bool over_air,
netdev_connect_cb_t cb)
{
struct netdev_handshake_state *nhs;
@ -2801,17 +2864,16 @@ int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
handshake_state_new_snonce(netdev->handshake);
netdev->prev_frequency = netdev->frequency;
netdev->frequency = target_bss->frequency;
handshake_state_set_authenticator_address(netdev->handshake,
target_bss->addr);
handshake_state_set_authenticator_rsn(netdev->handshake,
target_bss->rsne);
memcpy(netdev->handshake->mde + 2, target_bss->mde, 3);
netdev->ap = ft_sm_new(netdev->handshake, netdev_ft_tx_authenticate,
netdev_ft_tx_associate, netdev);
netdev->operational = false;
netdev->in_ft = true;
netdev->connect_cb = cb;
@ -2848,14 +2910,23 @@ int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_rssi_polling_update(netdev);
if (!auth_proto_start(netdev->ap))
goto restore_snonce;
if (netdev->sm) {
eapol_sm_free(netdev->sm);
netdev->sm = NULL;
}
if (over_air)
netdev->ap = ft_over_air_sm_new(netdev->handshake,
netdev_ft_tx_authenticate,
netdev_ft_tx_associate, netdev);
else
netdev->ap = ft_over_ds_sm_new(netdev->handshake,
netdev_ft_over_ds_tx_authenticate,
netdev_ft_tx_associate, netdev);
if (!auth_proto_start(netdev->ap))
goto restore_snonce;
return 0;
restore_snonce:
@ -2864,6 +2935,19 @@ restore_snonce:
return err;
}
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
return fast_transition(netdev, target_bss, true, cb);
}
int netdev_fast_transition_over_ds(struct netdev *netdev,
struct scan_bss *target_bss,
netdev_connect_cb_t cb)
{
return fast_transition(netdev, target_bss, false, cb);
}
static void netdev_preauth_cb(const uint8_t *pmk, void *user_data)
{
struct netdev_preauth_state *preauth = user_data;
@ -4309,6 +4393,7 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg)
const uint8_t action_neighbor_report_prefix[2] = { 0x05, 0x05 };
const uint8_t action_sa_query_resp_prefix[2] = { 0x08, 0x01 };
const uint8_t action_sa_query_req_prefix[2] = { 0x08, 0x00 };
const uint8_t action_ft_response_prefix[] = { 0x06, 0x02 };
struct l_io *pae_io = NULL;
const struct l_settings *settings = iwd_get_config();
bool pae_over_nl80211;
@ -4459,6 +4544,10 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg)
sizeof(action_sa_query_req_prefix),
netdev_sa_query_req_frame_event, NULL);
netdev_frame_watch_add(netdev, 0x00d0, action_ft_response_prefix,
sizeof(action_ft_response_prefix),
netdev_ft_response_frame_event, NULL);
/* Set RSSI threshold for CQM notifications */
if (netdev->type == NL80211_IFTYPE_STATION)
netdev_cqm_rssi_update(netdev);

View File

@ -161,6 +161,9 @@ 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(struct netdev *netdev,
struct scan_bss *target_bss,
netdev_connect_cb_t cb);
int netdev_preauthenticate(struct netdev *netdev, struct scan_bss *target_bss,
netdev_preauthenticate_cb_t cb,
void *user_data);

View File

@ -1231,10 +1231,20 @@ static void station_transition_start(struct station *station,
return;
}
if (netdev_fast_transition(station->netdev, bss,
/* FT-over-DS can be better suited for these situations */
if ((hs->mde[4] & 1) && (station->ap_directed_roaming ||
station->signal_low)) {
if (netdev_fast_transition_over_ds(station->netdev, bss,
station_fast_transition_cb) < 0) {
station_roam_failed(station);
return;
station_roam_failed(station);
return;
}
} else {
if (netdev_fast_transition(station->netdev, bss,
station_fast_transition_cb) < 0) {
station_roam_failed(station);
return;
}
}
station->connected_bss = bss;