From a30dff07fb18421551405bb5c41814675d9436e8 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 22 Nov 2014 11:31:03 +0200 Subject: [PATCH] Add BSS_TM_REQ command to send BSS Transition Management Request hostapd control interface can now be used to request transmission of a BSS Transition Management Request frame to a specified station. Signed-off-by: Jouni Malinen --- hostapd/ctrl_iface.c | 190 ++++++++++++++++++++++++++++++++++++++++++ hostapd/hostapd_cli.c | 28 +++++++ src/ap/wnm_ap.c | 124 +++++++++++++++++++++------ src/ap/wnm_ap.h | 6 +- 4 files changed, 323 insertions(+), 25 deletions(-) diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 162a642e1..fa0e81fa0 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -863,6 +863,193 @@ static int hostapd_ctrl_iface_ess_disassoc(struct hostapd_data *hapd, return wnm_send_ess_disassoc_imminent(hapd, sta, url, disassoc_timer); } + +static int hostapd_ctrl_iface_bss_tm_req(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + const char *pos, *end; + int disassoc_timer = 0; + struct sta_info *sta; + u8 req_mode = 0, valid_int = 0x01; + u8 bss_term_dur[12]; + char *url = NULL; + int ret; + u8 nei_rep[1000]; + u8 *nei_pos = nei_rep; + + if (hwaddr_aton(cmd, addr)) { + wpa_printf(MSG_DEBUG, "Invalid STA MAC address"); + return -1; + } + + sta = ap_get_sta(hapd, addr); + if (sta == NULL) { + wpa_printf(MSG_DEBUG, "Station " MACSTR + " not found for BSS TM Request message", + MAC2STR(addr)); + return -1; + } + + pos = os_strstr(cmd, " disassoc_timer="); + if (pos) { + pos += 16; + disassoc_timer = atoi(pos); + if (disassoc_timer < 0 || disassoc_timer > 65535) { + wpa_printf(MSG_DEBUG, "Invalid disassoc_timer"); + return -1; + } + } + + pos = os_strstr(cmd, " valid_int="); + if (pos) { + pos += 11; + valid_int = atoi(pos); + } + + pos = os_strstr(cmd, " bss_term="); + if (pos) { + pos += 10; + req_mode |= WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED; + /* TODO: TSF configurable/learnable */ + bss_term_dur[0] = 4; /* Subelement ID */ + bss_term_dur[1] = 10; /* Length */ + os_memset(bss_term_dur, 2, 8); + end = os_strchr(pos, ','); + if (end == NULL) { + wpa_printf(MSG_DEBUG, "Invalid bss_term data"); + return -1; + } + end++; + WPA_PUT_LE16(&bss_term_dur[10], atoi(end)); + } + + + /* + * BSS Transition Candidate List Entries - Neighbor Report elements + * neighbor=,,, + * ,[,] + */ + pos = cmd; + while (pos) { + u8 *nei_start; + long int val; + char *endptr, *tmp; + + pos = os_strstr(pos, " neighbor="); + if (!pos) + break; + if (nei_pos + 15 > nei_rep + sizeof(nei_rep)) { + wpa_printf(MSG_DEBUG, + "Not enough room for additional neighbor"); + return -1; + } + pos += 10; + + nei_start = nei_pos; + *nei_pos++ = WLAN_EID_NEIGHBOR_REPORT; + nei_pos++; /* length to be filled in */ + + if (hwaddr_aton(pos, nei_pos)) { + wpa_printf(MSG_DEBUG, "Invalid BSSID"); + return -1; + } + nei_pos += ETH_ALEN; + pos += 17; + if (*pos != ',') { + wpa_printf(MSG_DEBUG, "Missing BSSID Information"); + return -1; + } + pos++; + + val = strtol(pos, &endptr, 0); + WPA_PUT_LE32(nei_pos, val); + nei_pos += 4; + if (*endptr != ',') { + wpa_printf(MSG_DEBUG, "Missing Operating Class"); + return -1; + } + pos = endptr + 1; + + *nei_pos++ = atoi(pos); /* Operating Class */ + pos = os_strchr(pos, ','); + if (pos == NULL) { + wpa_printf(MSG_DEBUG, "Missing Channel Number"); + return -1; + } + pos++; + + *nei_pos++ = atoi(pos); /* Channel Number */ + pos = os_strchr(pos, ','); + if (pos == NULL) { + wpa_printf(MSG_DEBUG, "Missing PHY Type"); + return -1; + } + pos++; + + *nei_pos++ = atoi(pos); /* PHY Type */ + end = os_strchr(pos, ' '); + tmp = os_strchr(pos, ','); + if (tmp && (!end || tmp < end)) { + /* Optional Subelements (hexdump) */ + size_t len; + + pos = tmp + 1; + end = os_strchr(pos, ' '); + if (end) + len = end - pos; + else + len = os_strlen(pos); + if (nei_pos + len / 2 > nei_rep + sizeof(nei_rep)) { + wpa_printf(MSG_DEBUG, + "Not enough room for neighbor subelements"); + return -1; + } + if (len & 0x01 || + hexstr2bin(pos, nei_pos, len / 2) < 0) { + wpa_printf(MSG_DEBUG, + "Invalid neighbor subelement info"); + return -1; + } + nei_pos += len / 2; + pos = end; + } + + nei_start[1] = nei_pos - nei_start - 2; + } + + pos = os_strstr(cmd, " url="); + if (pos) { + size_t len; + pos += 5; + end = os_strchr(pos, ' '); + if (end) + len = end - pos; + else + len = os_strlen(pos); + url = os_malloc(len + 1); + if (url == NULL) + return -1; + os_memcpy(url, pos, len); + url[len] = '\0'; + req_mode |= WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT; + } + + if (os_strstr(cmd, " pref=1")) + req_mode |= WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED; + if (os_strstr(cmd, " abridged=1")) + req_mode |= WNM_BSS_TM_REQ_ABRIDGED; + if (os_strstr(cmd, " disassoc_imminent=1")) + req_mode |= WNM_BSS_TM_REQ_DISASSOC_IMMINENT; + + ret = wnm_send_bss_tm_req(hapd, sta, req_mode, disassoc_timer, + valid_int, bss_term_dur, url, + nei_pos > nei_rep ? nei_rep : NULL, + nei_pos - nei_rep); + os_free(url); + return ret; +} + #endif /* CONFIG_WNM */ @@ -1725,6 +1912,9 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, } else if (os_strncmp(buf, "ESS_DISASSOC ", 13) == 0) { if (hostapd_ctrl_iface_ess_disassoc(hapd, buf + 13)) reply_len = -1; + } else if (os_strncmp(buf, "BSS_TM_REQ ", 11) == 0) { + if (hostapd_ctrl_iface_bss_tm_req(hapd, buf + 11)) + reply_len = -1; #endif /* CONFIG_WNM */ } else if (os_strcmp(buf, "GET_CONFIG") == 0) { reply_len = hostapd_ctrl_iface_get_config(hapd, reply, diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index 283e20fb0..612176293 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -622,6 +622,33 @@ static int hostapd_cli_cmd_ess_disassoc(struct wpa_ctrl *ctrl, int argc, } +static int hostapd_cli_cmd_bss_tm_req(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[2000], *tmp; + int res, i, total; + + if (argc < 1) { + printf("Invalid 'bss_tm_req' command - at least one argument (STA addr) is needed\n"); + return -1; + } + + res = os_snprintf(buf, sizeof(buf), "BSS_TM_REQ %s", argv[0]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + + total = res; + for (i = 1; i < argc; i++) { + tmp = &buf[total]; + res = os_snprintf(tmp, sizeof(buf) - total, " %s", argv[i]); + if (res < 0 || (size_t) res >= sizeof(buf) - total - 1) + return -1; + total += res; + } + return wpa_ctrl_command(ctrl, buf); +} + + static int hostapd_cli_cmd_get_config(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -1010,6 +1037,7 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { #endif /* CONFIG_WPS */ { "disassoc_imminent", hostapd_cli_cmd_disassoc_imminent }, { "ess_disassoc", hostapd_cli_cmd_ess_disassoc }, + { "bss_tm_req", hostapd_cli_cmd_bss_tm_req }, { "get_config", hostapd_cli_cmd_get_config }, { "help", hostapd_cli_cmd_help }, { "interface", hostapd_cli_cmd_interface }, diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c index cf25dbb68..a24900a1f 100644 --- a/src/ap/wnm_ap.c +++ b/src/ap/wnm_ap.c @@ -1,6 +1,6 @@ /* * hostapd - WNM - * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -436,6 +436,34 @@ int wnm_send_disassoc_imminent(struct hostapd_data *hapd, } +static void set_disassoc_timer(struct hostapd_data *hapd, struct sta_info *sta, + int disassoc_timer) +{ + int timeout, beacon_int; + + /* + * Prevent STA from reconnecting using cached PMKSA to force + * full authentication with the authentication server (which may + * decide to reject the connection), + */ + wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); + + beacon_int = hapd->iconf->beacon_int; + if (beacon_int < 1) + beacon_int = 100; /* best guess */ + /* Calculate timeout in ms based on beacon_int in TU */ + timeout = disassoc_timer * beacon_int * 128 / 125; + wpa_printf(MSG_DEBUG, "Disassociation timer for " MACSTR + " set to %d ms", MAC2STR(sta->addr), timeout); + + sta->timeout_next = STA_DISASSOC_FROM_CLI; + eloop_cancel_timeout(ap_handle_timer, hapd, sta); + eloop_register_timeout(timeout / 1000, + timeout % 1000 * 1000, + ap_handle_timer, hapd, sta); +} + + int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, struct sta_info *sta, const char *url, int disassoc_timer) @@ -477,30 +505,78 @@ int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, return -1; } - /* send disassociation frame after time-out */ if (disassoc_timer) { - int timeout, beacon_int; - - /* - * Prevent STA from reconnecting using cached PMKSA to force - * full authentication with the authentication server (which may - * decide to reject the connection), - */ - wpa_auth_pmksa_remove(hapd->wpa_auth, sta->addr); - - beacon_int = hapd->iconf->beacon_int; - if (beacon_int < 1) - beacon_int = 100; /* best guess */ - /* Calculate timeout in ms based on beacon_int in TU */ - timeout = disassoc_timer * beacon_int * 128 / 125; - wpa_printf(MSG_DEBUG, "Disassociation timer for " MACSTR - " set to %d ms", MAC2STR(sta->addr), timeout); - - sta->timeout_next = STA_DISASSOC_FROM_CLI; - eloop_cancel_timeout(ap_handle_timer, hapd, sta); - eloop_register_timeout(timeout / 1000, - timeout % 1000 * 1000, - ap_handle_timer, hapd, sta); + /* send disassociation frame after time-out */ + set_disassoc_timer(hapd, sta, disassoc_timer); + } + + return 0; +} + + +int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta, + u8 req_mode, int disassoc_timer, u8 valid_int, + const u8 *bss_term_dur, const char *url, + const u8 *nei_rep, size_t nei_rep_len) +{ + u8 *buf, *pos; + struct ieee80211_mgmt *mgmt; + size_t url_len; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to " + MACSTR " req_mode=0x%x disassoc_timer=%d valid_int=0x%x", + MAC2STR(sta->addr), req_mode, disassoc_timer, valid_int); + buf = os_zalloc(1000 + nei_rep_len); + if (buf == NULL) + return -1; + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + os_memcpy(mgmt->da, sta->addr, ETH_ALEN); + os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN); + mgmt->u.action.category = WLAN_ACTION_WNM; + mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ; + mgmt->u.action.u.bss_tm_req.dialog_token = 1; + mgmt->u.action.u.bss_tm_req.req_mode = req_mode; + mgmt->u.action.u.bss_tm_req.disassoc_timer = + host_to_le16(disassoc_timer); + mgmt->u.action.u.bss_tm_req.validity_interval = valid_int; + + pos = mgmt->u.action.u.bss_tm_req.variable; + + if ((req_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED) && + bss_term_dur) { + os_memcpy(pos, bss_term_dur, 12); + pos += 12; + } + + if (url) { + /* Session Information URL */ + url_len = os_strlen(url); + if (url_len > 255) + return -1; + *pos++ = url_len; + os_memcpy(pos, url, url_len); + pos += url_len; + } + + if (nei_rep) { + os_memcpy(pos, nei_rep, nei_rep_len); + pos += nei_rep_len; + } + + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { + wpa_printf(MSG_DEBUG, + "Failed to send BSS Transition Management Request frame"); + os_free(buf); + return -1; + } + os_free(buf); + + if (disassoc_timer) { + /* send disassociation frame after time-out */ + set_disassoc_timer(hapd, sta, disassoc_timer); } return 0; diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h index eeaf5eca3..778930720 100644 --- a/src/ap/wnm_ap.h +++ b/src/ap/wnm_ap.h @@ -1,6 +1,6 @@ /* * IEEE 802.11v WNM related functions and structures - * Copyright (c) 2011-2013, Qualcomm Atheros, Inc. + * Copyright (c) 2011-2014, Qualcomm Atheros, Inc. * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -18,5 +18,9 @@ int wnm_send_disassoc_imminent(struct hostapd_data *hapd, int wnm_send_ess_disassoc_imminent(struct hostapd_data *hapd, struct sta_info *sta, const char *url, int disassoc_timer); +int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta, + u8 req_mode, int disassoc_timer, u8 valid_int, + const u8 *bss_term_dur, const char *url, + const u8 *nei_rep, size_t nei_rep_len); #endif /* WNM_AP_H */