mirror of
https://git.kernel.org/pub/scm/network/wireless/iwd.git
synced 2024-11-26 18:59:22 +01:00
netdev: Implement netdev_fast_transition
Build and send the FT Authentication Request frame, the initial Fast Transition message. In this version the assumption is that once we start a transition attempt there's no going back so the old handshake_state, scan_bss, etc. can be replaced by the new objects immediately and there's no point at which both the old and the new connection states are needed. Also the disconnect event for the old connection is implicit. At netdev level the state during a transition is almost the same with a new connection setup. The first disconnect event on the netlink socket after the FT Authenticate is assumed to be the one generated by the kernel for the old connection. The disconnect event doesn't contain the AP bssid (unlike the deauthenticate event preceding it), otherwise we could check to see if the bssid is the one we are interested in or could check connect_cmd_id assuming a disconnect doesn't happen before the connect command finishes.
This commit is contained in:
parent
46717cae08
commit
d52683a2cb
207
src/netdev.c
207
src/netdev.c
@ -75,6 +75,7 @@ struct netdev {
|
||||
uint32_t disconnect_cmd_id;
|
||||
enum netdev_result result;
|
||||
struct l_timeout *neighbor_report_timeout;
|
||||
uint8_t prev_bssid[ETH_ALEN];
|
||||
|
||||
struct l_queue *watches;
|
||||
uint32_t next_watch_id;
|
||||
@ -82,6 +83,7 @@ struct netdev {
|
||||
bool connected : 1;
|
||||
bool operational : 1;
|
||||
bool rekey_offload_support : 1;
|
||||
bool in_ft : 1;
|
||||
};
|
||||
|
||||
struct netdev_watch {
|
||||
@ -297,6 +299,7 @@ static void netdev_connect_free(struct netdev *netdev)
|
||||
netdev->neighbor_report_cb = NULL;
|
||||
netdev->user_data = NULL;
|
||||
netdev->result = NETDEV_RESULT_OK;
|
||||
netdev->in_ft = false;
|
||||
|
||||
if (netdev->pairwise_new_key_cmd_id) {
|
||||
l_genl_family_cancel(nl80211, netdev->pairwise_new_key_cmd_id);
|
||||
@ -523,7 +526,8 @@ static void netdev_disconnect_event(struct l_genl_msg *msg,
|
||||
|
||||
l_debug("");
|
||||
|
||||
if (!netdev->connected || netdev->disconnect_cmd_id > 0)
|
||||
if (!netdev->connected || netdev->disconnect_cmd_id > 0 ||
|
||||
netdev->in_ft)
|
||||
return;
|
||||
|
||||
if (!l_genl_attr_init(&attr, msg)) {
|
||||
@ -1529,6 +1533,207 @@ int netdev_disconnect(struct netdev *netdev,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build an FT Authentication Request frame according to 12.5.2 / 12.5.4:
|
||||
* RSN or non-RSN Over-the-air FT Protocol, with the IE contents
|
||||
* according to 12.8.2: FT authentication sequence: contents of first message.
|
||||
*/
|
||||
static struct l_genl_msg *netdev_build_cmd_ft_authenticate(
|
||||
struct netdev *netdev,
|
||||
const struct scan_bss *bss,
|
||||
const struct handshake_state *hs)
|
||||
{
|
||||
uint32_t auth_type = NL80211_AUTHTYPE_FT;
|
||||
struct l_genl_msg *msg;
|
||||
struct iovec iov[3];
|
||||
int iov_elems = 0;
|
||||
bool is_rsn = hs->own_ie != NULL;
|
||||
uint8_t mde[5];
|
||||
|
||||
msg = l_genl_msg_new_sized(NL80211_CMD_AUTHENTICATE, 512);
|
||||
l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index);
|
||||
l_genl_msg_append_attr(msg, NL80211_ATTR_WIPHY_FREQ,
|
||||
4, &bss->frequency);
|
||||
l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, bss->addr);
|
||||
l_genl_msg_append_attr(msg, NL80211_ATTR_SSID,
|
||||
bss->ssid_len, bss->ssid);
|
||||
l_genl_msg_append_attr(msg, NL80211_ATTR_AUTH_TYPE, 4, &auth_type);
|
||||
|
||||
if (is_rsn) {
|
||||
struct ie_rsn_info rsn_info;
|
||||
uint8_t *rsne;
|
||||
|
||||
/*
|
||||
* Rebuild the RSNE to include the PMKR0Name and append
|
||||
* MDE + FTE.
|
||||
*
|
||||
* 12.8.2: "If present, the RSNE shall be set as follows:
|
||||
* — Version field shall be set to 1.
|
||||
* — PMKID Count field shall be set to 1.
|
||||
* — PMKID List field shall contain the PMKR0Name.
|
||||
* — All other fields shall be as specified in 8.4.2.27
|
||||
* and 11.5.3."
|
||||
*/
|
||||
if (ie_parse_rsne_from_data(hs->own_ie, hs->own_ie[1] + 2,
|
||||
&rsn_info) < 0)
|
||||
goto error;
|
||||
|
||||
rsn_info.num_pmkids = 1;
|
||||
rsn_info.pmkids = hs->pmk_r0_name;
|
||||
|
||||
rsne = alloca(256);
|
||||
ie_build_rsne(&rsn_info, rsne);
|
||||
|
||||
iov[iov_elems].iov_base = rsne;
|
||||
iov[iov_elems].iov_len = rsne[1] + 2;
|
||||
iov_elems += 1;
|
||||
}
|
||||
|
||||
/* The MDE advertised by the BSS must be passed verbatim */
|
||||
mde[0] = IE_TYPE_MOBILITY_DOMAIN;
|
||||
mde[1] = 3;
|
||||
memcpy(mde + 2, bss->mde, 3);
|
||||
|
||||
iov[iov_elems].iov_base = mde;
|
||||
iov[iov_elems].iov_len = 5;
|
||||
iov_elems += 1;
|
||||
|
||||
if (is_rsn) {
|
||||
struct ie_ft_info ft_info;
|
||||
uint8_t *fte;
|
||||
|
||||
/*
|
||||
* 12.8.2: "If present, the FTE shall be set as follows:
|
||||
* — R0KH-ID shall be the value of R0KH-ID obtained by the
|
||||
* FTO during its FT initial mobility domain association
|
||||
* exchange.
|
||||
* — SNonce shall be set to a value chosen randomly by the
|
||||
* FTO, following the recommendations of 11.6.5.
|
||||
* — All other fields shall be set to 0."
|
||||
*/
|
||||
|
||||
memset(&ft_info, 0, sizeof(ft_info));
|
||||
|
||||
memcpy(ft_info.r0khid, hs->r0khid, hs->r0khid_len);
|
||||
ft_info.r0khid_len = hs->r0khid_len;
|
||||
|
||||
memcpy(ft_info.snonce, hs->snonce, 32);
|
||||
|
||||
fte = alloca(256);
|
||||
ie_build_fast_bss_transition(&ft_info, fte);
|
||||
|
||||
iov[iov_elems].iov_base = fte;
|
||||
iov[iov_elems].iov_len = fte[1] + 2;
|
||||
iov_elems += 1;
|
||||
}
|
||||
|
||||
l_genl_msg_append_attrv(msg, NL80211_ATTR_IE, iov, iov_elems);
|
||||
|
||||
return msg;
|
||||
|
||||
error:
|
||||
l_genl_msg_unref(msg);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void netdev_cmd_authenticate_ft_cb(struct l_genl_msg *msg,
|
||||
void *user_data)
|
||||
{
|
||||
struct netdev *netdev = user_data;
|
||||
|
||||
netdev->connect_cmd_id = 0;
|
||||
|
||||
if (l_genl_msg_get_error(msg) < 0) {
|
||||
netdev->result = NETDEV_RESULT_AUTHENTICATION_FAILED;
|
||||
netdev_connect_failed(NULL, netdev);
|
||||
}
|
||||
}
|
||||
|
||||
int netdev_fast_transition(struct netdev *netdev, struct scan_bss *target_bss,
|
||||
netdev_connect_cb_t cb)
|
||||
{
|
||||
struct l_genl_msg *cmd_authenticate;
|
||||
uint8_t orig_snonce[32];
|
||||
int err;
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* We reuse the handshake_state object and reset what's needed.
|
||||
* Could also create a new object and copy most of the state but
|
||||
* we would end up doing more work.
|
||||
*/
|
||||
|
||||
memcpy(orig_snonce, netdev->handshake->snonce, 32);
|
||||
handshake_state_new_snonce(netdev->handshake);
|
||||
|
||||
cmd_authenticate = netdev_build_cmd_ft_authenticate(netdev, target_bss,
|
||||
netdev->handshake);
|
||||
if (!cmd_authenticate) {
|
||||
err = -EINVAL;
|
||||
goto restore_snonce;
|
||||
}
|
||||
|
||||
netdev->connect_cmd_id = l_genl_family_send(nl80211,
|
||||
cmd_authenticate,
|
||||
netdev_cmd_authenticate_ft_cb,
|
||||
netdev, NULL);
|
||||
if (!netdev->connect_cmd_id) {
|
||||
l_genl_msg_unref(cmd_authenticate);
|
||||
err = -EIO;
|
||||
goto restore_snonce;
|
||||
}
|
||||
|
||||
memcpy(netdev->prev_bssid, netdev->handshake->aa, ETH_ALEN);
|
||||
handshake_state_set_authenticator_address(netdev->handshake,
|
||||
target_bss->addr);
|
||||
handshake_state_set_ap_rsn(netdev->handshake, target_bss->rsne);
|
||||
memcpy(netdev->handshake->mde + 2, target_bss->mde, 3);
|
||||
|
||||
if (netdev->sm) {
|
||||
eapol_sm_free(netdev->sm);
|
||||
|
||||
netdev->sm = eapol_sm_new(netdev->handshake);
|
||||
eapol_sm_set_use_eapol_start(netdev->sm, false);
|
||||
}
|
||||
|
||||
netdev->operational = false;
|
||||
netdev->in_ft = true;
|
||||
|
||||
netdev->connect_cb = cb;
|
||||
netdev->frequency = target_bss->frequency;
|
||||
|
||||
/*
|
||||
* Cancel commands that could be running because of EAPoL activity
|
||||
* like re-keying, this way the callbacks for those commands don't
|
||||
* have to check if failures resulted from the transition.
|
||||
*/
|
||||
if (netdev->group_new_key_cmd_id) {
|
||||
l_genl_family_cancel(nl80211, netdev->group_new_key_cmd_id);
|
||||
netdev->group_new_key_cmd_id = 0;
|
||||
}
|
||||
|
||||
if (netdev->group_management_new_key_cmd_id) {
|
||||
l_genl_family_cancel(nl80211,
|
||||
netdev->group_management_new_key_cmd_id);
|
||||
netdev->group_management_new_key_cmd_id = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
restore_snonce:
|
||||
memcpy(netdev->handshake->snonce, orig_snonce, 32);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static uint32_t netdev_send_action_frame(struct netdev *netdev,
|
||||
const uint8_t *to,
|
||||
const uint8_t *body, size_t body_len,
|
||||
|
@ -95,6 +95,8 @@ int netdev_connect_wsc(struct netdev *netdev, struct scan_bss *bss,
|
||||
void *user_data);
|
||||
int netdev_disconnect(struct netdev *netdev,
|
||||
netdev_disconnect_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_set_powered(struct netdev *netdev, bool powered,
|
||||
netdev_set_powered_cb_t cb, void *user_data,
|
||||
|
Loading…
Reference in New Issue
Block a user