diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 4c4d51b48..2ba7cc127 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -2721,8 +2721,12 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->time_zone = os_strdup(pos); if (bss->time_zone == NULL) errors++; +#ifdef CONFIG_WNM } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) { bss->wnm_sleep_mode = atoi(pos); + } else if (os_strcmp(buf, "bss_transition") == 0) { + bss->bss_transition = atoi(pos); +#endif /* CONFIG_WNM */ #ifdef CONFIG_INTERWORKING } else if (os_strcmp(buf, "interworking") == 0) { bss->interworking = atoi(pos); diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index bd16b1792..3c9071caa 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -436,6 +436,50 @@ static int hostapd_ctrl_iface_wps_config(struct hostapd_data *hapd, char *txt) #endif /* CONFIG_WPS */ +#ifdef CONFIG_WNM + +static int hostapd_ctrl_iface_disassoc_imminent(struct hostapd_data *hapd, + const char *cmd) +{ + u8 addr[ETH_ALEN]; + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + int disassoc_timer; + + if (hwaddr_aton(cmd, addr)) + return -1; + if (cmd[17] != ' ') + return -1; + disassoc_timer = atoi(cmd + 17); + + os_memset(buf, 0, sizeof(buf)); + mgmt = (struct ieee80211_mgmt *) buf; + mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT, + WLAN_FC_STYPE_ACTION); + 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->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 = + WNM_BSS_TM_REQ_DISASSOC_IMMINENT; + mgmt->u.action.u.bss_tm_req.disassoc_timer = + host_to_le16(disassoc_timer); + mgmt->u.action.u.bss_tm_req.validity_interval = 0; + + pos = mgmt->u.action.u.bss_tm_req.variable; + + if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) { + wpa_printf(MSG_DEBUG, "Failed to send BSS Transition " + "Management Request frame"); + return -1; + } + + return 0; +} + + static int hostapd_ctrl_iface_ess_disassoc(struct hostapd_data *hapd, const char *cmd) { @@ -486,6 +530,8 @@ static int hostapd_ctrl_iface_ess_disassoc(struct hostapd_data *hapd, return 0; } +#endif /* CONFIG_WNM */ + static int hostapd_ctrl_iface_get_config(struct hostapd_data *hapd, char *buf, size_t buflen) @@ -906,9 +952,14 @@ static void hostapd_ctrl_iface_receive(int sock, void *eloop_ctx, hapd, buf + 14, reply, reply_size); #endif /* CONFIG_WPS_NFC */ #endif /* CONFIG_WPS */ +#ifdef CONFIG_WNM + } else if (os_strncmp(buf, "DISASSOC_IMMINENT ", 18) == 0) { + if (hostapd_ctrl_iface_disassoc_imminent(hapd, buf + 18)) + reply_len = -1; } else if (os_strncmp(buf, "ESS_DISASSOC ", 13) == 0) { if (hostapd_ctrl_iface_ess_disassoc(hapd, buf + 13)) reply_len = -1; +#endif /* CONFIG_WNM */ } else if (os_strcmp(buf, "GET_CONFIG") == 0) { reply_len = hostapd_ctrl_iface_get_config(hapd, reply, reply_size); diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index 5a3cdd3df..75b194165 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -1310,6 +1310,11 @@ own_ip_addr=127.0.0.1 # 1 = enabled (allow stations to use WNM-Sleep Mode) #wnm_sleep_mode=1 +# BSS Transition Management +# 0 = disabled (default) +# 1 = enabled +#bss_transition=1 + ##### IEEE 802.11u-2011 ####################################################### # Enable Interworking service diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index 633c13d27..e8e1bb24a 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -539,6 +539,26 @@ static int hostapd_cli_cmd_wps_config(struct wpa_ctrl *ctrl, int argc, #endif /* CONFIG_WPS */ +static int hostapd_cli_cmd_disassoc_imminent(struct wpa_ctrl *ctrl, int argc, + char *argv[]) +{ + char buf[300]; + int res; + + if (argc < 2) { + printf("Invalid 'disassoc_imminent' command - two arguments " + "(STA addr and Disassociation Timer) are needed\n"); + return -1; + } + + res = os_snprintf(buf, sizeof(buf), "DISASSOC_IMMINENT %s %s", + argv[0], argv[1]); + if (res < 0 || res >= (int) sizeof(buf)) + return -1; + return wpa_ctrl_command(ctrl, buf); +} + + static int hostapd_cli_cmd_ess_disassoc(struct wpa_ctrl *ctrl, int argc, char *argv[]) { @@ -775,6 +795,7 @@ static struct hostapd_cli_cmd hostapd_cli_commands[] = { { "wps_ap_pin", hostapd_cli_cmd_wps_ap_pin }, { "wps_config", hostapd_cli_cmd_wps_config }, #endif /* CONFIG_WPS */ + { "disassoc_imminent", hostapd_cli_cmd_disassoc_imminent }, { "ess_disassoc", hostapd_cli_cmd_ess_disassoc }, { "get_config", hostapd_cli_cmd_get_config }, { "help", hostapd_cli_cmd_help }, diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index bc04307dd..a1d2b048b 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -392,6 +392,7 @@ struct hostapd_bss_config { int time_advertisement; char *time_zone; int wnm_sleep_mode; + int bss_transition; /* IEEE 802.11u - Interworking */ int interworking; diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c index 7d87379de..76f78a7db 100644 --- a/src/ap/ieee802_11_shared.c +++ b/src/ap/ieee802_11_shared.c @@ -192,6 +192,8 @@ u8 * hostapd_eid_ext_capab(struct hostapd_data *hapd, u8 *eid) *pos = 0x00; if (hapd->conf->wnm_sleep_mode) *pos |= 0x02; /* Bit 17 - WNM-Sleep Mode */ + if (hapd->conf->bss_transition) + *pos |= 0x08; /* Bit 19 - BSS Transition */ pos++; if (len < 4) diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c index 13961e061..54a6b857d 100644 --- a/src/ap/wnm_ap.c +++ b/src/ap/wnm_ap.c @@ -250,6 +250,15 @@ int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd, return -1; switch (action->data[0]) { + case WNM_BSS_TRANS_MGMT_QUERY: + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Query"); + /* TODO */ + return -1; + case WNM_BSS_TRANS_MGMT_RESP: + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management " + "Response"); + /* TODO */ + return -1; case WNM_SLEEP_MODE_REQ: ieee802_11_rx_wnmsleep_req(hapd, action->sa, action->data + 1, action->len - 1); diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h index 1a9ad1a7d..e873545f6 100644 --- a/src/common/ieee802_11_defs.h +++ b/src/common/ieee802_11_defs.h @@ -538,6 +538,16 @@ struct ieee80211_mgmt { * Entries */ u8 variable[0]; } STRUCT_PACKED bss_tm_req; + struct { + u8 action; /* 8 */ + u8 dialog_token; + u8 status_code; + u8 bss_termination_delay; + /* Target BSSID (optional), + * BSS Transition Candidate List + * Entries (optional) */ + u8 variable[0]; + } STRUCT_PACKED bss_tm_resp; } u; } STRUCT_PACKED action; } u; diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c index 45c0aa84e..4d9e4533e 100644 --- a/wpa_supplicant/wnm_sta.c +++ b/wpa_supplicant/wnm_sta.c @@ -13,6 +13,7 @@ #include "rsn_supp/wpa.h" #include "wpa_supplicant_i.h" #include "driver_i.h" +#include "scan.h" #define MAX_TFS_IE_LEN 1024 @@ -293,11 +294,104 @@ static void ieee802_11_rx_wnmsleep_resp(struct wpa_supplicant *wpa_s, } +static void wnm_send_bss_transition_mgmt_resp(struct wpa_supplicant *wpa_s, + u8 dialog_token, u8 status, + u8 delay, const u8 *target_bssid) +{ + u8 buf[1000], *pos; + struct ieee80211_mgmt *mgmt; + size_t len; + + wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Response " + "to " MACSTR " dialog_token=%u status=%u delay=%d", + MAC2STR(wpa_s->bssid), dialog_token, status, delay); + + mgmt = (struct ieee80211_mgmt *) buf; + os_memset(&buf, 0, sizeof(buf)); + os_memcpy(mgmt->da, wpa_s->bssid, ETH_ALEN); + os_memcpy(mgmt->sa, wpa_s->own_addr, ETH_ALEN); + os_memcpy(mgmt->bssid, wpa_s->bssid, 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.bss_tm_resp.action = WNM_BSS_TRANS_MGMT_RESP; + mgmt->u.action.u.bss_tm_resp.dialog_token = dialog_token; + mgmt->u.action.u.bss_tm_resp.status_code = status; + mgmt->u.action.u.bss_tm_resp.bss_termination_delay = delay; + pos = mgmt->u.action.u.bss_tm_resp.variable; + if (target_bssid) { + os_memcpy(pos, target_bssid, ETH_ALEN); + pos += ETH_ALEN; + } + + len = pos - (u8 *) &mgmt->u.action.category; + + wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid, + wpa_s->own_addr, wpa_s->bssid, + &mgmt->u.action.category, len, 0); +} + + +static void ieee802_11_rx_bss_trans_mgmt_req(struct wpa_supplicant *wpa_s, + const u8 *pos, const u8 *end, + int reply) +{ + u8 dialog_token; + u8 mode; + u16 disassoc_timer; + + if (pos + 5 > end) + return; + + dialog_token = pos[0]; + mode = pos[1]; + disassoc_timer = WPA_GET_LE16(pos + 2); + + wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Request: " + "dialog_token=%u request_mode=0x%x " + "disassoc_timer=%u validity_interval=%u", + dialog_token, mode, disassoc_timer, pos[4]); + pos += 5; + if (mode & 0x08) + pos += 12; /* BSS Termination Duration */ + if (mode & 0x10) { + char url[256]; + if (pos + 1 > end || pos + 1 + pos[0] > end) { + wpa_printf(MSG_DEBUG, "WNM: Invalid BSS Transition " + "Management Request (URL)"); + return; + } + os_memcpy(url, pos + 1, pos[0]); + url[pos[0]] = '\0'; + wpa_msg(wpa_s, MSG_INFO, "WNM: ESS Disassociation Imminent - " + "session_info_url=%s", url); + } + + if (mode & 0x04) { + wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - " + "Disassociation Timer %u", disassoc_timer); + if (disassoc_timer && !wpa_s->scanning) { + /* TODO: mark current BSS less preferred for + * selection */ + wpa_printf(MSG_DEBUG, "Trying to find another BSS"); + wpa_supplicant_req_scan(wpa_s, 0, 0); + } + } + + if (reply) { + /* TODO: add support for reporting Accept */ + wnm_send_bss_transition_mgmt_resp(wpa_s, dialog_token, + 1 /* Reject - unspecified */, + 0, NULL); + } +} + + void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, struct rx_action *action) { const u8 *pos, *end; - u8 act, mode; + u8 act; if (action->data == NULL || action->len == 0) return; @@ -308,32 +402,17 @@ void ieee802_11_rx_wnm_action(struct wpa_supplicant *wpa_s, wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR, act, MAC2STR(action->sa)); + if (wpa_s->wpa_state < WPA_ASSOCIATED || + os_memcmp(action->sa, wpa_s->bssid, ETH_ALEN) != 0) { + wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action " + "frame"); + return; + } switch (act) { case WNM_BSS_TRANS_MGMT_REQ: - if (pos + 5 > end) - break; - wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management " - "Request: dialog_token=%u request_mode=0x%x " - "disassoc_timer=%u validity_interval=%u", - pos[0], pos[1], WPA_GET_LE16(pos + 2), pos[4]); - mode = pos[1]; - pos += 5; - if (mode & 0x08) - pos += 12; /* BSS Termination Duration */ - if (mode & 0x10) { - char url[256]; - if (pos + 1 > end || pos + 1 + pos[0] > end) { - wpa_printf(MSG_DEBUG, "WNM: Invalid BSS " - "Transition Management Request " - "(URL)"); - break; - } - os_memcpy(url, pos + 1, pos[0]); - url[pos[0]] = '\0'; - wpa_msg(wpa_s, MSG_INFO, "WNM: ESS Disassociation " - "Imminent - session_info_url=%s", url); - } + ieee802_11_rx_bss_trans_mgmt_req(wpa_s, pos, end, + !(action->da[0] & 0x01)); break; case WNM_SLEEP_MODE_RESP: ieee802_11_rx_wnmsleep_resp(wpa_s, action->data, action->len); diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 3e4304494..d76f958e3 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -1242,6 +1242,7 @@ int wpas_build_ext_capab(struct wpa_supplicant *wpa_s, u8 *buf) #ifdef CONFIG_WNM ext_capab |= BIT(17); /* WNM-Sleep Mode */ + ext_capab |= BIT(19); /* BSS Transition */ #endif /* CONFIG_WNM */ if (!ext_capab)