diff --git a/src/ft.c b/src/ft.c index 8d2193b2..8457163a 100644 --- a/src/ft.c +++ b/src/ft.c @@ -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); +} diff --git a/src/ft.h b/src/ft.h index da7d9344..72653bd5 100644 --- a/src/ft.h +++ b/src/ft.h @@ -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); diff --git a/src/netdev.c b/src/netdev.c index fbabcc85..3ca6209f 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -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); diff --git a/src/netdev.h b/src/netdev.h index acdbee75..98b17876 100644 --- a/src/netdev.h +++ b/src/netdev.h @@ -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); diff --git a/src/station.c b/src/station.c index 40396fac..7b335b25 100644 --- a/src/station.c +++ b/src/station.c @@ -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;