From d32d94dbf47a3baced194e99931601e18f4e0549 Mon Sep 17 00:00:00 2001 From: Xi Chen Date: Sun, 26 Feb 2012 17:25:55 +0200 Subject: [PATCH] WNM: Add WNM-Sleep Mode implementation for AP Signed-hostap: Jouni Malinen --- hostapd/Makefile | 5 + src/ap/drv_callbacks.c | 8 ++ src/ap/wnm_ap.c | 258 +++++++++++++++++++++++++++++++++++++++++++++++++ src/ap/wnm_ap.h | 17 ++++ src/ap/wpa_auth.c | 132 +++++++++++++++++++++++++ src/ap/wpa_auth.h | 9 ++ src/ap/wpa_auth_i.h | 3 + 7 files changed, 432 insertions(+) create mode 100644 src/ap/wnm_ap.c create mode 100644 src/ap/wnm_ap.h diff --git a/hostapd/Makefile b/hostapd/Makefile index d33a9a7..446210f 100644 --- a/hostapd/Makefile +++ b/hostapd/Makefile @@ -165,6 +165,11 @@ NEED_AES_OMAC1=y NEED_AES_UNWRAP=y endif +ifdef CONFIG_IEEE80211V +CFLAGS += -DCONFIG_IEEE80211V +OBJS += ../src/ap/wnm_ap.o +endif + ifdef CONFIG_IEEE80211N CFLAGS += -DCONFIG_IEEE80211N endif diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c index fca4ae5..c793c6a 100644 --- a/src/ap/drv_callbacks.c +++ b/src/ap/drv_callbacks.c @@ -16,6 +16,7 @@ #include "crypto/random.h" #include "p2p/p2p.h" #include "wps/wps.h" +#include "wnm_ap.h" #include "hostapd.h" #include "ieee802_11.h" #include "sta_info.h" @@ -502,6 +503,13 @@ static void hostapd_action_rx(struct hostapd_data *hapd, action->data + 2); } #endif /* CONFIG_IEEE80211W */ +#ifdef CONFIG_IEEE80211V + if (action->category == WLAN_ACTION_WNM) { + wpa_printf(MSG_DEBUG, "%s: WNM_ACTION length %d", + __func__, (int) action->len); + ieee802_11_rx_wnm_action_ap(hapd, action); + } +#endif /* CONFIG_IEEE80211V */ } diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c new file mode 100644 index 0000000..2594404 --- /dev/null +++ b/src/ap/wnm_ap.c @@ -0,0 +1,258 @@ +/* + * hostapd - WNM + * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#include "utils/includes.h" + +#include "utils/common.h" +#include "common/ieee802_11_defs.h" +#include "ap/hostapd.h" +#include "ap/sta_info.h" +#include "ap/ap_config.h" +#include "ap/ap_drv_ops.h" +#include "ap/wpa_auth.h" +#include "wnm_ap.h" + +#define MAX_TFS_IE_LEN 1024 + +#ifdef CONFIG_IEEE80211V + +/* get the TFS IE from driver */ +static int ieee80211_11_get_tfs_ie(struct hostapd_data *hapd, const u8 *addr, + u8 *buf, u16 *buf_len, enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS get operation %d", __func__, oper); + + return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); +} + + +/* set the TFS IE to driver */ +static int ieee80211_11_set_tfs_ie(struct hostapd_data *hapd, const u8 *addr, + u8 *buf, u16 *buf_len, enum wnm_oper oper) +{ + wpa_printf(MSG_DEBUG, "%s: TFS set operation %d", __func__, oper); + + return hostapd_drv_wnm_oper(hapd, oper, addr, buf, buf_len); +} + + +/* MLME-SLEEPMODE.response */ +static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd, + const u8 *addr, u8 dialog_token, + u8 action_type, u16 intval) +{ + struct ieee80211_mgmt *mgmt; + int res; + size_t len; + size_t gtk_elem_len = 0; + size_t igtk_elem_len = 0; + struct wnm_sleep_element wnmsleep_ie; + u8 *wnmtfs_ie; + u8 wnmsleep_ie_len; + u16 wnmtfs_ie_len; + u8 *pos; + struct sta_info *sta; + enum wnm_oper tfs_oper = action_type == 0 ? WNM_SLEEP_TFS_RESP_IE_ADD : + WNM_SLEEP_TFS_RESP_IE_NONE; + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "%s: station not found", __func__); + return -EINVAL; + } + + /* WNM-Sleep Mode IE */ + os_memset(&wnmsleep_ie, 0, sizeof(struct wnm_sleep_element)); + wnmsleep_ie_len = sizeof(struct wnm_sleep_element); + wnmsleep_ie.eid = WLAN_EID_WNMSLEEP; + wnmsleep_ie.len = wnmsleep_ie_len - 2; + wnmsleep_ie.action_type = action_type; + wnmsleep_ie.status = WNM_STATUS_SLEEP_ACCEPT; + wnmsleep_ie.intval = intval; + + /* TFS IE(s) */ + wnmtfs_ie = os_zalloc(MAX_TFS_IE_LEN); + if (wnmtfs_ie == NULL) + return -1; + if (ieee80211_11_get_tfs_ie(hapd, addr, wnmtfs_ie, &wnmtfs_ie_len, + tfs_oper)) { + wnmtfs_ie_len = 0; + os_free(wnmtfs_ie); + wnmtfs_ie = NULL; + } + +#define MAX_GTK_SUBELEM_LEN 45 +#define MAX_IGTK_SUBELEM_LEN 26 + mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len + + MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN); + if (mgmt == NULL) { + wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for " + "WNM-Sleep Response action frame"); + return -1; + } + os_memcpy(mgmt->da, addr, ETH_ALEN); + os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.wnm_sleep_resp.action = WNM_SLEEP_MODE_RESP; + mgmt->u.action.u.wnm_sleep_resp.dialogtoken = dialog_token; + pos = (u8 *)mgmt->u.action.u.wnm_sleep_resp.variable; + /* add key data if MFP is enabled */ + if (wpa_auth_uses_mfp(sta->wpa_sm) || action_type != 1){ + mgmt->u.action.u.wnm_sleep_resp.keydata_len = 0; + } else { + gtk_elem_len = wpa_wnmsleep_gtk_subelem(sta->wpa_sm, pos); + pos += gtk_elem_len; + wpa_printf(MSG_DEBUG, "Pass 4, gtk_len = %d", + (int) gtk_elem_len); +#ifdef CONFIG_IEEE80211W + res = wpa_wnmsleep_igtk_subelem(sta->wpa_sm, pos); + if (res < 0) { + os_free(wnmtfs_ie); + os_free(mgmt); + return -1; + } + igtk_elem_len = res; + pos += igtk_elem_len; + wpa_printf(MSG_DEBUG, "Pass 4 igtk_len = %d", + (int) igtk_elem_len); +#endif /* CONFIG_IEEE80211W */ + + WPA_PUT_LE16((u8 *) + &mgmt->u.action.u.wnm_sleep_resp.keydata_len, + gtk_elem_len + igtk_elem_len); + } + os_memcpy(pos, &wnmsleep_ie, wnmsleep_ie_len); + /* copy TFS IE here */ + pos += wnmsleep_ie_len; + os_memcpy(pos, wnmtfs_ie, wnmtfs_ie_len); + + len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_resp) + gtk_elem_len + + igtk_elem_len + wnmsleep_ie_len + wnmtfs_ie_len; + + /* In driver, response frame should be forced to sent when STA is in + * PS mode */ + res = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, + mgmt->da, &mgmt->u.action.category, len); + + if (!res) { + wpa_printf(MSG_DEBUG, "Successfully send WNM-Sleep Response " + "frame"); + + /* when entering wnmsleep + * 1. pause the node in driver + * 2. mark the node so that AP won't update GTK/IGTK during + * WNM Sleep + */ + if (wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT && + wnmsleep_ie.action_type == 0) { + hostapd_drv_wnm_oper(hapd, WNM_SLEEP_ENTER_CONFIRM, + addr, NULL, NULL); + wpa_set_wnmsleep(sta->wpa_sm, 1); + } + /* when exiting wnmsleep + * 1. unmark the node + * 2. start GTK/IGTK update if MFP is not used + * 3. unpause the node in driver + */ + if (wnmsleep_ie.status == WNM_STATUS_SLEEP_ACCEPT && + wnmsleep_ie.action_type == 1) { + wpa_set_wnmsleep(sta->wpa_sm, 0); + hostapd_drv_wnm_oper(hapd, WNM_SLEEP_EXIT_CONFIRM, + addr, NULL, NULL); + if (wpa_auth_uses_mfp(sta->wpa_sm) && action_type == 1) + wpa_wnmsleep_rekey_gtk(sta->wpa_sm); + } + } else + wpa_printf(MSG_DEBUG, "Fail to send WNM-Sleep Response frame"); + +#undef MAX_GTK_SUBELEM_LEN +#undef MAX_IGTK_SUBELEM_LEN + os_free(wnmtfs_ie); + os_free(mgmt); + return res; +} + + +static void ieee802_11_rx_wnmsleep_req(struct hostapd_data *hapd, + const u8 *addr, const u8 *frm, int len) +{ + /* + * Action [1] | Dialog Token [1] | WNM-Sleep Mode IE | + * TFS Response IE + */ + u8 *pos = (u8 *) frm; /* point to action field */ + u8 dialog_token = pos[1]; + struct wnm_sleep_element *wnmsleep_ie = NULL; + /* multiple TFS Req IE (assuming consecutive) */ + u8 *tfsreq_ie_start = NULL; + u8 *tfsreq_ie_end = NULL; + u16 tfsreq_ie_len = 0; + + pos += 1 + 1; + while (pos - frm < len - 1) { + u8 ie_len = *(pos+1); + if (*pos == WLAN_EID_WNMSLEEP) + wnmsleep_ie = (struct wnm_sleep_element *)pos; + else if (*pos == WLAN_EID_TFS_REQ) { + if (!tfsreq_ie_start) + tfsreq_ie_start = pos; + tfsreq_ie_end = pos; + } else + wpa_printf(MSG_DEBUG, "EID %d not recognized", *pos); + pos += ie_len + 2; + } + + if (!wnmsleep_ie) { + wpa_printf(MSG_DEBUG, "No WNM-Sleep IE found"); + return; + } + + if (wnmsleep_ie->action_type == 0 && tfsreq_ie_start && + tfsreq_ie_end && tfsreq_ie_end - tfsreq_ie_start >= 0) { + tfsreq_ie_len = (tfsreq_ie_end + tfsreq_ie_end[1] + 2) - + tfsreq_ie_start; + wpa_printf(MSG_DEBUG, "TFS Req IE(s) found"); + /* pass the TFS Req IE(s) to driver for processing */ + if (ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, + &tfsreq_ie_len, + WNM_SLEEP_TFS_REQ_IE_SET)) + wpa_printf(MSG_DEBUG, "Fail to set TFS Req IE"); + } + + ieee802_11_send_wnmsleep_resp(hapd, addr, dialog_token, + wnmsleep_ie->action_type, + wnmsleep_ie->intval); + + if (wnmsleep_ie->action_type == 1) { + /* clear the tfs after sending the resp frame */ + ieee80211_11_set_tfs_ie(hapd, addr, tfsreq_ie_start, + &tfsreq_ie_len, WNM_SLEEP_TFS_IE_DEL); + } +} + + +void ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, + struct rx_action *action) +{ + u8 *pos = (u8 *) action->data + 1; /* point to the action field */ + u8 act = *pos; + + switch (act) { + case WNM_SLEEP_MODE_REQ: + ieee802_11_rx_wnmsleep_req(hapd, action->sa, action->data + 1, + action->len); + break; + default: + break; + } +} + +#endif /* CONFIG_IEEE80211V */ diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h new file mode 100644 index 0000000..ab7c4f1 --- /dev/null +++ b/src/ap/wnm_ap.h @@ -0,0 +1,17 @@ +/* + * IEEE 802.11v WNM related functions and structures + * Copyright (c) 2011-2012, Qualcomm Atheros, Inc. + * + * This software may be distributed under the terms of the BSD license. + * See README for more details. + */ + +#ifndef WNM_AP_H +#define WNM_AP_H + +struct rx_action; + +void ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, + struct rx_action *action); + +#endif /* WNM_AP_H */ diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c index ef49837..374b0a4 100644 --- a/src/ap/wpa_auth.c +++ b/src/ap/wpa_auth.c @@ -2461,6 +2461,12 @@ static int wpa_group_update_sta(struct wpa_state_machine *sm, void *ctx) "marking station for GTK rekeying"); } +#ifdef CONFIG_IEEE80211V + /* Do not rekey GTK/IGTK when STA is in wnmsleep */ + if (sm->is_wnmsleep) + return 0; +#endif /* CONFIG_IEEE80211V */ + sm->group->GKeyDoneStations++; sm->GUpdateStationKeys = TRUE; @@ -2469,6 +2475,132 @@ static int wpa_group_update_sta(struct wpa_state_machine *sm, void *ctx) } +#ifdef CONFIG_IEEE80211V +/* update GTK when exiting wnmsleep mode */ +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm) +{ + if (sm->is_wnmsleep) + return; + + wpa_group_update_sta(sm, NULL); +} + + +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag) +{ + sm->is_wnmsleep = !!flag; +} + + +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + u8 *subelem; + struct wpa_group *gsm = sm->group; + size_t subelem_len, pad_len; + const u8 *key; + size_t key_len; + u8 keybuf[32]; + + /* GTK subslement */ + key_len = gsm->GTK_len; + if (key_len > sizeof(keybuf)) + return 0; + + /* + * Pad key for AES Key Wrap if it is not multiple of 8 bytes or is less + * than 16 bytes. + */ + pad_len = key_len % 8; + if (pad_len) + pad_len = 8 - pad_len; + if (key_len + pad_len < 16) + pad_len += 8; + if (pad_len) { + os_memcpy(keybuf, gsm->GTK[gsm->GN - 1], key_len); + os_memset(keybuf + key_len, 0, pad_len); + keybuf[key_len] = 0xdd; + key_len += pad_len; + key = keybuf; + } else + key = gsm->GTK[gsm->GN - 1]; + + /* + * Sub-elem ID[1] | Length[1] | Key Info[2] | Key Length[1] | RSC[8] | + * Key[5..32] | 8 padding. + */ + subelem_len = 13 + key_len + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return 0; + + subelem[0] = WNM_SLEEP_SUBELEM_GTK; + subelem[1] = 11 + key_len + 8; + /* Key ID in B0-B1 of Key Info */ + WPA_PUT_LE16(&subelem[2], gsm->GN & 0x03); + subelem[4] = gsm->GTK_len; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN, subelem + 5) != 0) + { + os_free(subelem); + return 0; + } + if (aes_wrap(sm->PTK.kek, key_len / 8, key, subelem + 13)) { + os_free(subelem); + return 0; + } + + os_memcpy(pos, subelem, subelem_len); + + wpa_hexdump_key(MSG_DEBUG, "Plaintext GTK", + gsm->GTK[gsm->GN - 1], gsm->GTK_len); + os_free(subelem); + + return subelem_len; +} + + +#ifdef CONFIG_IEEE80211W +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos) +{ + u8 *subelem, *ptr; + struct wpa_group *gsm = sm->group; + size_t subelem_len; + + /* IGTK subelement + * Sub-elem ID[1] | Length[1] | KeyID[2] | PN[6] | + * Key[16] | 8 padding */ + subelem_len = 1 + 1 + 2 + 6 + WPA_IGTK_LEN + 8; + subelem = os_zalloc(subelem_len); + if (subelem == NULL) + return 0; + + ptr = subelem; + *ptr++ = WNM_SLEEP_SUBELEM_IGTK; + *ptr++ = subelem_len - 2; + WPA_PUT_LE16(ptr, gsm->GN_igtk); + ptr += 2; + if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_igtk, ptr) != 0) { + os_free(subelem); + return 0; + } + ptr += 6; + if (aes_wrap(sm->PTK.kek, WPA_IGTK_LEN / 8, + gsm->IGTK[gsm->GN_igtk - 4], ptr)) { + os_free(subelem); + return -1; + } + + os_memcpy(pos, subelem, subelem_len); + + wpa_hexdump_key(MSG_DEBUG, "Plaintext IGTK", + gsm->IGTK[gsm->GN_igtk - 4], WPA_IGTK_LEN); + os_free(subelem); + + return subelem_len; +} +#endif /* CONFIG_IEEE80211W */ +#endif /* CONFIG_IEEE80211V */ + + static void wpa_group_setkeys(struct wpa_authenticator *wpa_auth, struct wpa_group *group) { diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h index 6cfa00b..91ba499 100644 --- a/src/ap/wpa_auth.h +++ b/src/ap/wpa_auth.h @@ -282,4 +282,13 @@ int wpa_ft_rrb_rx(struct wpa_authenticator *wpa_auth, const u8 *src_addr, void wpa_ft_push_pmk_r1(struct wpa_authenticator *wpa_auth, const u8 *addr); #endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_IEEE80211V +void wpa_wnmsleep_rekey_gtk(struct wpa_state_machine *sm); +void wpa_set_wnmsleep(struct wpa_state_machine *sm, int flag); +int wpa_wnmsleep_gtk_subelem(struct wpa_state_machine *sm, u8 *pos); +#ifdef CONFIG_IEEE80211W +int wpa_wnmsleep_igtk_subelem(struct wpa_state_machine *sm, u8 *pos); +#endif /* CONFIG_IEEE80211W */ +#endif /* CONFIG_IEEE80211V */ + #endif /* WPA_AUTH_H */ diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h index b223576..d5cf2c5 100644 --- a/src/ap/wpa_auth_i.h +++ b/src/ap/wpa_auth_i.h @@ -87,6 +87,9 @@ struct wpa_state_machine { unsigned int ft_completed:1; unsigned int pmk_r1_name_valid:1; #endif /* CONFIG_IEEE80211R */ +#ifdef CONFIG_IEEE80211V + unsigned int is_wnmsleep:1; +#endif /* CONFIG_IEEE80211V */ u8 req_replay_counter[WPA_REPLAY_COUNTER_LEN]; int req_replay_counter_used; -- 2.1.4