From 57acf1e8da1e3e7c0a84fbbde43c5c62873cb372 Mon Sep 17 00:00:00 2001
From: David Weidenkopf <dweidenkopf@cococorp.com>
Date: Thu, 7 Jul 2016 17:33:19 -0700
Subject: [PATCH] WNM: BSS transition (802.11v) support. New CLI command to use
 the BSS transition function.

Signed-off-by: Henry M. Bennett <hbennett@cococorp.com>
Signed-off-by: Alexis Green <agreen@cococorp.com>
Signed-off-by: David Weidenkopf <dweidenkopf@cococorp.com>
---
 hostapd/ctrl_iface.c         |  63 ++++++++++++++++++++
 hostapd/defconfig            |   2 +-
 hostapd/hostapd_cli.c        |  19 ++++++
 src/ap/ieee802_11.c          |  28 ++++++++-
 src/ap/sta_info.h            |   2 +-
 src/ap/wnm_ap.c              | 138 ++++++++++++++++++++++++++++++++++++++++---
 src/ap/wnm_ap.h              |   5 ++
 src/common/ieee802_11_defs.h |  50 ++++++++++++++++
 8 files changed, 295 insertions(+), 12 deletions(-)

diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 62bef18..c26ca1b 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -1070,6 +1070,66 @@ static int hostapd_ctrl_iface_bss_tm_req(struct hostapd_data *hapd,
 	return ret;
 }
 
+static int hostapd_ctrl_iface_bss_transition(struct hostapd_data *hapd,
+					   const char *cmd)
+{
+	u8 addr[ETH_ALEN];
+	u8 ap_addr[ETH_ALEN];
+	const char *timerstr, *apstr, *channelstr;
+	int disassoc_timer;
+	u8 dest_ap_channel;
+	struct sta_info *sta;
+
+	if (hwaddr_aton(cmd, addr))
+		return -1;
+
+	sta = ap_get_sta(hapd, addr);
+	if (sta == NULL) {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_WARNING, " not found for BSS transition message");
+
+		//wpa_printf(MSG_DEBUG, "Station " MACSTR
+		//	   " not found for BSS transition message",
+		//	   MAC2STR(addr));
+		return -1;
+	}
+
+	timerstr = cmd + 17;
+	if (*timerstr != ' ') {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_WARNING, "time not found");
+		return -1;
+	}
+	timerstr++; // skip the space
+	disassoc_timer = atoi(timerstr);
+	if (disassoc_timer < 0 || disassoc_timer > 65535) {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_WARNING, "time out of range");
+		return -1;
+	}
+
+	apstr = os_strchr(timerstr, ' ');
+	if (apstr == NULL) {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_WARNING, "dest AP not found");
+		return -1;
+	}
+	apstr++; // skip the space
+	if (hwaddr_aton(apstr, ap_addr)) {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_WARNING, "dest AP couldn't be decoded");
+		return -1;
+	}
+
+	channelstr = os_strchr(apstr, ' ');
+	channelstr++; // skip the space
+	sscanf(channelstr, "%hhu", &dest_ap_channel); // C99 way of reading in an unsigned char
+	//dest_ap_channel = atoi(channelstr);
+
+	return wnm_send_bss_tm_req2(hapd, sta, disassoc_timer, ap_addr, dest_ap_channel);
+}
+
+
 #endif /* CONFIG_WNM */
 
 
@@ -2415,6 +2475,9 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
 	} else if (os_strncmp(buf, "BSS_TM_REQ ", 11) == 0) {
 		if (hostapd_ctrl_iface_bss_tm_req(hapd, buf + 11))
 			reply_len = -1;
+	} else if (os_strncmp(buf, "BSS_TRANSITION ", 15) == 0) {
+		if (hostapd_ctrl_iface_bss_transition(hapd, buf + 15))
+			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/defconfig b/hostapd/defconfig
index f7b60e0..58eaa31 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -152,7 +152,7 @@ CONFIG_IPV6=y
 
 # Wireless Network Management (IEEE Std 802.11v-2011)
 # Note: This is experimental and not complete implementation.
-#CONFIG_WNM=y
+CONFIG_WNM=y
 
 # IEEE 802.11ac (Very High Throughput) support
 #CONFIG_IEEE80211AC=y
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index ff133f6..222b120 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -686,6 +686,24 @@ static int hostapd_cli_cmd_ess_disassoc(struct wpa_ctrl *ctrl, int argc,
 	return wpa_ctrl_command(ctrl, buf);
 }
 
+static int hostapd_cli_cmd_bss_transition(struct wpa_ctrl *ctrl, int argc,
+					char *argv[])
+{
+	char buf[300];
+	int res;
+
+	if (argc < 4) {
+		printf("Invalid 'bss_transition' command - four arguments (STA "
+		       "addr, disassoc beacon timer, AP addr, AP channel) are needed\n");
+		return -1;
+	}
+
+	res = os_snprintf(buf, sizeof(buf), "BSS_TRANSITION %s %s %s %s",
+			  argv[0], argv[1], argv[2], argv[3]);
+	if (res < 0 || res >= (int) sizeof(buf))
+		return -1;
+	return wpa_ctrl_command(ctrl, buf);
+}
 
 static int hostapd_cli_cmd_bss_tm_req(struct wpa_ctrl *ctrl, int argc,
 				      char *argv[])
@@ -1251,6 +1269,7 @@ static const struct hostapd_cli_cmd hostapd_cli_commands[] = {
 	{ "wps_config", hostapd_cli_cmd_wps_config },
 	{ "wps_get_status", hostapd_cli_cmd_wps_get_status },
 #endif /* CONFIG_WPS */
+	{ "bss_transition", hostapd_cli_cmd_bss_transition },
 	{ "disassoc_imminent", hostapd_cli_cmd_disassoc_imminent },
 	{ "ess_disassoc", hostapd_cli_cmd_ess_disassoc },
 	{ "bss_tm_req", hostapd_cli_cmd_bss_tm_req },
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 11f12f9..ad035e4 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -1494,6 +1494,19 @@ static u16 check_ext_capab(struct hostapd_data *hapd, struct sta_info *sta,
 			sta->qos_map_enabled = 1;
 	}
 #endif /* CONFIG_INTERWORKING */
+#ifdef CONFIG_WNM
+	// BIT#19 BSS Transition
+	// The STA sets the BSS Transition field to 1 when
+	// dot11MgmtOptionBSSTransitionActivated is true, and sets it to 0 otherwise. See
+	// 10.23.6.
+	if (ext_capab_ie_len >= 3) {
+		if (ext_capab_ie[2] & 0x8) {
+			sta->dot11MgmtOptionBSSTransitionActivated = 1;
+			hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO, "WNM: dot11MgmtOptionBSSTransitionActivated is true\n");
+		}
+	}
+#endif
 
 	if (ext_capab_ie_len > 0)
 		sta->ecsa_supported = !!(ext_capab_ie[0] & BIT(2));
@@ -3016,8 +3029,19 @@ int ieee802_11_get_mib(struct hostapd_data *hapd, char *buf, size_t buflen)
 int ieee802_11_get_mib_sta(struct hostapd_data *hapd, struct sta_info *sta,
 			   char *buf, size_t buflen)
 {
-	/* TODO */
-	return 0;
+	int len = 0, ret = 0;
+
+	if(sta->dot11MgmtOptionBSSTransitionActivated)
+		ret = os_snprintf(
+			buf + len, buflen - len,
+			"dot11MgmtOptionBSSTransitionActivated=%u\n",
+			sta->dot11MgmtOptionBSSTransitionActivated);
+
+	if (ret < 0 || (size_t) ret >= buflen - len)
+		return len;
+	len += ret;
+
+	return len;
 }
 
 
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 5d4d0c8..9b1ad21 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -97,7 +97,7 @@ struct sta_info {
 	u16 igtk_key_id;
 	u8 sae_auth_retry;
 #endif /* CONFIG_MESH */
-
+	unsigned int dot11MgmtOptionBSSTransitionActivated:1;
 	unsigned int nonerp_set:1;
 	unsigned int no_short_slot_time_set:1;
 	unsigned int no_short_preamble_set:1;
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 41d50ce..3401714 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -1,6 +1,7 @@
 /*
  * hostapd - WNM
  * Copyright (c) 2011-2014, Qualcomm Atheros, Inc.
+ * Copyright (c) 2015, CoCo Communications, Inc.
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -17,9 +18,15 @@
 #include "ap/ap_config.h"
 #include "ap/ap_drv_ops.h"
 #include "ap/wpa_auth.h"
+#include "ap/wpa_auth_i.h"
+#include "radius/radius.h"
+#include "ap/accounting.h"
+#include "ap/ap_mlme.h"
+#include "ap/ieee802_1x.h"
 #include "mbo_ap.h"
 #include "wnm_ap.h"
 
+#define WNM_DEAUTH_DELAY_USEC 10
 #define MAX_TFS_IE_LEN  1024
 
 
@@ -319,7 +326,8 @@ static void ieee802_11_rx_bss_trans_mgmt_query(struct hostapd_data *hapd,
 	dialog_token = *pos++;
 	reason = *pos++;
 
-	wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Query from "
+	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+				   HOSTAPD_LEVEL_INFO,  "WNM: BSS Transition Management Query from "
 		   MACSTR " dialog_token=%u reason=%u",
 		   MAC2STR(addr), dialog_token, reason);
 
@@ -336,6 +344,7 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 {
 	u8 dialog_token, status_code, bss_termination_delay;
 	const u8 *pos, *end;
+	struct sta_info *sta;
 
 	if (len < 3) {
 		wpa_printf(MSG_DEBUG, "WNM: Ignore too short BSS Transition Management Response from "
@@ -349,7 +358,8 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 	status_code = *pos++;
 	bss_termination_delay = *pos++;
 
-	wpa_printf(MSG_DEBUG, "WNM: BSS Transition Management Response from "
+	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			   HOSTAPD_LEVEL_INFO, "WNM: BSS Transition Management Response from "
 		   MACSTR " dialog_token=%u status_code=%u "
 		   "bss_termination_delay=%u", MAC2STR(addr), dialog_token,
 		   status_code, bss_termination_delay);
@@ -359,14 +369,38 @@ static void ieee802_11_rx_bss_trans_mgmt_resp(struct hostapd_data *hapd,
 			wpa_printf(MSG_DEBUG, "WNM: not enough room for Target BSSID field");
 			return;
 		}
-		wpa_printf(MSG_DEBUG, "WNM: Target BSSID: " MACSTR,
-			   MAC2STR(pos));
-		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
-			" status_code=%u bss_termination_delay=%u target_bssid="
-			MACSTR,
-			MAC2STR(addr), status_code, bss_termination_delay,
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_INFO,  "WNM: BSS Transition Accepted - target BSSID: " MACSTR,
 			MAC2STR(pos));
+
+		sta = ap_get_sta(hapd, addr);
+		if (sta == NULL) {
+			hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+				HOSTAPD_LEVEL_WARNING, "sta "MACSTR" not found for BSS transition response",
+				MAC2STR(addr));
+			return;
+		}
+
+		ap_sta_set_authorized(hapd, sta, 0);
+		sta->flags &= ~WLAN_STA_ASSOC;
+		wpa_auth_sm_event(sta->wpa_sm, WPA_DISASSOC);
+		ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
+		sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
+		accounting_sta_stop(hapd, sta);
+		ieee802_1x_free_station(hapd, sta);
+
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+					   HOSTAPD_LEVEL_INFO, "WNM: disassociated due to accepted BSS transition request");
+
+		sta->timeout_next = STA_DEAUTH;
+		eloop_cancel_timeout(ap_handle_timer, hapd, sta);
+		eloop_register_timeout(0, WNM_DEAUTH_DELAY_USEC, ap_handle_timer, hapd, sta);
+		mlme_disassociate_indication(hapd, sta, WLAN_REASON_DISASSOC_STA_HAS_LEFT);
+
 		pos += ETH_ALEN;
+	} else if (status_code == WNM_BSS_TM_REJECT_NO_SUITABLE_CANDIDATES) {
+		hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+					   HOSTAPD_LEVEL_INFO,  "WNM: BSS Transition Rejected - No Suitable Candidates");
 	} else {
 		wpa_msg(hapd->msg_ctx, MSG_INFO, BSS_TM_RESP MACSTR
 			" status_code=%u bss_termination_delay=%u",
@@ -594,6 +628,7 @@ int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta,
 	if (url) {
 		/* Session Information URL */
 		url_len = os_strlen(url);
+
 		if (url_len > 255) {
 			os_free(buf);
 			return -1;
@@ -629,3 +664,90 @@ int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta,
 
 	return 0;
 }
+
+/*
+802.11-2012 10.23.6.3 BSS transition management request
+The AP may send an unsolicited BSS Transition
+Management Request frame to a non-AP STA at any time if the non-AP STA indicates that it supports the
+BSS Transition Management capability in the Extended Capabilities element.
+A non-AP STA that supports BSS transition management shall
+respond to an individually addressed BSS Transition Management Request frame with a BSS Transition
+Management Response frame.
+This version will create the neighbor report using the passed in bssid and channel
+*/
+int wnm_send_bss_tm_req2(struct hostapd_data *hapd,
+		struct sta_info *sta, int disassoc_timer,
+		const u8 *bssid, u8 ap_channel)
+{
+	static const u8 valid_int = 255;
+	u8 report_ie_len;
+	u8 op_class, channel;
+	struct wnm_neighbor_report_element* report_ie;
+	u8 req_mode = 0;
+	u8* pos = NULL;
+
+	/*
+	802.11-2012
+	The BSS Transition Candidate List Entries field of a BSS Transition Management Response frame contains
+	zero or more Neighbor Report elements describing the non-AP STA’s preferences for target BSS
+	candidates. The Preference field value of a Neighbor Report element used in a BSS Transition Management
+	Response frame shall be between 1 and 255. The value of 0 is reserved. The values between 1 and 255
+	provide the indication of order, with 255 indicating the most preferred BSS within the given candidate list,
+	decreasing numbers representing decreasing preference relative only to entries with lower values of the
+	Preference field, and equal numbers representing equal preference. The non-AP STA should not list any
+	BSS that is not considered as a target BSS candidate.
+
+	Contains the description of candidate BSS transition APs and their capabilities as described in 8.4.2.39.
+	*/
+	/* add 3 octets for candidate preference */
+	report_ie = (struct wnm_neighbor_report_element*)
+			os_zalloc(sizeof(struct wnm_neighbor_report_element) + 3);
+	if (report_ie == NULL) return -1;
+
+	req_mode = WNM_BSS_TM_REQ_ABRIDGED | WNM_BSS_TM_REQ_PREF_CAND_LIST_INCLUDED;
+	if(disassoc_timer > 0) {
+		req_mode |= WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
+	}
+
+	report_ie_len = sizeof(struct wnm_neighbor_report_element);
+	report_ie->eid = WLAN_EID_NEIGHBOR_REPORT;
+	report_ie->len = report_ie_len - 2 + 3;
+	os_memcpy(report_ie->bssid, bssid, ETH_ALEN);
+
+	if (ieee80211_freq_to_channel_ext(hapd->iface->freq,
+						  hapd->iconf->secondary_channel,
+						  hapd->iconf->vht_oper_chwidth,
+						  &op_class, &channel) != NUM_HOSTAPD_MODES)
+		report_ie->operating_class = op_class;
+
+	report_ie->channel_number = ap_channel;
+
+	/*
+	The AP Reachability field indicates whether the AP identified by this BSSID is reachable by the STA that
+	requested the neighbor report. For example, the AP identified by this BSSID is reachable for the exchange of
+	preauthentication frames as described in 11.5.9.2
+
+	The Security bit, if 1, indicates that the AP identified by this BSSID supports the same security provisioning
+	as used by the STA in its current association.
+	*/
+	report_ie->bssid_info[0] = WNM_REACHABILITY_REACHABLE | WNM_SECURITY;
+
+	pos = (u8*) report_ie;
+	pos += report_ie_len;
+
+	/*  BSS Transition Candidate Preference subelement field */
+	*pos++ = 3;   /* Subelement ID */
+	*pos++ = 1;   /* length */
+	*pos++ = 255; /* Preference */
+	report_ie_len += 3;
+
+	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
+			HOSTAPD_LEVEL_INFO, "WNM: Send BSS Transition Management Request "
+		   "client "MACSTR" (disassoc_timer=%d) to AP "MACSTR,
+		   MAC2STR(sta->addr), disassoc_timer, MAC2STR(bssid));
+
+	return wnm_send_bss_tm_req(hapd, sta, req_mode, disassoc_timer,
+				   valid_int, NULL, NULL, (u8*) report_ie,
+				   report_ie_len, NULL, 0);
+}
+
diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h
index a44eadb..bba9dd7 100644
--- a/src/ap/wnm_ap.h
+++ b/src/ap/wnm_ap.h
@@ -1,6 +1,7 @@
 /*
  * IEEE 802.11v WNM related functions and structures
  * Copyright (c) 2011-2014, Qualcomm Atheros, Inc.
+ * Copyright (c) 2015, CoCo Communications, Inc.
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -23,5 +24,9 @@ int wnm_send_bss_tm_req(struct hostapd_data *hapd, struct sta_info *sta,
 			const u8 *bss_term_dur, const char *url,
 			const u8 *nei_rep, size_t nei_rep_len,
 			const u8 *mbo_attrs, size_t mbo_len);
+int wnm_send_bss_tm_req2(struct hostapd_data *hapd,
+			struct sta_info *sta, int disassoc_timer,
+			const u8 *ap_addr, u8 ap_channel);
+
 
 #endif /* WNM_AP_H */
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 02d2ad7..b4343c5 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1433,6 +1433,56 @@ enum wnm_action {
 #define WNM_BSS_TM_REQ_DISASSOC_IMMINENT BIT(2)
 #define WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED BIT(3)
 #define WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT BIT(4)
+/* IEEE Std 802.11-2012 - Figure 8-141 Measurement Report field */
+#define WNM_REACHABILITY_NOT		0x1
+#define WNM_REACHABILITY_UNKNOWN	0x2
+#define WNM_REACHABILITY_REACHABLE	0x3
+
+#define WNM_SECURITY				0x4
+
+#define WNM_MOBILITY_DOMAIN			0x4
+
+/* IEEE 802.11-2012 - 8.4.2.39 Neighbor Report element */
+struct wnm_neighbor_report_element {
+	u8 eid;     /* WLAN_EID_NEIGHBOR_REPORT */
+	u8 len;
+	u8 bssid[6];
+	/*
+	u8 info_ap_reachability:2;
+	u8 info_security:1;
+	u8 info_key_scope:1;
+	u8 info_spectrum_management:1;
+	u8 info_QoS:1;
+	u8 info_APSD:1;
+	u8 info_rm:1;
+	u8 info_delayed_block_ack:1;
+	u8 info_immediate_block_ack:1;
+	u8 info_mobility_domain:1;
+	u8 info_high_throughput:1;
+	u8 info_reserved1:4;
+	*/
+	u8 bssid_info[4];
+	u8 operating_class;
+	u8 channel_number;
+	u8 PHY_type;
+	u8 subelements[0];	/* Optional Subelements */
+} STRUCT_PACKED;
+
+/* IEEE 802.11-2012 Table 8-115 Optional subelement IDs for neighbor report */
+struct wnm_neighbor_report_candidate_preference_subelement {
+	u8 sub_eid; // 3
+	u8 len; // 1
+	u8 preference; // 0 is ignore, 255 is highest
+} STRUCT_PACKED;
+
+/* IEEE 802.11-2012 - 8.4.2.38 AP Channel Report element */
+struct wnm_ap_channel_report_element {
+	u8 eid;     /* WLAN_EID_CHANNEL_REPORT */
+	u8 len;
+	u8 operating_class;
+	u8 channel_list[0];
+} STRUCT_PACKED;
+
 
 /* IEEE Std 802.11-2012 - Table 8-253 */
 enum bss_trans_mgmt_status_code {
-- 
1.9.1

