Cumulative patch from commit 077dcfb8c48d2509a6e116c0de3ad57d2fbfe4fe

077dcfb AP: Debug print management frame TX result
ca911d6 MBO: Parse non-preferred channel list on the AP
3f48274 WNM: Fix a memory leak on AP error path
f5ca176 VLAN: Fix vlan_compare() for tagged VLANs
1260564 hostapd_cli: Add support for RAW command
940491c MBO: Mandate use of PMF for WPA2+MBO association (STA)
4c57228 MBO: Mandate use of PMF for WPA2+MBO association (AP)
8dd49f0 MBO: Update STA cellular data capability based on WNM Notification
6332aaf MBO: Track STA cellular data capability from association request
f3cb7a6 WNM: Minimal processing for WNM Notification Request frames on AP
e578343 MBO: Indicate WNM-Notification support on AP when MBO is enabled
990b7b6 Simplify hostapd_build_ap_extra_ies() with helper functions
d010048 MBO: Expire non-matching bss_tmp_disallowed entries as part of check
f4c74e1 MBO: Parse MBO IE in ieee802_11_parse_elems()
016082e MBO: Send WNM-Notification when cellular capabilities change
c0e2a17 hostapd: Add MBO IE to BSS Transition Management Request frame
fb9a1c3 hostapd: Add MBO IE to Beacon, Probe Response, Association Response
c484b19 Move Hotspot 2.0 element in (Re)Association Request frames
a0c38e5 Re-order elements in (Re)Association Request frames
9a493fa WNM: Add candidate list to BSS transition query
84d1c0f WNM: Add candidate list to BSS transition response
cf11ab7 utils: Derive phy type by frequency and bandwidth
c8082d2 MBO: Add MBO IE to BSS Transition Management Response frame
dd59990 MBO: Parse MBO IE in BSS Transition Management Request frames
5e57ba2 MBO: Add Supported Operating Classes element to Association Request
7d46f58 MBO: Add global operating class definitions
cb06cf3 MBO: Prevent association to APs that explicitly disallow this
c5d193d MBO: Add cellular capability to MBO IE
2d5b861 MBO: Send MBO WNM-Notification Request frames to notify changes
92c6e2e MBO: Implement MBO non-preferred channel report in Association Request
facf2c7 MBO: Add non-preferred channel configuration in wpa_supplicant
425dd78 MBO: Add Multi Band Operation definitions
a159958 ndis: Use the new get_ie() helper to avoid duplicated code
231b04b utils: Share a single helper function to get IE by ID
ea69d97 wpa_supplicant: Share a single get_mode() implementation
75cc211 VLAN: Check vlan_desc validity in a failure debug print
43022ab Use 64-bit TX/RX byte counters for statistics
3f81ac0 AP: Set STA assoc flag in the driver before sending Assoc Resp frame
bb598c3 AP: Add support for full station state
dc55b6b nl80211: Add support for full station state operations
5558b99 EAP-FAST peer: Remove fixed return value from eap_fast_parse_phase1()
4b16c15 EAP-pwd server: Use os_get_random() for unpredictable token
239952b DFS: Remove the os_random() fallback
98a516e WPS: Use only os_get_random() for PIN generation
f441e5a Use os_get_random() for Shared Key authentication challenge
8c676b5 Add RADIUS Service-Type attribute with a value of Framed
09d96de mesh: Drop Authentication frames from BLOCKED STA
70c9396 SAE: Fix PMKID calculation for PMKSA cache
1492fbb Print Acct-Session-Id and Acct-Multi-Session-Id 64-bit values
e21ceca kqueue: Use 0 instead of NULL for udata
640b0b9 ctype functions require an unsigned char
a5a3efc Fix compile on NetBSD for vlan
a084c24 wired: Fix compile on NetBSD for wired driver
634e2e2 Add CONFIG_ELOOP_KQUEUE to defconfig
99a94f5 nl80211: Avoid wpa_printf %s call with NULL pointer in set_param()
ba91e92 wpa_supplicant: Parse ifname argument from DATA_TEST_CONFIG
8be640b VLAN: Add per-STA vif option
d0bdc96 VLAN: Actually add tagged VLANs to AP_VLAN
f9c0018 VLAN: Factor out per-vid code in newlink/dellink
8e44c19 radius: Add tagged VLAN parsing
1889af2 VLAN: Separate station grouping and uplink configuration
3a583e0 OpenSSL: Fix PKCS#12 parsing of extra certificates with OpenSSL 1.0.1
ddd0032 wpa_cli: Clean up logical operation
24c382a TDLS: Clean up os_memcmp use
6136d43 trace: Free symbols on program exit
8bcf8de OpenSSL: Fix memory leak in PKCS12 additional certificate parsing
03e3ddf OpenSSL: Fix memory leak in HMAC_CTX compatibility wrapper function
d9a0f69 OpenSSL: Fix memory leak in OCSP parsing
29bc76e OpenSSL: Do not use library init/deinit functions with 1.1.0
0f09637 OpenSSL: Fix memory leak in subjectAltName parsing
e60913b curl: Fix memory leak in subjectAltName parsing
6014890 OpenSSL: Fix memory leak with EVP_CIPHER_CTX_new()
99a1735 rfkill: Fix a memory leak
1f1e599 OpenSSL: Fix memory leak on error path
b907491 wpa_supplicant: Basic support for PBSS/PCP
86b5c40 nl80211: Basic support for PBSS/PCP
afa453a Sync with mac80211-next.git include/uapi/linux/nl80211.h
d1d8a2b EAP peer: Simplify buildNotify return
1314bc1 Clean up EAP peer PCSC identity functions

Change-Id: I9db475a2a4ebc88d2ee024319ed59a850636bb16
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
diff --git a/src/ap/accounting.c b/src/ap/accounting.c
index f3ce121..9357a46 100644
--- a/src/ap/accounting.c
+++ b/src/ap/accounting.c
@@ -167,19 +167,25 @@
 	if (hostapd_drv_read_sta_data(hapd, data, sta->addr))
 		return -1;
 
-	if (sta->last_rx_bytes > data->rx_bytes)
-		sta->acct_input_gigawords++;
-	if (sta->last_tx_bytes > data->tx_bytes)
-		sta->acct_output_gigawords++;
-	sta->last_rx_bytes = data->rx_bytes;
-	sta->last_tx_bytes = data->tx_bytes;
+	if (!data->bytes_64bit) {
+		/* Extend 32-bit counters from the driver to 64-bit counters */
+		if (sta->last_rx_bytes_lo > data->rx_bytes)
+			sta->last_rx_bytes_hi++;
+		sta->last_rx_bytes_lo = data->rx_bytes;
+
+		if (sta->last_tx_bytes_lo > data->tx_bytes)
+			sta->last_tx_bytes_hi++;
+		sta->last_tx_bytes_lo = data->tx_bytes;
+	}
 
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
-		       HOSTAPD_LEVEL_DEBUG, "updated TX/RX stats: "
-		       "Acct-Input-Octets=%lu Acct-Input-Gigawords=%u "
-		       "Acct-Output-Octets=%lu Acct-Output-Gigawords=%u",
-		       sta->last_rx_bytes, sta->acct_input_gigawords,
-		       sta->last_tx_bytes, sta->acct_output_gigawords);
+		       HOSTAPD_LEVEL_DEBUG,
+		       "updated TX/RX stats: rx_bytes=%llu [%u:%u] tx_bytes=%llu [%u:%u] bytes_64bit=%d",
+		       data->rx_bytes, sta->last_rx_bytes_hi,
+		       sta->last_rx_bytes_lo,
+		       data->tx_bytes, sta->last_tx_bytes_hi,
+		       sta->last_tx_bytes_lo,
+		       data->bytes_64bit);
 
 	return 0;
 }
@@ -220,12 +226,14 @@
 
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
 		       HOSTAPD_LEVEL_INFO,
-		       "starting accounting session %016lX",
-		       (long unsigned int) sta->acct_session_id);
+		       "starting accounting session %016llX",
+		       (unsigned long long) sta->acct_session_id);
 
 	os_get_reltime(&sta->acct_session_start);
-	sta->last_rx_bytes = sta->last_tx_bytes = 0;
-	sta->acct_input_gigawords = sta->acct_output_gigawords = 0;
+	sta->last_rx_bytes_hi = 0;
+	sta->last_rx_bytes_lo = 0;
+	sta->last_tx_bytes_hi = 0;
+	sta->last_tx_bytes_lo = 0;
 	hostapd_drv_sta_clear_stats(hapd, sta->addr);
 
 	if (!hapd->conf->radius->acct_server)
@@ -254,7 +262,7 @@
 	int cause = sta->acct_terminate_cause;
 	struct hostap_sta_driver_data data;
 	struct os_reltime now_r, diff;
-	u32 gigawords;
+	u64 bytes;
 
 	if (!hapd->conf->radius->acct_server)
 		return;
@@ -288,37 +296,37 @@
 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Packets");
 			goto fail;
 		}
+		if (data.bytes_64bit)
+			bytes = data.rx_bytes;
+		else
+			bytes = ((u64) sta->last_rx_bytes_hi << 32) |
+				sta->last_rx_bytes_lo;
 		if (!radius_msg_add_attr_int32(msg,
 					       RADIUS_ATTR_ACCT_INPUT_OCTETS,
-					       data.rx_bytes)) {
+					       (u32) bytes)) {
 			wpa_printf(MSG_INFO, "Could not add Acct-Input-Octets");
 			goto fail;
 		}
-		gigawords = sta->acct_input_gigawords;
-#if __WORDSIZE == 64
-		gigawords += data.rx_bytes >> 32;
-#endif
-		if (gigawords &&
-		    !radius_msg_add_attr_int32(
-			    msg, RADIUS_ATTR_ACCT_INPUT_GIGAWORDS,
-			    gigawords)) {
+		if (!radius_msg_add_attr_int32(msg,
+					       RADIUS_ATTR_ACCT_INPUT_GIGAWORDS,
+					       (u32) (bytes >> 32))) {
 			wpa_printf(MSG_INFO, "Could not add Acct-Input-Gigawords");
 			goto fail;
 		}
+		if (data.bytes_64bit)
+			bytes = data.tx_bytes;
+		else
+			bytes = ((u64) sta->last_tx_bytes_hi << 32) |
+				sta->last_tx_bytes_lo;
 		if (!radius_msg_add_attr_int32(msg,
 					       RADIUS_ATTR_ACCT_OUTPUT_OCTETS,
-					       data.tx_bytes)) {
+					       (u32) bytes)) {
 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Octets");
 			goto fail;
 		}
-		gigawords = sta->acct_output_gigawords;
-#if __WORDSIZE == 64
-		gigawords += data.tx_bytes >> 32;
-#endif
-		if (gigawords &&
-		    !radius_msg_add_attr_int32(
-			    msg, RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS,
-			    gigawords)) {
+		if (!radius_msg_add_attr_int32(msg,
+					       RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS,
+					       (u32) (bytes >> 32))) {
 			wpa_printf(MSG_INFO, "Could not add Acct-Output-Gigawords");
 			goto fail;
 		}
@@ -370,8 +378,8 @@
 		eloop_cancel_timeout(accounting_interim_update, hapd, sta);
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
 			       HOSTAPD_LEVEL_INFO,
-			       "stopped accounting session %016lX",
-			       (long unsigned int) sta->acct_session_id);
+			       "stopped accounting session %016llX",
+			       (unsigned long long) sta->acct_session_id);
 		sta->acct_session_started = 0;
 	}
 }
@@ -430,8 +438,8 @@
 	if (hapd->acct_session_id) {
 		char buf[20];
 
-		os_snprintf(buf, sizeof(buf), "%016lX",
-			    (long unsigned int) hapd->acct_session_id);
+		os_snprintf(buf, sizeof(buf), "%016llX",
+			    (unsigned long long) hapd->acct_session_id);
 		if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
 					 (u8 *) buf, os_strlen(buf)))
 			wpa_printf(MSG_ERROR, "Could not add Acct-Session-Id");
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 88074f2..477ea5b 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -629,7 +629,7 @@
  * Perform a binary search for given MAC address from a pre-sorted list.
  */
 int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
-			  const u8 *addr, int *vlan_id)
+			  const u8 *addr, struct vlan_description *vlan_id)
 {
 	int start, end, middle, res;
 
@@ -669,11 +669,26 @@
 }
 
 
-int hostapd_vlan_id_valid(struct hostapd_vlan *vlan, int vlan_id)
+int hostapd_vlan_valid(struct hostapd_vlan *vlan,
+		       struct vlan_description *vlan_desc)
 {
 	struct hostapd_vlan *v = vlan;
+	int i;
+
+	if (!vlan_desc->notempty || vlan_desc->untagged < 0 ||
+	    vlan_desc->untagged > MAX_VLAN_ID)
+		return 0;
+	for (i = 0; i < MAX_NUM_TAGGED_VLAN; i++) {
+		if (vlan_desc->tagged[i] < 0 ||
+		    vlan_desc->tagged[i] > MAX_VLAN_ID)
+			return 0;
+	}
+	if (!vlan_desc->untagged && !vlan_desc->tagged[0])
+		return 0;
+
 	while (v) {
-		if (v->vlan_id == vlan_id || v->vlan_id == VLAN_ID_WILDCARD)
+		if (!vlan_compare(&v->vlan_desc, vlan_desc) ||
+		    v->vlan_id == VLAN_ID_WILDCARD)
 			return 1;
 		v = v->next;
 	}
@@ -866,6 +881,15 @@
 	}
 #endif /* CONFIG_HS20 */
 
+#ifdef CONFIG_MBO
+	if (full_config && bss->mbo_enabled && (bss->wpa & 2) &&
+	    bss->ieee80211w == NO_MGMT_FRAME_PROTECTION) {
+		wpa_printf(MSG_ERROR,
+			   "MBO: PMF needs to be enabled whenever using WPA2 with MBO");
+		return -1;
+	}
+#endif /* CONFIG_MBO */
+
 	return 0;
 }
 
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 44bcccc..9afca48 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -17,6 +17,7 @@
 #include "common/ieee802_11_common.h"
 #include "wps/wps.h"
 #include "fst/fst.h"
+#include "vlan.h"
 
 /**
  * mesh_conf - local MBSS state and settings
@@ -53,7 +54,7 @@
 
 struct mac_acl_entry {
 	macaddr addr;
-	int vlan_id;
+	struct vlan_description vlan_id;
 };
 
 struct hostapd_radius_servers;
@@ -103,6 +104,7 @@
 #define DYNAMIC_VLAN_NAMING_WITH_DEVICE 1
 #define DYNAMIC_VLAN_NAMING_END 2
 	int vlan_naming;
+	int per_sta_vif;
 #ifdef CONFIG_FULL_DYNAMIC_VLAN
 	char *vlan_tagged_interface;
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
@@ -114,6 +116,7 @@
 struct hostapd_vlan {
 	struct hostapd_vlan *next;
 	int vlan_id; /* VLAN ID or -1 (VLAN_ID_WILDCARD) for wildcard entry */
+	struct vlan_description vlan_desc;
 	char ifname[IFNAMSIZ + 1];
 	int configured;
 	int dynamic_vlan;
@@ -570,6 +573,12 @@
 
 	char *no_probe_resp_if_seen_on;
 	char *no_auth_if_seen_on;
+
+	int pbss;
+
+#ifdef CONFIG_MBO
+	int mbo_enabled;
+#endif /* CONFIG_MBO */
 };
 
 
@@ -688,13 +697,14 @@
 void hostapd_config_free_bss(struct hostapd_bss_config *conf);
 void hostapd_config_free(struct hostapd_config *conf);
 int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
-			  const u8 *addr, int *vlan_id);
+			  const u8 *addr, struct vlan_description *vlan_id);
 int hostapd_rate_found(int *list, int rate);
 const u8 * hostapd_get_psk(const struct hostapd_bss_config *conf,
 			   const u8 *addr, const u8 *p2p_dev_addr,
 			   const u8 *prev_psk);
 int hostapd_setup_wpa_psk(struct hostapd_bss_config *conf);
-int hostapd_vlan_id_valid(struct hostapd_vlan *vlan, int vlan_id);
+int hostapd_vlan_valid(struct hostapd_vlan *vlan,
+		       struct vlan_description *vlan_desc);
 const char * hostapd_get_vlan_id_ifname(struct hostapd_vlan *vlan,
 					int vlan_id);
 struct hostapd_radius_attr *
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index b390450..b89f60e 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -33,10 +33,36 @@
 		res |= WPA_STA_SHORT_PREAMBLE;
 	if (flags & WLAN_STA_MFP)
 		res |= WPA_STA_MFP;
+	if (flags & WLAN_STA_AUTH)
+		res |= WPA_STA_AUTHENTICATED;
+	if (flags & WLAN_STA_ASSOC)
+		res |= WPA_STA_ASSOCIATED;
 	return res;
 }
 
 
+static int add_buf(struct wpabuf **dst, const struct wpabuf *src)
+{
+	if (!src)
+		return 0;
+	if (wpabuf_resize(dst, wpabuf_len(src)) != 0)
+		return -1;
+	wpabuf_put_buf(*dst, src);
+	return 0;
+}
+
+
+static int add_buf_data(struct wpabuf **dst, const u8 *data, size_t len)
+{
+	if (!data || !len)
+		return 0;
+	if (wpabuf_resize(dst, len) != 0)
+		return -1;
+	wpabuf_put_data(*dst, data, len);
+	return 0;
+}
+
+
 int hostapd_build_ap_extra_ies(struct hostapd_data *hapd,
 			       struct wpabuf **beacon_ret,
 			       struct wpabuf **proberesp_ret,
@@ -49,82 +75,38 @@
 
 	pos = buf;
 	pos = hostapd_eid_time_adv(hapd, pos);
-	if (pos != buf) {
-		if (wpabuf_resize(&beacon, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(beacon, buf, pos - buf);
-	}
+	if (add_buf_data(&beacon, buf, pos - buf) < 0)
+		goto fail;
 	pos = hostapd_eid_time_zone(hapd, pos);
-	if (pos != buf) {
-		if (wpabuf_resize(&proberesp, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(proberesp, buf, pos - buf);
-	}
+	if (add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
 
 	pos = buf;
 	pos = hostapd_eid_ext_capab(hapd, pos);
-	if (pos != buf) {
-		if (wpabuf_resize(&assocresp, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(assocresp, buf, pos - buf);
-	}
+	if (add_buf_data(&assocresp, buf, pos - buf) < 0)
+		goto fail;
 	pos = hostapd_eid_interworking(hapd, pos);
 	pos = hostapd_eid_adv_proto(hapd, pos);
 	pos = hostapd_eid_roaming_consortium(hapd, pos);
-	if (pos != buf) {
-		if (wpabuf_resize(&beacon, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(beacon, buf, pos - buf);
-
-		if (wpabuf_resize(&proberesp, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(proberesp, buf, pos - buf);
-	}
+	if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+	    add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
 
 #ifdef CONFIG_FST
-	if (hapd->iface->fst_ies) {
-		size_t add = wpabuf_len(hapd->iface->fst_ies);
-
-		if (wpabuf_resize(&beacon, add) < 0)
-			goto fail;
-		wpabuf_put_buf(beacon, hapd->iface->fst_ies);
-		if (wpabuf_resize(&proberesp, add) < 0)
-			goto fail;
-		wpabuf_put_buf(proberesp, hapd->iface->fst_ies);
-		if (wpabuf_resize(&assocresp, add) < 0)
-			goto fail;
-		wpabuf_put_buf(assocresp, hapd->iface->fst_ies);
-	}
+	if (add_buf(&beacon, hapd->iface->fst_ies) < 0 ||
+	    add_buf(&proberesp, hapd->iface->fst_ies) < 0 ||
+	    add_buf(&assocresp, hapd->iface->fst_ies) < 0)
+		goto fail;
 #endif /* CONFIG_FST */
 
-	if (hapd->wps_beacon_ie) {
-		if (wpabuf_resize(&beacon, wpabuf_len(hapd->wps_beacon_ie)) <
-		    0)
-			goto fail;
-		wpabuf_put_buf(beacon, hapd->wps_beacon_ie);
-	}
-
-	if (hapd->wps_probe_resp_ie) {
-		if (wpabuf_resize(&proberesp,
-				  wpabuf_len(hapd->wps_probe_resp_ie)) < 0)
-			goto fail;
-		wpabuf_put_buf(proberesp, hapd->wps_probe_resp_ie);
-	}
+	if (add_buf(&beacon, hapd->wps_beacon_ie) < 0 ||
+	    add_buf(&proberesp, hapd->wps_probe_resp_ie) < 0)
+		goto fail;
 
 #ifdef CONFIG_P2P
-	if (hapd->p2p_beacon_ie) {
-		if (wpabuf_resize(&beacon, wpabuf_len(hapd->p2p_beacon_ie)) <
-		    0)
-			goto fail;
-		wpabuf_put_buf(beacon, hapd->p2p_beacon_ie);
-	}
-
-	if (hapd->p2p_probe_resp_ie) {
-		if (wpabuf_resize(&proberesp,
-				  wpabuf_len(hapd->p2p_probe_resp_ie)) < 0)
-			goto fail;
-		wpabuf_put_buf(proberesp, hapd->p2p_probe_resp_ie);
-	}
+	if (add_buf(&beacon, hapd->p2p_beacon_ie) < 0 ||
+	    add_buf(&proberesp, hapd->p2p_probe_resp_ie) < 0)
+		goto fail;
 #endif /* CONFIG_P2P */
 
 #ifdef CONFIG_P2P_MANAGER
@@ -148,8 +130,7 @@
 #ifdef CONFIG_WPS
 	if (hapd->conf->wps_state) {
 		struct wpabuf *a = wps_build_assoc_resp_ie();
-		if (a && wpabuf_resize(&assocresp, wpabuf_len(a)) == 0)
-			wpabuf_put_buf(assocresp, a);
+		add_buf(&assocresp, a);
 		wpabuf_free(a);
 	}
 #endif /* CONFIG_WPS */
@@ -169,44 +150,35 @@
 	if (hapd->p2p_group) {
 		struct wpabuf *a;
 		a = p2p_group_assoc_resp_ie(hapd->p2p_group, P2P_SC_SUCCESS);
-		if (a && wpabuf_resize(&assocresp, wpabuf_len(a)) == 0)
-			wpabuf_put_buf(assocresp, a);
+		add_buf(&assocresp, a);
 		wpabuf_free(a);
 	}
 #endif /* CONFIG_WIFI_DISPLAY */
 
 #ifdef CONFIG_HS20
-	pos = buf;
-	pos = hostapd_eid_hs20_indication(hapd, pos);
-	if (pos != buf) {
-		if (wpabuf_resize(&beacon, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(beacon, buf, pos - buf);
-
-		if (wpabuf_resize(&proberesp, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(proberesp, buf, pos - buf);
-	}
+	pos = hostapd_eid_hs20_indication(hapd, buf);
+	if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+	    add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
 
 	pos = hostapd_eid_osen(hapd, buf);
-	if (pos != buf) {
-		if (wpabuf_resize(&beacon, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(beacon, buf, pos - buf);
-
-		if (wpabuf_resize(&proberesp, pos - buf) != 0)
-			goto fail;
-		wpabuf_put_data(proberesp, buf, pos - buf);
-	}
+	if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+	    add_buf_data(&proberesp, buf, pos - buf) < 0)
+		goto fail;
 #endif /* CONFIG_HS20 */
 
-	if (hapd->conf->vendor_elements) {
-		size_t add = wpabuf_len(hapd->conf->vendor_elements);
-		if (wpabuf_resize(&beacon, add) == 0)
-			wpabuf_put_buf(beacon, hapd->conf->vendor_elements);
-		if (wpabuf_resize(&proberesp, add) == 0)
-			wpabuf_put_buf(proberesp, hapd->conf->vendor_elements);
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled) {
+		pos = hostapd_eid_mbo(hapd, buf, sizeof(buf));
+		if (add_buf_data(&beacon, buf, pos - buf) < 0 ||
+		    add_buf_data(&proberesp, buf, pos - buf) < 0 ||
+		    add_buf_data(&assocresp, buf, pos - buf) < 0)
+			goto fail;
 	}
+#endif /* CONFIG_MBO */
+
+	add_buf(&beacon, hapd->conf->vendor_elements);
+	add_buf(&proberesp, hapd->conf->vendor_elements);
 
 	*beacon_ret = beacon;
 	*proberesp_ret = proberesp;
@@ -390,7 +362,7 @@
 		    u16 listen_interval,
 		    const struct ieee80211_ht_capabilities *ht_capab,
 		    const struct ieee80211_vht_capabilities *vht_capab,
-		    u32 flags, u8 qosinfo, u8 vht_opmode)
+		    u32 flags, u8 qosinfo, u8 vht_opmode, int set)
 {
 	struct hostapd_sta_add_params params;
 
@@ -412,6 +384,7 @@
 	params.vht_opmode = vht_opmode;
 	params.flags = hostapd_sta_flags_to_drv(flags);
 	params.qosinfo = qosinfo;
+	params.set = set;
 	return hapd->driver->sta_add(hapd->drv_priv, &params);
 }
 
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 5a1e28e..757a706 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -41,7 +41,7 @@
 		    u16 listen_interval,
 		    const struct ieee80211_ht_capabilities *ht_capab,
 		    const struct ieee80211_vht_capabilities *vht_capab,
-		    u32 flags, u8 qosinfo, u8 vht_opmode);
+		    u32 flags, u8 qosinfo, u8 vht_opmode, int set);
 int hostapd_set_privacy(struct hostapd_data *hapd, int enabled);
 int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem,
 			     size_t elem_len);
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 3276d12..0720e14 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -387,6 +387,9 @@
 		buflen += 5 + 2 + sizeof(struct ieee80211_vht_capabilities) +
 			2 + sizeof(struct ieee80211_vht_operation);
 	}
+
+	buflen += hostapd_mbo_ie_len(hapd);
+
 	resp = os_zalloc(buflen);
 	if (resp == NULL)
 		return NULL;
@@ -518,6 +521,8 @@
 	pos = hostapd_eid_osen(hapd, pos);
 #endif /* CONFIG_HS20 */
 
+	pos = hostapd_eid_mbo(hapd, pos, (u8 *) resp + buflen - pos);
+
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements),
 			  wpabuf_len(hapd->conf->vendor_elements));
@@ -980,6 +985,8 @@
 	}
 #endif /* CONFIG_IEEE80211AC */
 
+	tail_len += hostapd_mbo_ie_len(hapd);
+
 	tailpos = tail = os_malloc(tail_len);
 	if (head == NULL || tail == NULL) {
 		wpa_printf(MSG_ERROR, "Failed to set beacon data");
@@ -1133,6 +1140,8 @@
 	tailpos = hostapd_eid_osen(hapd, tailpos);
 #endif /* CONFIG_HS20 */
 
+	tailpos = hostapd_eid_mbo(hapd, tailpos, tail + tail_len - tailpos);
+
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(tailpos, wpabuf_head(hapd->conf->vendor_elements),
 			  wpabuf_len(hapd->conf->vendor_elements));
@@ -1217,6 +1226,7 @@
 		params->osen = 1;
 	}
 #endif /* CONFIG_HS20 */
+	params->pbss = hapd->conf->pbss;
 	return 0;
 }
 
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index c98978f..a95230e 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -22,6 +22,7 @@
 #include "p2p_hostapd.h"
 #include "ctrl_iface_ap.h"
 #include "ap_drv_ops.h"
+#include "mbo_ap.h"
 
 
 static int hostapd_get_sta_tx_rx(struct hostapd_data *hapd,
@@ -35,7 +36,7 @@
 		return 0;
 
 	ret = os_snprintf(buf, buflen, "rx_packets=%lu\ntx_packets=%lu\n"
-			  "rx_bytes=%lu\ntx_bytes=%lu\n",
+			  "rx_bytes=%llu\ntx_bytes=%llu\n",
 			  data.rx_packets, data.tx_packets,
 			  data.rx_bytes, data.tx_bytes);
 	if (os_snprintf_error(buflen, ret))
@@ -161,6 +162,10 @@
 			len += res;
 	}
 
+	res = mbo_ap_get_info(sta, buf + len, buflen - len);
+	if (res >= 0)
+		len += res;
+
 	return len;
 }
 
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 7273caa..bda23f0 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -450,7 +450,7 @@
 		return NULL;
 
 	if (os_get_random((u8 *) &_rand, sizeof(_rand)) < 0)
-		_rand = os_random();
+		return NULL;
 	chan_idx = _rand % num_available_chandefs;
 	dfs_find_channel(iface, &chan, chan_idx, skip_radar);
 
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 9f53660..702ee64 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -34,6 +34,7 @@
 #include "hw_features.h"
 #include "dfs.h"
 #include "beacon.h"
+#include "mbo_ap.h"
 
 
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
@@ -173,6 +174,8 @@
 		sta->mb_ies = NULL;
 #endif /* CONFIG_FST */
 
+	mbo_ap_check_sta_assoc(hapd, sta, &elems);
+
 	if (hapd->conf->wpa) {
 		if (ie == NULL || ielen == 0) {
 #ifdef CONFIG_WPS
@@ -347,6 +350,17 @@
 			return WLAN_STATUS_INVALID_IE;
 #endif /* CONFIG_HS20 */
 	}
+
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled && (hapd->conf->wpa & 2) &&
+	    elems.mbo && sta->cell_capa && !(sta->flags & WLAN_STA_MFP) &&
+	    hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) {
+		wpa_printf(MSG_INFO,
+			   "MBO: Reject WPA2 association without PMF");
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+#endif /* CONFIG_MBO */
+
 #ifdef CONFIG_WPS
 skip_wpa_check:
 #endif /* CONFIG_WPS */
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index a848f35..dd2dc17 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -683,8 +683,8 @@
 		for (sta = hapd->sta_list; sta; sta = sta->next) {
 			if (!sta->radius_das_match)
 				continue;
-			os_snprintf(buf, sizeof(buf), "%016lX",
-				    (long unsigned int) sta->acct_session_id);
+			os_snprintf(buf, sizeof(buf), "%016llX",
+				    (unsigned long long) sta->acct_session_id);
 			if (os_memcmp(attr->acct_session_id, buf, 16) != 0)
 				sta->radius_das_match = 0;
 			else
@@ -716,8 +716,8 @@
 				sta->radius_das_match = 0;
 				continue;
 			}
-			os_snprintf(buf, sizeof(buf), "%016lX",
-				    (long unsigned int)
+			os_snprintf(buf, sizeof(buf), "%016llX",
+				    (unsigned long long)
 				    sta->eapol_sm->acct_multi_session_id);
 			if (os_memcmp(attr->acct_multi_session_id, buf, 16) !=
 			    0)
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 0f31dd4..d6d96db 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -281,6 +281,10 @@
 
 	struct l2_packet_data *l2_test;
 #endif /* CONFIG_TESTING_OPTIONS */
+
+#ifdef CONFIG_MBO
+	unsigned int mbo_assoc_disallow;
+#endif /* CONFIG_MBO */
 };
 
 
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index ec6f8a7..84e6e15 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -42,6 +42,7 @@
 #include "hw_features.h"
 #include "ieee802_11.h"
 #include "dfs.h"
+#include "mbo_ap.h"
 
 
 u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
@@ -207,16 +208,17 @@
 		if (!sta->challenge) {
 			/* Generate a pseudo-random challenge */
 			u8 key[8];
-			struct os_time now;
-			int r;
+
 			sta->challenge = os_zalloc(WLAN_AUTH_CHALLENGE_LEN);
 			if (sta->challenge == NULL)
 				return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
-			os_get_time(&now);
-			r = os_random();
-			os_memcpy(key, &now.sec, 4);
-			os_memcpy(key + 4, &r, 4);
+			if (os_get_random(key, sizeof(key)) < 0) {
+				os_free(sta->challenge);
+				sta->challenge = NULL;
+				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			}
+
 			rc4_skip(key, sizeof(key), 0,
 				 sta->challenge, WLAN_AUTH_CHALLENGE_LEN);
 		}
@@ -250,19 +252,20 @@
 #endif /* CONFIG_NO_RC4 */
 
 
-static void send_auth_reply(struct hostapd_data *hapd,
-			    const u8 *dst, const u8 *bssid,
-			    u16 auth_alg, u16 auth_transaction, u16 resp,
-			    const u8 *ies, size_t ies_len)
+static int send_auth_reply(struct hostapd_data *hapd,
+			   const u8 *dst, const u8 *bssid,
+			   u16 auth_alg, u16 auth_transaction, u16 resp,
+			   const u8 *ies, size_t ies_len)
 {
 	struct ieee80211_mgmt *reply;
 	u8 *buf;
 	size_t rlen;
+	int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 	rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
 	buf = os_zalloc(rlen);
 	if (buf == NULL)
-		return;
+		return -1;
 
 	reply = (struct ieee80211_mgmt *) buf;
 	reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
@@ -283,9 +286,13 @@
 		   MAC2STR(dst), auth_alg, auth_transaction,
 		   resp, (unsigned long) ies_len);
 	if (hostapd_drv_send_mlme(hapd, reply, rlen, 0) < 0)
-		wpa_printf(MSG_INFO, "send_auth_reply: send");
+		wpa_printf(MSG_INFO, "send_auth_reply: send failed");
+	else
+		reply_res = WLAN_STATUS_SUCCESS;
 
 	os_free(buf);
+
+	return reply_res;
 }
 
 
@@ -296,17 +303,25 @@
 {
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta;
+	int reply_res;
 
-	send_auth_reply(hapd, dst, bssid, WLAN_AUTH_FT, auth_transaction,
-			status, ies, ies_len);
-
-	if (status != WLAN_STATUS_SUCCESS)
-		return;
+	reply_res = send_auth_reply(hapd, dst, bssid, WLAN_AUTH_FT,
+				    auth_transaction, status, ies, ies_len);
 
 	sta = ap_get_sta(hapd, dst);
 	if (sta == NULL)
 		return;
 
+	if (sta->added_unassoc && (reply_res != WLAN_STATUS_SUCCESS ||
+				   status != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+		return;
+	}
+
+	if (status != WLAN_STATUS_SUCCESS)
+		return;
+
 	hostapd_logger(hapd, dst, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG, "authentication OK (FT)");
 	sta->flags |= WLAN_STA_AUTH;
@@ -369,18 +384,19 @@
 				const u8 *bssid, int update)
 {
 	struct wpabuf *data;
+	int reply_res;
 
 	data = auth_build_sae_commit(hapd, sta, update);
 	if (data == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
-	send_auth_reply(hapd, sta->addr, bssid,
-			WLAN_AUTH_SAE, 1, WLAN_STATUS_SUCCESS,
-			wpabuf_head(data), wpabuf_len(data));
+	reply_res = send_auth_reply(hapd, sta->addr, bssid, WLAN_AUTH_SAE, 1,
+				    WLAN_STATUS_SUCCESS, wpabuf_head(data),
+				    wpabuf_len(data));
 
 	wpabuf_free(data);
 
-	return WLAN_STATUS_SUCCESS;
+	return reply_res;
 }
 
 
@@ -389,18 +405,19 @@
 				 const u8 *bssid)
 {
 	struct wpabuf *data;
+	int reply_res;
 
 	data = auth_build_sae_confirm(hapd, sta);
 	if (data == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 
-	send_auth_reply(hapd, sta->addr, bssid,
-			WLAN_AUTH_SAE, 2, WLAN_STATUS_SUCCESS,
-			wpabuf_head(data), wpabuf_len(data));
+	reply_res = send_auth_reply(hapd, sta->addr, bssid, WLAN_AUTH_SAE, 2,
+				    WLAN_STATUS_SUCCESS, wpabuf_head(data),
+				    wpabuf_len(data));
 
 	wpabuf_free(data);
 
-	return WLAN_STATUS_SUCCESS;
+	return reply_res;
 }
 
 
@@ -665,7 +682,7 @@
 			wpa_auth_sm_event(sta->wpa_sm, WPA_AUTH);
 			sta->sae->state = SAE_ACCEPTED;
 			wpa_auth_pmksa_add_sae(hapd->wpa_auth, sta->addr,
-					       sta->sae->pmk);
+					       sta->sae->pmk, sta->sae->pmkid);
 		}
 		break;
 	case SAE_ACCEPTED:
@@ -698,15 +715,20 @@
 			    const struct ieee80211_mgmt *mgmt, size_t len,
 			    u16 auth_transaction, u16 status_code)
 {
-	u16 resp = WLAN_STATUS_SUCCESS;
+	int resp = WLAN_STATUS_SUCCESS;
 	struct wpabuf *data = NULL;
 
 	if (!sta->sae) {
-		if (auth_transaction != 1 || status_code != WLAN_STATUS_SUCCESS)
-			return;
+		if (auth_transaction != 1 ||
+		    status_code != WLAN_STATUS_SUCCESS) {
+			resp = -1;
+			goto remove_sta;
+		}
 		sta->sae = os_zalloc(sizeof(*sta->sae));
-		if (sta->sae == NULL)
-			return;
+		if (!sta->sae) {
+			resp = -1;
+			goto remove_sta;
+		}
 		sta->sae->state = SAE_NOTHING;
 		sta->sae->sync = 0;
 	}
@@ -746,7 +768,8 @@
 			if (sta->sae->tmp->anti_clogging_token == NULL) {
 				wpa_printf(MSG_ERROR,
 					   "SAE: Failed to alloc for anti-clogging token");
-				return;
+				resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+				goto remove_sta;
 			}
 
 			/*
@@ -756,10 +779,11 @@
 			 * Authentication frame, and the commit-scalar and
 			 * COMMIT-ELEMENT previously sent.
 			 */
-			if (auth_sae_send_commit(hapd, sta, mgmt->bssid, 0)) {
+			resp = auth_sae_send_commit(hapd, sta, mgmt->bssid, 0);
+			if (resp != WLAN_STATUS_SUCCESS) {
 				wpa_printf(MSG_ERROR,
 					   "SAE: Failed to send commit message");
-				return;
+				goto remove_sta;
 			}
 			sta->sae->state = SAE_COMMITTED;
 			sta->sae->sync = 0;
@@ -768,7 +792,7 @@
 		}
 
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 
 		resp = sae_parse_commit(sta->sae, mgmt->u.auth.variable,
 					((const u8 *) mgmt) + len -
@@ -778,14 +802,15 @@
 			wpa_printf(MSG_DEBUG,
 				   "SAE: Drop commit message from " MACSTR " due to reflection attack",
 				   MAC2STR(sta->addr));
-			return;
+			goto remove_sta;
 		}
 		if (token && check_sae_token(hapd, sta->addr, token, token_len)
 		    < 0) {
 			wpa_printf(MSG_DEBUG, "SAE: Drop commit message with "
 				   "incorrect token from " MACSTR,
 				   MAC2STR(sta->addr));
-			return;
+			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto remove_sta;
 		}
 
 		if (resp != WLAN_STATUS_SUCCESS)
@@ -810,7 +835,7 @@
 			       "SAE authentication (RX confirm, status=%u)",
 			       status_code);
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 		if (sta->sae->state >= SAE_CONFIRMED ||
 		    !(hapd->conf->mesh & MESH_ENABLED)) {
 			if (sae_check_confirm(sta->sae, mgmt->u.auth.variable,
@@ -827,7 +852,7 @@
 			       "unexpected SAE authentication transaction %u (status=%u)",
 			       auth_transaction, status_code);
 		if (status_code != WLAN_STATUS_SUCCESS)
-			return;
+			goto remove_sta;
 		resp = WLAN_STATUS_UNKNOWN_AUTH_TRANSACTION;
 	}
 
@@ -838,6 +863,13 @@
 				data ? wpabuf_head(data) : (u8 *) "",
 				data ? wpabuf_len(data) : 0);
 	}
+
+remove_sta:
+	if (sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS ||
+				   status_code != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 	wpabuf_free(data);
 }
 
@@ -882,11 +914,11 @@
 	u16 auth_alg, auth_transaction, status_code;
 	u16 resp = WLAN_STATUS_SUCCESS;
 	struct sta_info *sta = NULL;
-	int res;
+	int res, reply_res;
 	u16 fc;
 	const u8 *challenge = NULL;
 	u32 session_timeout, acct_interim_interval;
-	int vlan_id = 0;
+	struct vlan_description vlan_id;
 	struct hostapd_sta_wpa_psk_short *psk = NULL;
 	u8 resp_ies[2 + WLAN_AUTH_CHALLENGE_LEN];
 	size_t resp_ies_len = 0;
@@ -894,6 +926,8 @@
 	char *radius_cui = NULL;
 	u16 seq_ctrl;
 
+	os_memset(&vlan_id, 0, sizeof(vlan_id));
+
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
 		wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
 			   (unsigned long) len);
@@ -1067,13 +1101,22 @@
 				       seq_ctrl);
 			return;
 		}
+#ifdef CONFIG_MESH
+		if ((hapd->conf->mesh & MESH_ENABLED) &&
+		    sta->plink_state == PLINK_BLOCKED) {
+			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
+				   " is blocked - drop Authentication frame",
+				   MAC2STR(mgmt->sa));
+			return;
+		}
+#endif /* CONFIG_MESH */
 	} else {
 #ifdef CONFIG_MESH
 		if (hapd->conf->mesh & MESH_ENABLED) {
 			/* if the mesh peer is not available, we don't do auth.
 			 */
 			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
-				   " not yet known - drop Authentiation frame",
+				   " not yet known - drop Authentication frame",
 				   MAC2STR(mgmt->sa));
 			/*
 			 * Save a copy of the frame so that it can be processed
@@ -1095,19 +1138,23 @@
 	sta->last_seq_ctrl = seq_ctrl;
 	sta->last_subtype = WLAN_FC_STYPE_AUTH;
 
-	if (vlan_id > 0) {
-		if (!hostapd_vlan_id_valid(hapd->conf->vlan, vlan_id)) {
-			hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
-				       HOSTAPD_LEVEL_INFO, "Invalid VLAN ID "
-				       "%d received from RADIUS server",
-				       vlan_id);
-			resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
-			goto fail;
-		}
-		sta->vlan_id = vlan_id;
+	if (vlan_id.notempty &&
+	    !hostapd_vlan_valid(hapd->conf->vlan, &vlan_id)) {
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
+			       HOSTAPD_LEVEL_INFO,
+			       "Invalid VLAN %d%s received from RADIUS server",
+			       vlan_id.untagged,
+			       vlan_id.tagged[0] ? "+" : "");
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto fail;
+	}
+	if (ap_sta_set_vlan(hapd, sta, &vlan_id) < 0) {
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto fail;
+	}
+	if (sta->vlan_id)
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_RADIUS,
 			       HOSTAPD_LEVEL_INFO, "VLAN ID %d", sta->vlan_id);
-	}
 
 	hostapd_free_psk_list(sta->psk);
 	if (hapd->conf->wpa_psk_radius != PSK_RADIUS_IGNORED) {
@@ -1132,6 +1179,46 @@
 	else
 		ap_sta_no_session_timeout(hapd, sta);
 
+	/*
+	 * If the driver supports full AP client state, add a station to the
+	 * driver before sending authentication reply to make sure the driver
+	 * has resources, and not to go through the entire authentication and
+	 * association handshake, and fail it at the end.
+	 *
+	 * If this is not the first transaction, in a multi-step authentication
+	 * algorithm, the station already exists in the driver
+	 * (sta->added_unassoc = 1) so skip it.
+	 *
+	 * In mesh mode, the station was already added to the driver when the
+	 * NEW_PEER_CANDIDATE event is received.
+	 */
+	if (FULL_AP_CLIENT_STATE_SUPP(hapd->iface->drv_flags) &&
+	    !(hapd->conf->mesh & MESH_ENABLED) &&
+	    !(sta->added_unassoc)) {
+		/*
+		 * If a station that is already associated to the AP, is trying
+		 * to authenticate again, remove the STA entry, in order to make
+		 * sure the STA PS state gets cleared and configuration gets
+		 * updated. To handle this, station's added_unassoc flag is
+		 * cleared once the station has completed association.
+		 */
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_AUTH |
+				WLAN_STA_AUTHORIZED);
+
+		if (hostapd_sta_add(hapd, sta->addr, 0, 0, 0, 0, 0,
+				    NULL, NULL, sta->flags, 0, 0, 0)) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_NOTICE,
+				       "Could not add STA to kernel driver");
+			resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+			goto fail;
+		}
+
+		sta->added_unassoc = 1;
+	}
+
 	switch (auth_alg) {
 	case WLAN_AUTH_OPEN:
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
@@ -1205,8 +1292,15 @@
 	os_free(radius_cui);
 	hostapd_free_psk_list(psk);
 
-	send_auth_reply(hapd, mgmt->sa, mgmt->bssid, auth_alg,
-			auth_transaction + 1, resp, resp_ies, resp_ies_len);
+	reply_res = send_auth_reply(hapd, mgmt->sa, mgmt->bssid, auth_alg,
+				    auth_transaction + 1, resp, resp_ies,
+				    resp_ies_len);
+
+	if (sta && sta->added_unassoc && (resp != WLAN_STATUS_SUCCESS ||
+					  reply_res != WLAN_STATUS_SUCCESS)) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 }
 
 
@@ -1620,6 +1714,18 @@
 		sta->mb_ies = NULL;
 #endif /* CONFIG_FST */
 
+#ifdef CONFIG_MBO
+	mbo_ap_check_sta_assoc(hapd, sta, &elems);
+
+	if (hapd->conf->mbo_enabled && (hapd->conf->wpa & 2) &&
+	    elems.mbo && sta->cell_capa && !(sta->flags & WLAN_STA_MFP) &&
+	    hapd->conf->ieee80211w != NO_MGMT_FRAME_PROTECTION) {
+		wpa_printf(MSG_INFO,
+			   "MBO: Reject WPA2 association without PMF");
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+#endif /* CONFIG_MBO */
+
 	return WLAN_STATUS_SUCCESS;
 }
 
@@ -1646,9 +1752,65 @@
 }
 
 
-static void send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
-			    u16 status_code, int reassoc, const u8 *ies,
-			    size_t ies_len)
+static int add_associated_sta(struct hostapd_data *hapd,
+			      struct sta_info *sta)
+{
+	struct ieee80211_ht_capabilities ht_cap;
+	struct ieee80211_vht_capabilities vht_cap;
+
+	/*
+	 * Remove the STA entry to ensure the STA PS state gets cleared and
+	 * configuration gets updated. This is relevant for cases, such as
+	 * FT-over-the-DS, where a station re-associates back to the same AP but
+	 * skips the authentication flow, or if working with a driver that
+	 * does not support full AP client state.
+	 */
+	if (!sta->added_unassoc)
+		hostapd_drv_sta_remove(hapd, sta->addr);
+
+#ifdef CONFIG_IEEE80211N
+	if (sta->flags & WLAN_STA_HT)
+		hostapd_get_ht_capab(hapd, sta->ht_capabilities, &ht_cap);
+#endif /* CONFIG_IEEE80211N */
+#ifdef CONFIG_IEEE80211AC
+	if (sta->flags & WLAN_STA_VHT)
+		hostapd_get_vht_capab(hapd, sta->vht_capabilities, &vht_cap);
+#endif /* CONFIG_IEEE80211AC */
+
+	/*
+	 * Add the station with forced WLAN_STA_ASSOC flag. The sta->flags
+	 * will be set when the ACK frame for the (Re)Association Response frame
+	 * is processed (TX status driver event).
+	 */
+	if (hostapd_sta_add(hapd, sta->addr, sta->aid, sta->capability,
+			    sta->supported_rates, sta->supported_rates_len,
+			    sta->listen_interval,
+			    sta->flags & WLAN_STA_HT ? &ht_cap : NULL,
+			    sta->flags & WLAN_STA_VHT ? &vht_cap : NULL,
+			    sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
+			    sta->vht_opmode, sta->added_unassoc)) {
+		hostapd_logger(hapd, sta->addr,
+			       HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE,
+			       "Could not %s STA to kernel driver",
+			       sta->added_unassoc ? "set" : "add");
+
+		if (sta->added_unassoc) {
+			hostapd_drv_sta_remove(hapd, sta->addr);
+			sta->added_unassoc = 0;
+		}
+
+		return -1;
+	}
+
+	sta->added_unassoc = 0;
+
+	return 0;
+}
+
+
+static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
+			   u16 status_code, int reassoc, const u8 *ies,
+			   size_t ies_len)
 {
 	int send_len;
 	u8 buf[sizeof(struct ieee80211_mgmt) + 1024];
@@ -1766,11 +1928,17 @@
 		p = hostapd_eid_p2p_manage(hapd, p);
 #endif /* CONFIG_P2P_MANAGER */
 
+	p = hostapd_eid_mbo(hapd, p, buf + sizeof(buf) - p);
+
 	send_len += p - reply->u.assoc_resp.variable;
 
-	if (hostapd_drv_send_mlme(hapd, reply, send_len, 0) < 0)
+	if (hostapd_drv_send_mlme(hapd, reply, send_len, 0) < 0) {
 		wpa_printf(MSG_INFO, "Failed to send assoc resp: %s",
 			   strerror(errno));
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	return WLAN_STATUS_SUCCESS;
 }
 
 
@@ -1779,7 +1947,7 @@
 			 int reassoc)
 {
 	u16 capab_info, listen_interval, seq_ctrl, fc;
-	u16 resp = WLAN_STATUS_SUCCESS;
+	u16 resp = WLAN_STATUS_SUCCESS, reply_res;
 	const u8 *pos;
 	int left, i;
 	struct sta_info *sta;
@@ -1846,6 +2014,12 @@
 		wpa_printf(MSG_DEBUG, "FT: Allow STA " MACSTR " to associate "
 			   "prior to authentication since it is using "
 			   "over-the-DS FT", MAC2STR(mgmt->sa));
+
+		/*
+		 * Mark station as authenticated, to avoid adding station
+		 * entry in the driver as associated and not authenticated
+		 */
+		sta->flags |= WLAN_STA_AUTH;
 	} else
 #endif /* CONFIG_IEEE80211R */
 	if (sta == NULL || (sta->flags & WLAN_STA_AUTH) == 0) {
@@ -1889,6 +2063,13 @@
 		goto fail;
 	}
 
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled && hapd->mbo_assoc_disallow) {
+		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+		goto fail;
+	}
+#endif /* CONFIG_MBO */
+
 	/* followed by SSID and Supported rates; and HT capabilities if 802.11n
 	 * is used */
 	resp = check_assoc_ies(hapd, sta, pos, left, reassoc);
@@ -1973,7 +2154,39 @@
 	sta->timeout_next = STA_NULLFUNC;
 
  fail:
-	send_assoc_resp(hapd, sta, resp, reassoc, pos, left);
+	/*
+	 * In case of a successful response, add the station to the driver.
+	 * Otherwise, the kernel may ignore Data frames before we process the
+	 * ACK frame (TX status). In case of a failure, this station will be
+	 * removed.
+	 *
+	 * Note that this is not compliant with the IEEE 802.11 standard that
+	 * states that a non-AP station should transition into the
+	 * authenticated/associated state only after the station acknowledges
+	 * the (Re)Association Response frame. However, still do this as:
+	 *
+	 * 1. In case the station does not acknowledge the (Re)Association
+	 *    Response frame, it will be removed.
+	 * 2. Data frames will be dropped in the kernel until the station is
+	 *    set into authorized state, and there are no significant known
+	 *    issues with processing other non-Data Class 3 frames during this
+	 *    window.
+	 */
+	if (resp == WLAN_STATUS_SUCCESS && add_associated_sta(hapd, sta))
+		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+
+	reply_res = send_assoc_resp(hapd, sta, resp, reassoc, pos, left);
+
+	/*
+	 * Remove the station in case tranmission of a success response fails
+	 * (the STA was added associated to the driver) or if the station was
+	 * previously added unassociated.
+	 */
+	if ((reply_res != WLAN_STATUS_SUCCESS &&
+	     resp == WLAN_STATUS_SUCCESS) || sta->added_unassoc) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 }
 
 
@@ -2015,6 +2228,7 @@
 		hostapd_drv_br_delete_ip_neigh(hapd, 4, (u8 *) &sta->ipaddr);
 	ap_sta_ip6addr_del(hapd, sta);
 	hostapd_drv_sta_remove(hapd, sta->addr);
+	sta->added_unassoc = 0;
 
 	if (sta->timeout_next == STA_NULLFUNC ||
 	    sta->timeout_next == STA_DISASSOC) {
@@ -2391,16 +2605,10 @@
 	u16 auth_alg, auth_transaction, status_code;
 	struct sta_info *sta;
 
-	if (!ok) {
-		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_NOTICE,
-			       "did not acknowledge authentication response");
-		return;
-	}
-
-	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
-		wpa_printf(MSG_INFO, "handle_auth_cb - too short payload (len=%lu)",
-			   (unsigned long) len);
+	sta = ap_get_sta(hapd, mgmt->da);
+	if (!sta) {
+		wpa_printf(MSG_INFO, "handle_auth_cb: STA " MACSTR " not found",
+			   MAC2STR(mgmt->da));
 		return;
 	}
 
@@ -2408,11 +2616,17 @@
 	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
 	status_code = le_to_host16(mgmt->u.auth.status_code);
 
-	sta = ap_get_sta(hapd, mgmt->da);
-	if (!sta) {
-		wpa_printf(MSG_INFO, "handle_auth_cb: STA " MACSTR " not found",
-			   MAC2STR(mgmt->da));
-		return;
+	if (!ok) {
+		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_NOTICE,
+			       "did not acknowledge authentication response");
+		goto fail;
+	}
+
+	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
+		wpa_printf(MSG_INFO, "handle_auth_cb - too short payload (len=%lu)",
+			   (unsigned long) len);
+		goto fail;
 	}
 
 	if (status_code == WLAN_STATUS_SUCCESS &&
@@ -2421,6 +2635,15 @@
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO, "authenticated");
 		sta->flags |= WLAN_STA_AUTH;
+		if (sta->added_unassoc)
+			hostapd_set_sta_flags(hapd, sta);
+		return;
+	}
+
+fail:
+	if (status_code != WLAN_STATUS_SUCCESS && sta->added_unassoc) {
+		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
 	}
 }
 
@@ -2456,15 +2679,6 @@
 	u16 status;
 	struct sta_info *sta;
 	int new_assoc = 1;
-	struct ieee80211_ht_capabilities ht_cap;
-	struct ieee80211_vht_capabilities vht_cap;
-
-	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
-				      sizeof(mgmt->u.assoc_resp))) {
-		wpa_printf(MSG_INFO, "handle_assoc_cb(reassoc=%d) - too short payload (len=%lu)",
-			   reassoc, (unsigned long) len);
-		return;
-	}
 
 	sta = ap_get_sta(hapd, mgmt->da);
 	if (!sta) {
@@ -2473,11 +2687,12 @@
 		return;
 	}
 
-	if (!ok) {
-		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_DEBUG,
-			       "did not acknowledge association response");
-		sta->flags &= ~WLAN_STA_ASSOC_REQ_OK;
+	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
+				      sizeof(mgmt->u.assoc_resp))) {
+		wpa_printf(MSG_INFO,
+			   "handle_assoc_cb(reassoc=%d) - too short payload (len=%lu)",
+			   reassoc, (unsigned long) len);
+		hostapd_drv_sta_remove(hapd, sta->addr);
 		return;
 	}
 
@@ -2486,6 +2701,18 @@
 	else
 		status = le_to_host16(mgmt->u.assoc_resp.status_code);
 
+	if (!ok) {
+		hostapd_logger(hapd, mgmt->da, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "did not acknowledge association response");
+		sta->flags &= ~WLAN_STA_ASSOC_REQ_OK;
+		/* The STA is added only in case of SUCCESS */
+		if (status == WLAN_STATUS_SUCCESS)
+			hostapd_drv_sta_remove(hapd, sta->addr);
+
+		return;
+	}
+
 	if (status != WLAN_STATUS_SUCCESS)
 		return;
 
@@ -2520,38 +2747,6 @@
 	sta->sa_query_timed_out = 0;
 #endif /* CONFIG_IEEE80211W */
 
-	/*
-	 * Remove the STA entry in order to make sure the STA PS state gets
-	 * cleared and configuration gets updated in case of reassociation back
-	 * to the same AP.
-	 */
-	hostapd_drv_sta_remove(hapd, sta->addr);
-
-#ifdef CONFIG_IEEE80211N
-	if (sta->flags & WLAN_STA_HT)
-		hostapd_get_ht_capab(hapd, sta->ht_capabilities, &ht_cap);
-#endif /* CONFIG_IEEE80211N */
-#ifdef CONFIG_IEEE80211AC
-	if (sta->flags & WLAN_STA_VHT)
-		hostapd_get_vht_capab(hapd, sta->vht_capabilities, &vht_cap);
-#endif /* CONFIG_IEEE80211AC */
-
-	if (hostapd_sta_add(hapd, sta->addr, sta->aid, sta->capability,
-			    sta->supported_rates, sta->supported_rates_len,
-			    sta->listen_interval,
-			    sta->flags & WLAN_STA_HT ? &ht_cap : NULL,
-			    sta->flags & WLAN_STA_VHT ? &vht_cap : NULL,
-			    sta->flags, sta->qosinfo, sta->vht_opmode)) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_NOTICE,
-			       "Could not add STA to kernel driver");
-
-		ap_sta_disconnect(hapd, sta, sta->addr,
-				  WLAN_REASON_DISASSOC_AP_BUSY);
-
-		return;
-	}
-
 	if (sta->flags & WLAN_STA_WDS) {
 		int ret;
 		char ifname_wds[IFNAMSIZ + 1];
@@ -2583,7 +2778,6 @@
 	else
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC);
 	hapd->new_assoc_sta_cb(hapd, sta, !new_assoc);
-
 	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
 }
 
@@ -2673,7 +2867,7 @@
 		handle_assoc_cb(hapd, mgmt, len, 1, ok);
 		break;
 	case WLAN_FC_STYPE_PROBE_RESP:
-		wpa_printf(MSG_EXCESSIVE, "mgmt::proberesp cb");
+		wpa_printf(MSG_EXCESSIVE, "mgmt::proberesp cb ok=%d", ok);
 		break;
 	case WLAN_FC_STYPE_DEAUTH:
 		wpa_printf(MSG_DEBUG, "mgmt::deauth cb");
@@ -2684,7 +2878,7 @@
 		handle_disassoc_cb(hapd, mgmt, len, ok);
 		break;
 	case WLAN_FC_STYPE_ACTION:
-		wpa_printf(MSG_DEBUG, "mgmt::action cb");
+		wpa_printf(MSG_DEBUG, "mgmt::action cb ok=%d", ok);
 		break;
 	default:
 		wpa_printf(MSG_INFO, "unknown mgmt cb frame subtype %d", stype);
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 0020ff5..78db204 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -109,4 +109,25 @@
 }
 #endif /* CONFIG_SAE */
 
+#ifdef CONFIG_MBO
+
+u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len);
+
+u8 hostapd_mbo_ie_len(struct hostapd_data *hapd);
+
+#else /* CONFIG_MBO */
+
+static inline u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid,
+				   size_t len)
+{
+	return eid;
+}
+
+static inline u8 hostapd_mbo_ie_len(struct hostapd_data *hapd)
+{
+	return 0;
+}
+
+#endif /* CONFIG_MBO */
+
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index ec0037a..f280709 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -35,7 +35,7 @@
 	struct hostapd_cached_radius_acl *next;
 	u32 session_timeout;
 	u32 acct_interim_interval;
-	int vlan_id;
+	struct vlan_description vlan_id;
 	struct hostapd_sta_wpa_psk_short *psk;
 	char *identity;
 	char *radius_cui;
@@ -99,7 +99,8 @@
 
 static int hostapd_acl_cache_get(struct hostapd_data *hapd, const u8 *addr,
 				 u32 *session_timeout,
-				 u32 *acct_interim_interval, int *vlan_id,
+				 u32 *acct_interim_interval,
+				 struct vlan_description *vlan_id,
 				 struct hostapd_sta_wpa_psk_short **psk,
 				 char **identity, char **radius_cui)
 {
@@ -222,7 +223,8 @@
  * @vlan_id: Buffer for returning VLAN ID
  * Returns: HOSTAPD_ACL_ACCEPT, HOSTAPD_ACL_REJECT, or HOSTAPD_ACL_PENDING
  */
- int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, int *vlan_id)
+int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr,
+		      struct vlan_description *vlan_id)
 {
 	if (hostapd_maclist_found(hapd->conf->accept_mac,
 				  hapd->conf->num_accept_mac, addr, vlan_id))
@@ -260,7 +262,8 @@
  */
 int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 			    const u8 *msg, size_t len, u32 *session_timeout,
-			    u32 *acct_interim_interval, int *vlan_id,
+			    u32 *acct_interim_interval,
+			    struct vlan_description *vlan_id,
 			    struct hostapd_sta_wpa_psk_short **psk,
 			    char **identity, char **radius_cui)
 {
@@ -271,7 +274,7 @@
 	if (acct_interim_interval)
 		*acct_interim_interval = 0;
 	if (vlan_id)
-		*vlan_id = 0;
+		os_memset(vlan_id, 0, sizeof(*vlan_id));
 	if (psk)
 		*psk = NULL;
 	if (identity)
@@ -499,6 +502,7 @@
 	struct hostapd_acl_query_data *query, *prev;
 	struct hostapd_cached_radius_acl *cache;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
+	int *untagged, *tagged, *notempty;
 
 	query = hapd->acl_queries;
 	prev = NULL;
@@ -556,7 +560,12 @@
 			cache->acct_interim_interval = 0;
 		}
 
-		cache->vlan_id = radius_msg_get_vlanid(msg);
+		notempty = &cache->vlan_id.notempty;
+		untagged = &cache->vlan_id.untagged;
+		tagged = cache->vlan_id.tagged;
+		*notempty = !!radius_msg_get_vlanid(msg, untagged,
+						    MAX_NUM_TAGGED_VLAN,
+						    tagged);
 
 		decode_tunnel_passwords(hapd, shared_secret, shared_secret_len,
 					msg, req, cache);
@@ -579,17 +588,18 @@
 		    !cache->psk)
 			cache->accepted = HOSTAPD_ACL_REJECT;
 
-		if (cache->vlan_id &&
-		    !hostapd_vlan_id_valid(hapd->conf->vlan, cache->vlan_id)) {
+		if (cache->vlan_id.notempty &&
+		    !hostapd_vlan_valid(hapd->conf->vlan, &cache->vlan_id)) {
 			hostapd_logger(hapd, query->addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO,
-				       "Invalid VLAN ID %d received from RADIUS server",
-				       cache->vlan_id);
-			cache->vlan_id = 0;
+				       "Invalid VLAN %d%s received from RADIUS server",
+				       cache->vlan_id.untagged,
+				       cache->vlan_id.tagged[0] ? "+" : "");
+			os_memset(&cache->vlan_id, 0, sizeof(cache->vlan_id));
 		}
 		if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED &&
-		    !cache->vlan_id)
+		    !cache->vlan_id.notempty)
 			cache->accepted = HOSTAPD_ACL_REJECT;
 	} else
 		cache->accepted = HOSTAPD_ACL_REJECT;
diff --git a/src/ap/ieee802_11_auth.h b/src/ap/ieee802_11_auth.h
index da81c14..71f53b9 100644
--- a/src/ap/ieee802_11_auth.h
+++ b/src/ap/ieee802_11_auth.h
@@ -16,10 +16,12 @@
 	HOSTAPD_ACL_ACCEPT_TIMEOUT = 3
 };
 
-int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr, int *vlan_id);
+int hostapd_check_acl(struct hostapd_data *hapd, const u8 *addr,
+		      struct vlan_description *vlan_id);
 int hostapd_allowed_address(struct hostapd_data *hapd, const u8 *addr,
 			    const u8 *msg, size_t len, u32 *session_timeout,
-			    u32 *acct_interim_interval, int *vlan_id,
+			    u32 *acct_interim_interval,
+			    struct vlan_description *vlan_id,
 			    struct hostapd_sta_wpa_psk_short **psk,
 			    char **identity, char **radius_cui);
 int hostapd_acl_init(struct hostapd_data *hapd);
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index 9e3363e..1077635 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -209,6 +209,10 @@
 		if (hapd->conf->hs20)
 			*pos |= 0x40; /* Bit 46 - WNM-Notification */
 #endif /* CONFIG_HS20 */
+#ifdef CONFIG_MBO
+		if (hapd->conf->mbo_enabled)
+			*pos |= 0x40; /* Bit 46 - WNM-Notification */
+#endif /* CONFIG_MBO */
 		break;
 	case 6: /* Bits 48-55 */
 		if (hapd->conf->ssid.utf8_ssid)
@@ -241,6 +245,10 @@
 	if (hapd->conf->hs20 && len < 6)
 		len = 6;
 #endif /* CONFIG_HS20 */
+#ifdef CONFIG_MBO
+	if (hapd->conf->mbo_enabled && len < 6)
+		len = 6;
+#endif /* CONFIG_MBO */
 	if (len < hapd->iface->extended_capa_len)
 		len = hapd->iface->extended_capa_len;
 	if (len == 0)
@@ -508,3 +516,45 @@
 
 	return pos;
 }
+
+
+#ifdef CONFIG_MBO
+
+u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
+{
+	u8 mbo[6], *mbo_pos = mbo;
+	u8 *pos = eid;
+
+	if (!hapd->conf->mbo_enabled)
+		return eid;
+
+	*mbo_pos++ = MBO_ATTR_ID_AP_CAPA_IND;
+	*mbo_pos++ = 1;
+	/* Not Cellular aware */
+	*mbo_pos++ = 0;
+
+	if (hapd->mbo_assoc_disallow) {
+		*mbo_pos++ = MBO_ATTR_ID_ASSOC_DISALLOW;
+		*mbo_pos++ = 1;
+		*mbo_pos++ = hapd->mbo_assoc_disallow;
+	}
+
+	pos += mbo_add_ie(pos, len, mbo, mbo_pos - mbo);
+
+	return pos;
+}
+
+
+u8 hostapd_mbo_ie_len(struct hostapd_data *hapd)
+{
+	if (!hapd->conf->mbo_enabled)
+		return 0;
+
+	/*
+	 * MBO IE header (6) + Capability Indication attribute (3) +
+	 * Association Disallowed attribute (3) = 12
+	 */
+	return 6 + 3 + (hapd->mbo_assoc_disallow ? 3 : 0);
+}
+
+#endif /* CONFIG_MBO */
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index d399b1e..c774d5c 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -222,7 +222,7 @@
 		   MAC2STR(sta->addr));
 
 #ifndef CONFIG_NO_VLAN
-	if (sta->vlan_id > 0 && sta->vlan_id <= MAX_VLAN_ID) {
+	if (sta->vlan_id > 0) {
 		wpa_printf(MSG_ERROR, "Using WEP with vlans is not supported.");
 		return;
 	}
@@ -405,6 +405,14 @@
 	char buf[128];
 
 	if (!hostapd_config_get_radius_attr(req_attr,
+					    RADIUS_ATTR_SERVICE_TYPE) &&
+	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_SERVICE_TYPE,
+				       RADIUS_SERVICE_TYPE_FRAMED)) {
+		wpa_printf(MSG_ERROR, "Could not add Service-Type");
+		return -1;
+	}
+
+	if (!hostapd_config_get_radius_attr(req_attr,
 					    RADIUS_ATTR_NAS_PORT) &&
 	    !radius_msg_add_attr_int32(msg, RADIUS_ATTR_NAS_PORT, sta->aid)) {
 		wpa_printf(MSG_ERROR, "Could not add NAS-Port");
@@ -439,8 +447,8 @@
 	}
 
 	if (sta->acct_session_id) {
-		os_snprintf(buf, sizeof(buf), "%016lX",
-			    (long unsigned int) sta->acct_session_id);
+		os_snprintf(buf, sizeof(buf), "%016llX",
+			    (unsigned long long) sta->acct_session_id);
 		if (!radius_msg_add_attr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
 					 (u8 *) buf, os_strlen(buf))) {
 			wpa_printf(MSG_ERROR, "Could not add Acct-Session-Id");
@@ -451,8 +459,8 @@
 	if ((hapd->conf->wpa & 2) &&
 	    !hapd->conf->disable_pmksa_caching &&
 	    sta->eapol_sm && sta->eapol_sm->acct_multi_session_id) {
-		os_snprintf(buf, sizeof(buf), "%016lX",
-			    (long unsigned int)
+		os_snprintf(buf, sizeof(buf), "%016llX",
+			    (unsigned long long)
 			    sta->eapol_sm->acct_multi_session_id);
 		if (!radius_msg_add_attr(
 			    msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
@@ -1151,7 +1159,7 @@
 		sta->eapol_sm->authFail = FALSE;
 		if (sta->eapol_sm->eap)
 			eap_sm_notify_cached(sta->eapol_sm->eap);
-		pmksa_cache_to_eapol_data(pmksa, sta->eapol_sm);
+		pmksa_cache_to_eapol_data(hapd, pmksa, sta->eapol_sm);
 		ap_sta_bind_vlan(hapd, sta);
 	} else {
 		if (reassoc) {
@@ -1617,10 +1625,16 @@
 	struct hostapd_data *hapd = data;
 	struct sta_info *sta;
 	u32 session_timeout = 0, termination_action, acct_interim_interval;
-	int session_timeout_set, vlan_id = 0;
+	int session_timeout_set;
 	struct eapol_state_machine *sm;
 	int override_eapReq = 0;
 	struct radius_hdr *hdr = radius_msg_get_hdr(msg);
+	struct vlan_description vlan_desc;
+#ifndef CONFIG_NO_VLAN
+	int *untagged, *tagged, *notempty;
+#endif /* CONFIG_NO_VLAN */
+
+	os_memset(&vlan_desc, 0, sizeof(vlan_desc));
 
 	sm = ieee802_1x_search_radius_identifier(hapd, hdr->identifier);
 	if (sm == NULL) {
@@ -1684,27 +1698,32 @@
 
 	switch (hdr->code) {
 	case RADIUS_CODE_ACCESS_ACCEPT:
-		if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
-			vlan_id = 0;
 #ifndef CONFIG_NO_VLAN
-		else
-			vlan_id = radius_msg_get_vlanid(msg);
-		if (vlan_id > 0 &&
-		    hostapd_vlan_id_valid(hapd->conf->vlan, vlan_id)) {
-			hostapd_logger(hapd, sta->addr,
-				       HOSTAPD_MODULE_RADIUS,
-				       HOSTAPD_LEVEL_INFO,
-				       "VLAN ID %d", vlan_id);
-		} else if (vlan_id > 0) {
+		if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED) {
+			notempty = &vlan_desc.notempty;
+			untagged = &vlan_desc.untagged;
+			tagged = vlan_desc.tagged;
+			*notempty = !!radius_msg_get_vlanid(msg, untagged,
+							    MAX_NUM_TAGGED_VLAN,
+							    tagged);
+		}
+
+		if (vlan_desc.notempty &&
+		    !hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
 			sta->eapol_sm->authFail = TRUE;
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_INFO,
-				       "Invalid VLAN ID %d received from RADIUS server",
-				       vlan_id);
+				       "Invalid VLAN %d%s received from RADIUS server",
+				       vlan_desc.untagged,
+				       vlan_desc.tagged[0] ? "+" : "");
+			os_memset(&vlan_desc, 0, sizeof(vlan_desc));
+			ap_sta_set_vlan(hapd, sta, &vlan_desc);
 			break;
-		} else if (hapd->conf->ssid.dynamic_vlan ==
-			   DYNAMIC_VLAN_REQUIRED) {
+		}
+
+		if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_REQUIRED &&
+		    !vlan_desc.notempty) {
 			sta->eapol_sm->authFail = TRUE;
 			hostapd_logger(hapd, sta->addr,
 				       HOSTAPD_MODULE_IEEE8021X,
@@ -1715,7 +1734,18 @@
 		}
 #endif /* CONFIG_NO_VLAN */
 
-		sta->vlan_id = vlan_id;
+		if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0)
+			break;
+
+#ifndef CONFIG_NO_VLAN
+		if (sta->vlan_id > 0) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_RADIUS,
+				       HOSTAPD_LEVEL_INFO,
+				       "VLAN ID %d", sta->vlan_id);
+		}
+#endif /* CONFIG_NO_VLAN */
+
 		if ((sta->flags & WLAN_STA_ASSOC) &&
 		    ap_sta_bind_vlan(hapd, sta) < 0)
 			break;
@@ -2511,12 +2541,12 @@
 			  /* TODO: dot1xAuthSessionOctetsTx */
 			  /* TODO: dot1xAuthSessionFramesRx */
 			  /* TODO: dot1xAuthSessionFramesTx */
-			  "dot1xAuthSessionId=%016lX\n"
+			  "dot1xAuthSessionId=%016llX\n"
 			  "dot1xAuthSessionAuthenticMethod=%d\n"
 			  "dot1xAuthSessionTime=%u\n"
 			  "dot1xAuthSessionTerminateCause=999\n"
 			  "dot1xAuthSessionUserName=%s\n",
-			  (long unsigned int) sta->acct_session_id,
+			  (unsigned long long) sta->acct_session_id,
 			  (wpa_key_mgmt_wpa_ieee8021x(
 				   wpa_auth_sta_key_mgmt(sta->wpa_sm))) ?
 			  1 : 2,
@@ -2528,8 +2558,8 @@
 
 	if (sm->acct_multi_session_id) {
 		ret = os_snprintf(buf + len, buflen - len,
-				  "authMultiSessionId=%016lX\n",
-				  (long unsigned int)
+				  "authMultiSessionId=%016llX\n",
+				  (unsigned long long)
 				  sm->acct_multi_session_id);
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
diff --git a/src/ap/mbo_ap.c b/src/ap/mbo_ap.c
new file mode 100644
index 0000000..5e0f92a
--- /dev/null
+++ b/src/ap/mbo_ap.c
@@ -0,0 +1,245 @@
+/*
+ * hostapd - MBO
+ * Copyright (c) 2016, 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 "common/ieee802_11_common.h"
+#include "hostapd.h"
+#include "sta_info.h"
+#include "mbo_ap.h"
+
+
+void mbo_ap_sta_free(struct sta_info *sta)
+{
+	struct mbo_non_pref_chan_info *info, *prev;
+
+	info = sta->non_pref_chan;
+	sta->non_pref_chan = NULL;
+	while (info) {
+		prev = info;
+		info = info->next;
+		os_free(prev);
+	}
+}
+
+
+static void mbo_ap_parse_non_pref_chan(struct sta_info *sta,
+				       const u8 *buf, size_t len)
+{
+	struct mbo_non_pref_chan_info *info, *tmp;
+	char channels[200], *pos, *end;
+	size_t num_chan, i;
+	int ret;
+
+	if (len <= 4)
+		return; /* Not enough room for any channels */
+
+	num_chan = len - 4;
+	info = os_zalloc(sizeof(*info) + num_chan);
+	if (!info)
+		return;
+	info->op_class = buf[0];
+	info->pref = buf[len - 3];
+	info->reason_code = buf[len - 2];
+	info->reason_detail = buf[len - 1];
+	info->num_channels = num_chan;
+	buf++;
+	os_memcpy(info->channels, buf, num_chan);
+	if (!sta->non_pref_chan) {
+		sta->non_pref_chan = info;
+	} else {
+		tmp = sta->non_pref_chan;
+		while (tmp->next)
+			tmp = tmp->next;
+		tmp->next = info;
+	}
+
+	pos = channels;
+	end = pos + sizeof(channels);
+	*pos = '\0';
+	for (i = 0; i < num_chan; i++) {
+		ret = os_snprintf(pos, end - pos, "%s%u",
+				  i == 0 ? "" : " ", buf[i]);
+		if (os_snprintf_error(end - pos, ret)) {
+			*pos = '\0';
+			break;
+		}
+		pos += ret;
+	}
+
+	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
+		   " non-preferred channel list (op class %u, pref %u, reason code %u, reason detail %u, channels %s)",
+		   MAC2STR(sta->addr), info->op_class, info->pref,
+		   info->reason_code, info->reason_detail, channels);
+}
+
+
+void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
+			    struct ieee802_11_elems *elems)
+{
+	const u8 *pos, *attr, *end;
+	size_t len;
+
+	if (!hapd->conf->mbo_enabled || !elems->mbo)
+		return;
+
+	pos = elems->mbo + 4;
+	len = elems->mbo_len - 4;
+	wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len);
+
+	attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA);
+	if (attr && attr[1] >= 1)
+		sta->cell_capa = attr[2];
+
+	mbo_ap_sta_free(sta);
+	end = pos + len;
+	while (end - pos > 1) {
+		u8 ie_len = pos[1];
+
+		if (2 + ie_len > end - pos)
+			break;
+
+		if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT)
+			mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len);
+		pos += 2 + pos[1];
+	}
+}
+
+
+int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen)
+{
+	char *pos = buf, *end = buf + buflen;
+	int ret;
+	struct mbo_non_pref_chan_info *info;
+	u8 i;
+	unsigned int count = 0;
+
+	if (!sta->cell_capa)
+		return 0;
+
+	ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa);
+	if (os_snprintf_error(end - pos, ret))
+		return pos - buf;
+	pos += ret;
+
+	for (info = sta->non_pref_chan; info; info = info->next) {
+		char *pos2 = pos;
+
+		ret = os_snprintf(pos2, end - pos2,
+				  "non_pref_chan[%u]=%u:%u:%u:%u:",
+				  count, info->op_class, info->pref,
+				  info->reason_code, info->reason_detail);
+		count++;
+		if (os_snprintf_error(end - pos2, ret))
+			break;
+		pos2 += ret;
+
+		for (i = 0; i < info->num_channels; i++) {
+			ret = os_snprintf(pos2, end - pos2, "%u%s",
+					  info->channels[i],
+					  i + 1 < info->num_channels ?
+					  "," : "");
+			if (os_snprintf_error(end - pos2, ret)) {
+				pos2 = NULL;
+				break;
+			}
+			pos2 += ret;
+		}
+
+		if (!pos2)
+			break;
+		ret = os_snprintf(pos2, end - pos2, "\n");
+		if (os_snprintf_error(end - pos2, ret))
+			break;
+		pos2 += ret;
+		pos = pos2;
+	}
+
+	return pos - buf;
+}
+
+
+static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta,
+					   const u8 *buf, size_t len)
+{
+	if (len < 1)
+		return;
+	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
+		   " updated cellular data capability: %u",
+		   MAC2STR(sta->addr), buf[0]);
+	sta->cell_capa = buf[0];
+}
+
+
+static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type,
+				      const u8 *buf, size_t len,
+				      int *first_non_pref_chan)
+{
+	switch (type) {
+	case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT:
+		if (*first_non_pref_chan) {
+			/*
+			 * Need to free the previously stored entries now to
+			 * allow the update to replace all entries.
+			 */
+			*first_non_pref_chan = 0;
+			mbo_ap_sta_free(sta);
+		}
+		mbo_ap_parse_non_pref_chan(sta, buf, len);
+		break;
+	case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA:
+		mbo_ap_wnm_notif_req_cell_capa(sta, buf, len);
+		break;
+	default:
+		wpa_printf(MSG_DEBUG,
+			   "MBO: Ignore unknown WNM Notification WFA subelement %u",
+			   type);
+		break;
+	}
+}
+
+
+void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
+				 const u8 *buf, size_t len)
+{
+	const u8 *pos, *end;
+	u8 ie_len;
+	struct sta_info *sta;
+	int first_non_pref_chan = 1;
+
+	if (!hapd->conf->mbo_enabled)
+		return;
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta)
+		return;
+
+	pos = buf;
+	end = buf + len;
+
+	while (end - pos > 1) {
+		ie_len = pos[1];
+
+		if (2 + ie_len > end - pos)
+			break;
+
+		if (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
+		    ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA)
+			mbo_ap_wnm_notif_req_elem(sta, pos[5],
+						  pos + 6, ie_len - 4,
+						  &first_non_pref_chan);
+		else
+			wpa_printf(MSG_DEBUG,
+				   "MBO: Ignore unknown WNM Notification element %u (len=%u)",
+				   pos[0], pos[1]);
+
+		pos += 2 + pos[1];
+	}
+}
diff --git a/src/ap/mbo_ap.h b/src/ap/mbo_ap.h
new file mode 100644
index 0000000..9f37f28
--- /dev/null
+++ b/src/ap/mbo_ap.h
@@ -0,0 +1,51 @@
+/*
+ * MBO related functions and structures
+ * Copyright (c) 2016, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef MBO_AP_H
+#define MBO_AP_H
+
+struct hostapd_data;
+struct sta_info;
+struct ieee802_11_elems;
+
+#ifdef CONFIG_MBO
+
+void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
+			    struct ieee802_11_elems *elems);
+int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen);
+void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
+				 const u8 *buf, size_t len);
+void mbo_ap_sta_free(struct sta_info *sta);
+
+#else /* CONFIG_MBO */
+
+static inline void mbo_ap_check_sta_assoc(struct hostapd_data *hapd,
+					  struct sta_info *sta,
+					  struct ieee802_11_elems *elems)
+{
+}
+
+static inline int mbo_ap_get_info(struct sta_info *sta, char *buf,
+				  size_t buflen)
+{
+	return 0;
+}
+
+static inline void mbo_ap_wnm_notification_req(struct hostapd_data *hapd,
+					       const u8 *addr,
+					       const u8 *buf, size_t len)
+{
+}
+
+static inline void mbo_ap_sta_free(struct sta_info *sta)
+{
+}
+
+#endif /* CONFIG_MBO */
+
+#endif /* MBO_AP_H */
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index eb37c78..30be30c 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -38,6 +38,7 @@
 
 static void _pmksa_cache_free_entry(struct rsn_pmksa_cache_entry *entry)
 {
+	os_free(entry->vlan_desc);
 	os_free(entry->identity);
 	wpabuf_free(entry->cui);
 #ifndef CONFIG_NO_RADIUS
@@ -126,6 +127,8 @@
 static void pmksa_cache_from_eapol_data(struct rsn_pmksa_cache_entry *entry,
 					struct eapol_state_machine *eapol)
 {
+	struct vlan_description *vlan_desc;
+
 	if (eapol == NULL)
 		return;
 
@@ -146,15 +149,26 @@
 #endif /* CONFIG_NO_RADIUS */
 
 	entry->eap_type_authsrv = eapol->eap_type_authsrv;
-	entry->vlan_id = ((struct sta_info *) eapol->sta)->vlan_id;
+
+	vlan_desc = ((struct sta_info *) eapol->sta)->vlan_desc;
+	if (vlan_desc && vlan_desc->notempty) {
+		entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
+		if (entry->vlan_desc)
+			*entry->vlan_desc = *vlan_desc;
+	} else {
+		entry->vlan_desc = NULL;
+	}
 
 	entry->acct_multi_session_id = eapol->acct_multi_session_id;
 }
 
 
-void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry,
+void pmksa_cache_to_eapol_data(struct hostapd_data *hapd,
+			       struct rsn_pmksa_cache_entry *entry,
 			       struct eapol_state_machine *eapol)
 {
+	struct sta_info *sta;
+
 	if (entry == NULL || eapol == NULL)
 		return;
 
@@ -185,7 +199,8 @@
 	}
 
 	eapol->eap_type_authsrv = entry->eap_type_authsrv;
-	((struct sta_info *) eapol->sta)->vlan_id = entry->vlan_id;
+	sta = (struct sta_info *) eapol->sta;
+	ap_sta_set_vlan(hapd, sta, entry->vlan_desc);
 
 	eapol->acct_multi_session_id = entry->acct_multi_session_id;
 }
@@ -232,6 +247,7 @@
  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_auth_init()
  * @pmk: The new pairwise master key
  * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
+ * @pmkid: Calculated PMKID
  * @kck: Key confirmation key or %NULL if not yet derived
  * @kck_len: KCK length in bytes
  * @aa: Authenticator address
@@ -248,7 +264,7 @@
  */
 struct rsn_pmksa_cache_entry *
 pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
-		     const u8 *pmk, size_t pmk_len,
+		     const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 		     const u8 *kck, size_t kck_len,
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp)
@@ -267,7 +283,9 @@
 		return NULL;
 	os_memcpy(entry->pmk, pmk, pmk_len);
 	entry->pmk_len = pmk_len;
-	if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
+	if (pmkid)
+		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
+	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
 		rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid);
 	else if (wpa_key_mgmt_suite_b(akmp))
 		rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
@@ -335,7 +353,13 @@
 	radius_copy_class(&entry->radius_class, &old_entry->radius_class);
 #endif /* CONFIG_NO_RADIUS */
 	entry->eap_type_authsrv = old_entry->eap_type_authsrv;
-	entry->vlan_id = old_entry->vlan_id;
+	if (old_entry->vlan_desc) {
+		entry->vlan_desc = os_zalloc(sizeof(struct vlan_description));
+		if (entry->vlan_desc)
+			*entry->vlan_desc = *old_entry->vlan_desc;
+	} else {
+		entry->vlan_desc = NULL;
+	}
 	entry->opportunistic = 1;
 
 	pmksa_cache_link_entry(pmksa, entry);
@@ -471,8 +495,8 @@
 
 		if (attr->acct_multi_session_id_len != 16)
 			return 0;
-		os_snprintf(buf, sizeof(buf), "%016lX",
-			    (long unsigned int) entry->acct_multi_session_id);
+		os_snprintf(buf, sizeof(buf), "%016llX",
+			    (unsigned long long) entry->acct_multi_session_id);
 		if (os_memcmp(attr->acct_multi_session_id, buf, 16) != 0)
 			return 0;
 		match++;
diff --git a/src/ap/pmksa_cache_auth.h b/src/ap/pmksa_cache_auth.h
index 4dc841f..3e60277 100644
--- a/src/ap/pmksa_cache_auth.h
+++ b/src/ap/pmksa_cache_auth.h
@@ -28,7 +28,7 @@
 	struct wpabuf *cui;
 	struct radius_class_data radius_class;
 	u8 eap_type_authsrv;
-	int vlan_id;
+	struct vlan_description *vlan_desc;
 	int opportunistic;
 
 	u64 acct_multi_session_id;
@@ -48,7 +48,7 @@
 	const u8 *pmkid);
 struct rsn_pmksa_cache_entry *
 pmksa_cache_auth_add(struct rsn_pmksa_cache *pmksa,
-		     const u8 *pmk, size_t pmk_len,
+		     const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 		     const u8 *kck, size_t kck_len,
 		     const u8 *aa, const u8 *spa, int session_timeout,
 		     struct eapol_state_machine *eapol, int akmp);
@@ -56,7 +56,8 @@
 pmksa_cache_add_okc(struct rsn_pmksa_cache *pmksa,
 		    const struct rsn_pmksa_cache_entry *old_entry,
 		    const u8 *aa, const u8 *pmkid);
-void pmksa_cache_to_eapol_data(struct rsn_pmksa_cache_entry *entry,
+void pmksa_cache_to_eapol_data(struct hostapd_data *hapd,
+			       struct rsn_pmksa_cache_entry *entry,
 			       struct eapol_state_machine *eapol);
 void pmksa_cache_free_entry(struct rsn_pmksa_cache *pmksa,
 			    struct rsn_pmksa_cache_entry *entry);
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 3d7c839..60058e4 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -32,8 +32,10 @@
 #include "ap_drv_ops.h"
 #include "gas_serv.h"
 #include "wnm_ap.h"
+#include "mbo_ap.h"
 #include "ndisc_snoop.h"
 #include "sta_info.h"
+#include "vlan.h"
 
 static void ap_sta_remove_in_other_bss(struct hostapd_data *hapd,
 				       struct sta_info *sta);
@@ -169,8 +171,10 @@
 	ap_sta_ip6addr_del(hapd, sta);
 
 	if (!hapd->iface->driver_ap_teardown &&
-	    !(sta->flags & WLAN_STA_PREAUTH))
+	    !(sta->flags & WLAN_STA_PREAUTH)) {
 		hostapd_drv_sta_remove(hapd, sta->addr);
+		sta->added_unassoc = 0;
+	}
 
 	ap_sta_hash_del(hapd, sta);
 	ap_sta_list_del(hapd, sta);
@@ -266,14 +270,18 @@
 	 * vlan_remove_dynamic() can check that no stations are left on the
 	 * AP_VLAN netdev.
 	 */
+	if (sta->vlan_id)
+		vlan_remove_dynamic(hapd, sta->vlan_id);
 	if (sta->vlan_id_bound) {
 		/*
 		 * Need to remove the STA entry before potentially removing the
 		 * VLAN.
 		 */
 		if (hapd->iface->driver_ap_teardown &&
-		    !(sta->flags & WLAN_STA_PREAUTH))
+		    !(sta->flags & WLAN_STA_PREAUTH)) {
 			hostapd_drv_sta_remove(hapd, sta->addr);
+			sta->added_unassoc = 0;
+		}
 		vlan_remove_dynamic(hapd, sta->vlan_id_bound);
 	}
 #endif /* CONFIG_NO_VLAN */
@@ -319,6 +327,8 @@
 	os_free(sta->sae);
 #endif /* CONFIG_SAE */
 
+	mbo_ap_sta_free(sta);
+
 	os_free(sta);
 }
 
@@ -670,6 +680,7 @@
 			   hapd->conf->iface, MAC2STR(sta->addr));
 		return -1;
 	}
+	sta->added_unassoc = 0;
 	return 0;
 }
 
@@ -802,6 +813,128 @@
 #endif /* CONFIG_WPS */
 
 
+static int ap_sta_get_free_vlan_id(struct hostapd_data *hapd)
+{
+	struct hostapd_vlan *vlan;
+	int vlan_id = MAX_VLAN_ID + 2;
+
+retry:
+	for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+		if (vlan->vlan_id == vlan_id) {
+			vlan_id++;
+			goto retry;
+		}
+	}
+	return vlan_id;
+}
+
+
+int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
+		    struct vlan_description *vlan_desc)
+{
+	struct hostapd_vlan *vlan = NULL, *wildcard_vlan = NULL;
+	int old_vlan_id, vlan_id = 0, ret = 0;
+
+	if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
+		vlan_desc = NULL;
+
+	/* Check if there is something to do */
+	if (hapd->conf->ssid.per_sta_vif && !sta->vlan_id) {
+		/* This sta is lacking its own vif */
+	} else if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED &&
+		   !hapd->conf->ssid.per_sta_vif && sta->vlan_id) {
+		/* sta->vlan_id needs to be reset */
+	} else if (!vlan_compare(vlan_desc, sta->vlan_desc)) {
+		return 0; /* nothing to change */
+	}
+
+	/* Now the real VLAN changed or the STA just needs its own vif */
+	if (hapd->conf->ssid.per_sta_vif) {
+		/* Assign a new vif, always */
+		/* find a free vlan_id sufficiently big */
+		vlan_id = ap_sta_get_free_vlan_id(hapd);
+		/* Get wildcard VLAN */
+		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+			if (vlan->vlan_id == VLAN_ID_WILDCARD)
+				break;
+		}
+		if (!vlan) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_DEBUG,
+				       "per_sta_vif missing wildcard");
+			vlan_id = 0;
+			ret = -1;
+			goto done;
+		}
+	} else if (vlan_desc && vlan_desc->notempty) {
+		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+			if (!vlan_compare(&vlan->vlan_desc, vlan_desc))
+				break;
+			if (vlan->vlan_id == VLAN_ID_WILDCARD)
+				wildcard_vlan = vlan;
+		}
+		if (vlan) {
+			vlan_id = vlan->vlan_id;
+		} else if (wildcard_vlan) {
+			vlan = wildcard_vlan;
+			vlan_id = vlan_desc->untagged;
+			if (vlan_desc->tagged[0]) {
+				/* Tagged VLAN configuration */
+				vlan_id = ap_sta_get_free_vlan_id(hapd);
+			}
+		} else {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_DEBUG,
+				       "missing vlan and wildcard for vlan=%d%s",
+				       vlan_desc->untagged,
+				       vlan_desc->tagged[0] ? "+" : "");
+			vlan_id = 0;
+			ret = -1;
+			goto done;
+		}
+	}
+
+	if (vlan && vlan->vlan_id == VLAN_ID_WILDCARD) {
+		vlan = vlan_add_dynamic(hapd, vlan, vlan_id, vlan_desc);
+		if (vlan == NULL) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_DEBUG,
+				       "could not add dynamic VLAN interface for vlan=%d%s",
+				       vlan_desc ? vlan_desc->untagged : -1,
+				       (vlan_desc && vlan_desc->tagged[0]) ?
+				       "+" : "");
+			vlan_id = 0;
+			ret = -1;
+			goto done;
+		}
+
+		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "added new dynamic VLAN interface '%s'",
+			       vlan->ifname);
+	} else if (vlan && vlan->dynamic_vlan > 0) {
+		vlan->dynamic_vlan++;
+		hostapd_logger(hapd, sta->addr,
+			       HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "updated existing dynamic VLAN interface '%s'",
+			       vlan->ifname);
+	}
+done:
+	old_vlan_id = sta->vlan_id;
+	sta->vlan_id = vlan_id;
+	sta->vlan_desc = vlan ? &vlan->vlan_desc : NULL;
+
+	if (vlan_id != old_vlan_id && old_vlan_id)
+		vlan_remove_dynamic(hapd, old_vlan_id);
+
+	return ret;
+}
+
+
 int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta)
 {
 #ifndef CONFIG_NO_VLAN
@@ -814,20 +947,11 @@
 	if (hapd->conf->ssid.vlan[0])
 		iface = hapd->conf->ssid.vlan;
 
-	if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
-		sta->vlan_id = 0;
-	else if (sta->vlan_id > 0) {
-		struct hostapd_vlan *wildcard_vlan = NULL;
-		vlan = hapd->conf->vlan;
-		while (vlan) {
+	if (sta->vlan_id > 0) {
+		for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
 			if (vlan->vlan_id == sta->vlan_id)
 				break;
-			if (vlan->vlan_id == VLAN_ID_WILDCARD)
-				wildcard_vlan = vlan;
-			vlan = vlan->next;
 		}
-		if (!vlan)
-			vlan = wildcard_vlan;
 		if (vlan)
 			iface = vlan->ifname;
 	}
@@ -847,24 +971,7 @@
 			       sta->vlan_id);
 		ret = -1;
 		goto done;
-	} else if (sta->vlan_id > 0 && vlan->vlan_id == VLAN_ID_WILDCARD) {
-		vlan = vlan_add_dynamic(hapd, vlan, sta->vlan_id);
-		if (vlan == NULL) {
-			hostapd_logger(hapd, sta->addr,
-				       HOSTAPD_MODULE_IEEE80211,
-				       HOSTAPD_LEVEL_DEBUG, "could not add "
-				       "dynamic VLAN interface for vlan_id=%d",
-				       sta->vlan_id);
-			ret = -1;
-			goto done;
-		}
-
-		iface = vlan->ifname;
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_DEBUG, "added new dynamic VLAN "
-			       "interface '%s'", iface);
-	} else if (vlan && vlan->vlan_id == sta->vlan_id &&
-		   vlan->dynamic_vlan > 0) {
+	} else if (vlan && vlan->dynamic_vlan > 0) {
 		vlan->dynamic_vlan++;
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211,
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 359f480..08e795e 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -15,6 +15,7 @@
 #endif /* CONFIG_MESH */
 
 #include "list.h"
+#include "vlan.h"
 
 /* STA flags */
 #define WLAN_STA_AUTH BIT(0)
@@ -45,6 +46,16 @@
 #define WLAN_SUPP_RATES_MAX 32
 
 
+struct mbo_non_pref_chan_info {
+	struct mbo_non_pref_chan_info *next;
+	u8 op_class;
+	u8 pref;
+	u8 reason_code;
+	u8 reason_detail;
+	u8 num_channels;
+	u8 channels[];
+};
+
 struct sta_info {
 	struct sta_info *next; /* next entry in sta list */
 	struct sta_info *hnext; /* next entry in hash table list */
@@ -87,6 +98,7 @@
 	unsigned int session_timeout_set:1;
 	unsigned int radius_das_match:1;
 	unsigned int ecsa_supported:1;
+	unsigned int added_unassoc:1;
 
 	u16 auth_alg;
 
@@ -107,10 +119,11 @@
 	int acct_terminate_cause; /* Acct-Terminate-Cause */
 	int acct_interim_interval; /* Acct-Interim-Interval */
 
-	unsigned long last_rx_bytes;
-	unsigned long last_tx_bytes;
-	u32 acct_input_gigawords; /* Acct-Input-Gigawords */
-	u32 acct_output_gigawords; /* Acct-Output-Gigawords */
+	/* For extending 32-bit driver counters to 64-bit counters */
+	u32 last_rx_bytes_hi;
+	u32 last_rx_bytes_lo;
+	u32 last_tx_bytes_hi;
+	u32 last_tx_bytes_lo;
 
 	u8 *challenge; /* IEEE 802.11 Shared Key Authentication Challenge */
 
@@ -118,6 +131,7 @@
 	struct rsn_preauth_interface *preauth_iface;
 
 	int vlan_id; /* 0: none, >0: VID */
+	struct vlan_description *vlan_desc;
 	int vlan_id_bound; /* updated by ap_sta_bind_vlan() */
 	 /* PSKs from RADIUS authentication server */
 	struct hostapd_sta_wpa_psk_short *psk;
@@ -170,6 +184,12 @@
 	u16 last_seq_ctrl;
 	/* Last Authentication/(Re)Association Request/Action frame subtype */
 	u8 last_subtype;
+
+#ifdef CONFIG_MBO
+	u8 cell_capa; /* 0 = unknown (not an MBO STA); otherwise,
+		       * enum mbo_cellular_capa values */
+	struct mbo_non_pref_chan_info *non_pref_chan;
+#endif /* CONFIG_MBO */
 };
 
 
@@ -220,6 +240,8 @@
 		      struct sta_info *sta, void *ctx);
 #endif /* CONFIG_WPS */
 int ap_sta_bind_vlan(struct hostapd_data *hapd, struct sta_info *sta);
+int ap_sta_set_vlan(struct hostapd_data *hapd, struct sta_info *sta,
+		    struct vlan_description *vlan_desc);
 void ap_sta_start_sa_query(struct hostapd_data *hapd, struct sta_info *sta);
 void ap_sta_stop_sa_query(struct hostapd_data *hapd, struct sta_info *sta);
 int ap_check_sa_query_timeout(struct hostapd_data *hapd, struct sta_info *sta);
diff --git a/src/ap/vlan.c b/src/ap/vlan.c
new file mode 100644
index 0000000..b6f6bb1
--- /dev/null
+++ b/src/ap/vlan.c
@@ -0,0 +1,34 @@
+/*
+ * hostapd / VLAN definition
+ * Copyright (c) 2016, Jouni Malinen <j@w1.fi>
+ *
+ * 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 "ap/vlan.h"
+
+/* compare the two arguments, NULL is treated as empty
+ * return zero iff they are equal
+ */
+int vlan_compare(struct vlan_description *a, struct vlan_description *b)
+{
+	int i;
+	const int a_empty = !a || !a->notempty;
+	const int b_empty = !b || !b->notempty;
+
+	if (a_empty && b_empty)
+		return 0;
+	if (a_empty || b_empty)
+		return 1;
+	if (a->untagged != b->untagged)
+		return 1;
+	for (i = 0; i < MAX_NUM_TAGGED_VLAN; i++) {
+		if (a->tagged[i] != b->tagged[i])
+			return 1;
+	}
+	return 0;
+}
diff --git a/src/ap/vlan.h b/src/ap/vlan.h
new file mode 100644
index 0000000..af84929
--- /dev/null
+++ b/src/ap/vlan.h
@@ -0,0 +1,30 @@
+/*
+ * hostapd / VLAN definition
+ * Copyright (c) 2015, Jouni Malinen <j@w1.fi>
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef VLAN_H
+#define VLAN_H
+
+#define MAX_NUM_TAGGED_VLAN 32
+
+struct vlan_description {
+	int notempty; /* 0 : no vlan information present, 1: else */
+	int untagged; /* >0 802.1q vid */
+	int tagged[MAX_NUM_TAGGED_VLAN]; /* first k items, ascending order */
+};
+
+#ifndef CONFIG_NO_VLAN
+int vlan_compare(struct vlan_description *a, struct vlan_description *b);
+#else /* CONFIG_NO_VLAN */
+static inline int
+vlan_compare(struct vlan_description *a, struct vlan_description *b)
+{
+	return 0;
+}
+#endif /* CONFIG_NO_VLAN */
+
+#endif /* VLAN_H */
diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c
index e3df164..8eab6cb 100644
--- a/src/ap/vlan_init.c
+++ b/src/ap/vlan_init.c
@@ -9,13 +9,6 @@
  */
 
 #include "utils/includes.h"
-#include <net/if.h>
-#include <sys/ioctl.h>
-#ifdef CONFIG_FULL_DYNAMIC_VLAN
-#include <linux/sockios.h>
-#include <linux/if_vlan.h>
-#include <linux/if_bridge.h>
-#endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
 #include "utils/common.h"
 #include "hostapd.h"
@@ -25,6 +18,14 @@
 #include "vlan_init.h"
 #include "vlan_util.h"
 
+#include <net/if.h>
+#include <sys/ioctl.h>
+#ifdef CONFIG_FULL_DYNAMIC_VLAN
+#include <linux/sockios.h>
+#include <linux/if_vlan.h>
+#include <linux/if_bridge.h>
+#endif /* CONFIG_FULL_DYNAMIC_VLAN */
+
 
 #ifdef CONFIG_FULL_DYNAMIC_VLAN
 
@@ -623,153 +624,228 @@
 #endif /* CONFIG_VLAN_NETLINK */
 
 
-static void vlan_newlink(char *ifname, struct hostapd_data *hapd)
+static void vlan_newlink_tagged(int vlan_naming, const char *tagged_interface,
+				const char *br_name, int vid,
+				struct hostapd_data *hapd)
 {
 	char vlan_ifname[IFNAMSIZ];
-	char br_name[IFNAMSIZ];
-	struct hostapd_vlan *vlan = hapd->conf->vlan;
-	char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
-	int vlan_naming = hapd->conf->ssid.vlan_naming;
 	int clean;
 
-	wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname);
+	if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE)
+		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
+			    tagged_interface, vid);
+	else
+		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", vid);
 
-	while (vlan) {
-		if (os_strcmp(ifname, vlan->ifname) == 0 && !vlan->configured) {
-			vlan->configured = 1;
+	clean = 0;
+	ifconfig_up(tagged_interface);
+	if (!vlan_add(tagged_interface, vid, vlan_ifname))
+		clean |= DVLAN_CLEAN_VLAN;
 
-			if (hapd->conf->vlan_bridge[0]) {
-				os_snprintf(br_name, sizeof(br_name), "%s%d",
-					    hapd->conf->vlan_bridge,
-					    vlan->vlan_id);
-			} else if (tagged_interface) {
-				os_snprintf(br_name, sizeof(br_name),
-				            "br%s.%d", tagged_interface,
-					    vlan->vlan_id);
-			} else {
-				os_snprintf(br_name, sizeof(br_name),
-				            "brvlan%d", vlan->vlan_id);
-			}
+	if (!br_addif(br_name, vlan_ifname))
+		clean |= DVLAN_CLEAN_VLAN_PORT;
 
-			dyn_iface_get(hapd, br_name,
-				      br_addbr(br_name) ? 0 : DVLAN_CLEAN_BR);
+	dyn_iface_get(hapd, vlan_ifname, clean);
 
-			ifconfig_up(br_name);
+	ifconfig_up(vlan_ifname);
+}
 
-			if (tagged_interface) {
-				if (vlan_naming ==
-				    DYNAMIC_VLAN_NAMING_WITH_DEVICE)
-					os_snprintf(vlan_ifname,
-						    sizeof(vlan_ifname),
-						    "%s.%d", tagged_interface,
-						    vlan->vlan_id);
-				else
-					os_snprintf(vlan_ifname,
-						    sizeof(vlan_ifname),
-						    "vlan%d", vlan->vlan_id);
 
-				clean = 0;
-				ifconfig_up(tagged_interface);
-				if (!vlan_add(tagged_interface, vlan->vlan_id,
-					      vlan_ifname))
-					clean |= DVLAN_CLEAN_VLAN;
+static void vlan_bridge_name(char *br_name, struct hostapd_data *hapd, int vid)
+{
+	char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
 
-				if (!br_addif(br_name, vlan_ifname))
-					clean |= DVLAN_CLEAN_VLAN_PORT;
-
-				dyn_iface_get(hapd, vlan_ifname, clean);
-
-				ifconfig_up(vlan_ifname);
-			}
-
-			if (!br_addif(br_name, ifname))
-				vlan->clean |= DVLAN_CLEAN_WLAN_PORT;
-
-			ifconfig_up(ifname);
-
-			break;
-		}
-		vlan = vlan->next;
+	if (hapd->conf->vlan_bridge[0]) {
+		os_snprintf(br_name, IFNAMSIZ, "%s%d",
+			    hapd->conf->vlan_bridge, vid);
+	} else if (tagged_interface) {
+		os_snprintf(br_name, IFNAMSIZ, "br%s.%d",
+			    tagged_interface, vid);
+	} else {
+		os_snprintf(br_name, IFNAMSIZ, "brvlan%d", vid);
 	}
 }
 
 
-static void vlan_dellink(char *ifname, struct hostapd_data *hapd)
+static void vlan_get_bridge(const char *br_name, struct hostapd_data *hapd,
+			    int vid)
 {
-	char vlan_ifname[IFNAMSIZ];
-	char br_name[IFNAMSIZ];
-	struct hostapd_vlan *first, *prev, *vlan = hapd->conf->vlan;
 	char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
 	int vlan_naming = hapd->conf->ssid.vlan_naming;
+
+	dyn_iface_get(hapd, br_name, br_addbr(br_name) ? 0 : DVLAN_CLEAN_BR);
+
+	ifconfig_up(br_name);
+
+	if (tagged_interface)
+		vlan_newlink_tagged(vlan_naming, tagged_interface, br_name,
+				    vid, hapd);
+}
+
+
+static void vlan_newlink(const char *ifname, struct hostapd_data *hapd)
+{
+	char br_name[IFNAMSIZ];
+	struct hostapd_vlan *vlan;
+	int untagged, *tagged, i, notempty;
+
+	wpa_printf(MSG_DEBUG, "VLAN: vlan_newlink(%s)", ifname);
+
+	for (vlan = hapd->conf->vlan; vlan; vlan = vlan->next) {
+		if (vlan->configured ||
+		    os_strcmp(ifname, vlan->ifname) != 0)
+			continue;
+		break;
+	}
+	if (!vlan)
+		return;
+
+	vlan->configured = 1;
+
+	notempty = vlan->vlan_desc.notempty;
+	untagged = vlan->vlan_desc.untagged;
+	tagged = vlan->vlan_desc.tagged;
+
+	if (!notempty) {
+		/* Non-VLAN STA */
+		if (hapd->conf->bridge[0] &&
+		    !br_addif(hapd->conf->bridge, ifname))
+			vlan->clean |= DVLAN_CLEAN_WLAN_PORT;
+	} else if (untagged > 0 && untagged <= MAX_VLAN_ID) {
+		vlan_bridge_name(br_name, hapd, untagged);
+
+		vlan_get_bridge(br_name, hapd, untagged);
+
+		if (!br_addif(br_name, ifname))
+			vlan->clean |= DVLAN_CLEAN_WLAN_PORT;
+	}
+
+	for (i = 0; i < MAX_NUM_TAGGED_VLAN && tagged[i]; i++) {
+		if (tagged[i] == untagged ||
+		    tagged[i] <= 0 || tagged[i] > MAX_VLAN_ID ||
+		    (i > 0 && tagged[i] == tagged[i - 1]))
+			continue;
+		vlan_bridge_name(br_name, hapd, tagged[i]);
+		vlan_get_bridge(br_name, hapd, tagged[i]);
+		vlan_newlink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE,
+				    ifname, br_name, tagged[i], hapd);
+	}
+
+	ifconfig_up(ifname);
+}
+
+
+static void vlan_dellink_tagged(int vlan_naming, const char *tagged_interface,
+				const char *br_name, int vid,
+				struct hostapd_data *hapd)
+{
+	char vlan_ifname[IFNAMSIZ];
 	int clean;
 
+	if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE)
+		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
+			    tagged_interface, vid);
+	else
+		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", vid);
+
+	clean = dyn_iface_put(hapd, vlan_ifname);
+
+	if (clean & DVLAN_CLEAN_VLAN_PORT)
+		br_delif(br_name, vlan_ifname);
+
+	if (clean & DVLAN_CLEAN_VLAN) {
+		ifconfig_down(vlan_ifname);
+		vlan_rem(vlan_ifname);
+	}
+}
+
+
+static void vlan_put_bridge(const char *br_name, struct hostapd_data *hapd,
+			    int vid)
+{
+	int clean;
+	char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
+	int vlan_naming = hapd->conf->ssid.vlan_naming;
+
+	if (tagged_interface)
+		vlan_dellink_tagged(vlan_naming, tagged_interface, br_name,
+				    vid, hapd);
+
+	clean = dyn_iface_put(hapd, br_name);
+	if ((clean & DVLAN_CLEAN_BR) && br_getnumports(br_name) == 0) {
+		ifconfig_down(br_name);
+		br_delbr(br_name);
+	}
+}
+
+
+static void vlan_dellink(const char *ifname, struct hostapd_data *hapd)
+{
+	struct hostapd_vlan *first, *prev, *vlan = hapd->conf->vlan;
+
 	wpa_printf(MSG_DEBUG, "VLAN: vlan_dellink(%s)", ifname);
 
 	first = prev = vlan;
 
 	while (vlan) {
-		if (os_strcmp(ifname, vlan->ifname) == 0 &&
-		    vlan->configured) {
-			if (hapd->conf->vlan_bridge[0]) {
-				os_snprintf(br_name, sizeof(br_name), "%s%d",
-					    hapd->conf->vlan_bridge,
-					    vlan->vlan_id);
-			} else if (tagged_interface) {
-				os_snprintf(br_name, sizeof(br_name),
-				            "br%s.%d", tagged_interface,
-					    vlan->vlan_id);
-			} else {
-				os_snprintf(br_name, sizeof(br_name),
-				            "brvlan%d", vlan->vlan_id);
-			}
+		if (os_strcmp(ifname, vlan->ifname) != 0) {
+			prev = vlan;
+			vlan = vlan->next;
+			continue;
+		}
+		break;
+	}
+	if (!vlan)
+		return;
+
+	if (vlan->configured) {
+		int notempty = vlan->vlan_desc.notempty;
+		int untagged = vlan->vlan_desc.untagged;
+		int *tagged = vlan->vlan_desc.tagged;
+		char br_name[IFNAMSIZ];
+		int i;
+
+		for (i = 0; i < MAX_NUM_TAGGED_VLAN && tagged[i]; i++) {
+			if (tagged[i] == untagged ||
+			    tagged[i] <= 0 || tagged[i] > MAX_VLAN_ID ||
+			    (i > 0 && tagged[i] == tagged[i - 1]))
+				continue;
+			vlan_bridge_name(br_name, hapd, tagged[i]);
+			vlan_dellink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE,
+					    ifname, br_name, tagged[i], hapd);
+			vlan_put_bridge(br_name, hapd, tagged[i]);
+		}
+
+		if (!notempty) {
+			/* Non-VLAN STA */
+			if (hapd->conf->bridge[0] &&
+			    (vlan->clean & DVLAN_CLEAN_WLAN_PORT))
+				br_delif(hapd->conf->bridge, ifname);
+		} else if (untagged > 0 && untagged <= MAX_VLAN_ID) {
+			vlan_bridge_name(br_name, hapd, untagged);
 
 			if (vlan->clean & DVLAN_CLEAN_WLAN_PORT)
 				br_delif(br_name, vlan->ifname);
 
-			if (tagged_interface) {
-				if (vlan_naming ==
-				    DYNAMIC_VLAN_NAMING_WITH_DEVICE)
-					os_snprintf(vlan_ifname,
-						    sizeof(vlan_ifname),
-						    "%s.%d", tagged_interface,
-						    vlan->vlan_id);
-				else
-					os_snprintf(vlan_ifname,
-						    sizeof(vlan_ifname),
-						    "vlan%d", vlan->vlan_id);
-
-				clean = dyn_iface_put(hapd, vlan_ifname);
-
-				if (clean & DVLAN_CLEAN_VLAN_PORT)
-					br_delif(br_name, vlan_ifname);
-
-				if (clean & DVLAN_CLEAN_VLAN) {
-					ifconfig_down(vlan_ifname);
-					vlan_rem(vlan_ifname);
-				}
-			}
-
-			clean = dyn_iface_put(hapd, br_name);
-			if ((clean & DVLAN_CLEAN_BR) &&
-			    br_getnumports(br_name) == 0) {
-				ifconfig_down(br_name);
-				br_delbr(br_name);
-			}
+			vlan_put_bridge(br_name, hapd, untagged);
 		}
-
-		if (os_strcmp(ifname, vlan->ifname) == 0) {
-			if (vlan == first) {
-				hapd->conf->vlan = vlan->next;
-			} else {
-				prev->next = vlan->next;
-			}
-			os_free(vlan);
-
-			break;
-		}
-		prev = vlan;
-		vlan = vlan->next;
 	}
+
+	/*
+	 * Ensure this VLAN interface is actually removed even if
+	 * NEWLINK message is only received later.
+	 */
+	if (if_nametoindex(vlan->ifname) && vlan_if_remove(hapd, vlan))
+		wpa_printf(MSG_ERROR,
+			   "VLAN: Could not remove VLAN iface: %s: %s",
+			   vlan->ifname, strerror(errno));
+
+	if (vlan == first)
+		hapd->conf->vlan = vlan->next;
+	else
+		prev->next = vlan->next;
+
+	os_free(vlan);
 }
 
 
@@ -977,15 +1053,17 @@
 	while (vlan) {
 		next = vlan->next;
 
+#ifdef CONFIG_FULL_DYNAMIC_VLAN
+		/* vlan_dellink() takes care of cleanup and interface removal */
+		if (vlan->vlan_id != VLAN_ID_WILDCARD)
+			vlan_dellink(vlan->ifname, hapd);
+#else /* CONFIG_FULL_DYNAMIC_VLAN */
 		if (vlan->vlan_id != VLAN_ID_WILDCARD &&
 		    vlan_if_remove(hapd, vlan)) {
 			wpa_printf(MSG_ERROR, "VLAN: Could not remove VLAN "
 				   "iface: %s: %s",
 				   vlan->ifname, strerror(errno));
 		}
-#ifdef CONFIG_FULL_DYNAMIC_VLAN
-		if (vlan->clean)
-			vlan_dellink(vlan->ifname, hapd);
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
 		vlan = next;
@@ -999,7 +1077,8 @@
 	hapd->full_dynamic_vlan = full_dynamic_vlan_init(hapd);
 #endif /* CONFIG_FULL_DYNAMIC_VLAN */
 
-	if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED &&
+	if ((hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED ||
+	     hapd->conf->ssid.per_sta_vif) &&
 	    !hapd->conf->vlan) {
 		/* dynamic vlans enabled but no (or empty) vlan_file given */
 		struct hostapd_vlan *vlan;
@@ -1037,13 +1116,13 @@
 
 struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
 				       struct hostapd_vlan *vlan,
-				       int vlan_id)
+				       int vlan_id,
+				       struct vlan_description *vlan_desc)
 {
 	struct hostapd_vlan *n = NULL;
 	char *ifname, *pos;
 
-	if (vlan == NULL || vlan_id <= 0 || vlan_id > MAX_VLAN_ID ||
-	    vlan->vlan_id != VLAN_ID_WILDCARD)
+	if (vlan == NULL || vlan->vlan_id != VLAN_ID_WILDCARD)
 		return NULL;
 
 	wpa_printf(MSG_DEBUG, "VLAN: %s(vlan_id=%d ifname=%s)",
@@ -1061,6 +1140,8 @@
 		goto free_ifname;
 
 	n->vlan_id = vlan_id;
+	if (vlan_desc)
+		n->vlan_desc = *vlan_desc;
 	n->dynamic_vlan = 1;
 
 	os_snprintf(n->ifname, sizeof(n->ifname), "%s%d%s", ifname, vlan_id,
@@ -1087,7 +1168,7 @@
 {
 	struct hostapd_vlan *vlan;
 
-	if (vlan_id <= 0 || vlan_id > MAX_VLAN_ID)
+	if (vlan_id <= 0)
 		return 1;
 
 	wpa_printf(MSG_DEBUG, "VLAN: %s(ifname=%s vlan_id=%d)",
diff --git a/src/ap/vlan_init.h b/src/ap/vlan_init.h
index aeb2dc6..d17c82c 100644
--- a/src/ap/vlan_init.h
+++ b/src/ap/vlan_init.h
@@ -15,7 +15,8 @@
 void vlan_deinit(struct hostapd_data *hapd);
 struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
 				       struct hostapd_vlan *vlan,
-				       int vlan_id);
+				       int vlan_id,
+				       struct vlan_description *vlan_desc);
 int vlan_remove_dynamic(struct hostapd_data *hapd, int vlan_id);
 #else /* CONFIG_NO_VLAN */
 static inline int vlan_init(struct hostapd_data *hapd)
@@ -27,9 +28,9 @@
 {
 }
 
-static inline struct hostapd_vlan * vlan_add_dynamic(struct hostapd_data *hapd,
-						     struct hostapd_vlan *vlan,
-						     int vlan_id)
+static inline struct hostapd_vlan *
+vlan_add_dynamic(struct hostapd_data *hapd, struct hostapd_vlan *vlan,
+		 int vlan_id, struct vlan_description *vlan_desc)
 {
 	return NULL;
 }
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 4c8bc10..41d50ce 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -17,6 +17,7 @@
 #include "ap/ap_config.h"
 #include "ap/ap_drv_ops.h"
 #include "ap/wpa_auth.h"
+#include "mbo_ap.h"
 #include "wnm_ap.h"
 
 #define MAX_TFS_IE_LEN  1024
@@ -94,6 +95,7 @@
 	if (mgmt == NULL) {
 		wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for "
 			   "WNM-Sleep Response action frame");
+		os_free(wnmtfs_ie);
 		return -1;
 	}
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
@@ -376,6 +378,29 @@
 }
 
 
+static void ieee802_11_rx_wnm_notification_req(struct hostapd_data *hapd,
+					       const u8 *addr, const u8 *buf,
+					       size_t len)
+{
+	u8 dialog_token, type;
+
+	if (len < 2)
+		return;
+	dialog_token = *buf++;
+	type = *buf++;
+	len -= 2;
+
+	wpa_printf(MSG_DEBUG,
+		   "WNM: Received WNM Notification Request frame from "
+		   MACSTR " (dialog_token=%u type=%u)",
+		   MAC2STR(addr), dialog_token, type);
+	wpa_hexdump(MSG_MSGDUMP, "WNM: Notification Request subelements",
+		    buf, len);
+	if (type == WLAN_EID_VENDOR_SPECIFIC)
+		mbo_ap_wnm_notification_req(hapd, addr, buf, len);
+}
+
+
 int ieee802_11_rx_wnm_action_ap(struct hostapd_data *hapd,
 				const struct ieee80211_mgmt *mgmt, size_t len)
 {
@@ -402,6 +427,10 @@
 	case WNM_SLEEP_MODE_REQ:
 		ieee802_11_rx_wnmsleep_req(hapd, mgmt->sa, payload, plen);
 		return 0;
+	case WNM_NOTIFICATION_REQ:
+		ieee802_11_rx_wnm_notification_req(hapd, mgmt->sa, payload,
+						   plen);
+		return 0;
 	}
 
 	wpa_printf(MSG_DEBUG, "WNM: Unsupported WNM Action %u from " MACSTR,
@@ -527,7 +556,8 @@
 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)
+			const u8 *nei_rep, size_t nei_rep_len,
+			const u8 *mbo_attrs, size_t mbo_len)
 {
 	u8 *buf, *pos;
 	struct ieee80211_mgmt *mgmt;
@@ -536,7 +566,7 @@
 	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);
+	buf = os_zalloc(1000 + nei_rep_len + mbo_len);
 	if (buf == NULL)
 		return -1;
 	mgmt = (struct ieee80211_mgmt *) buf;
@@ -579,6 +609,11 @@
 		pos += nei_rep_len;
 	}
 
+	if (mbo_len > 0) {
+		pos += mbo_add_ie(pos, buf + sizeof(buf) - pos, mbo_attrs,
+				  mbo_len);
+	}
+
 	if (hostapd_drv_send_mlme(hapd, buf, pos - buf, 0) < 0) {
 		wpa_printf(MSG_DEBUG,
 			   "Failed to send BSS Transition Management Request frame");
diff --git a/src/ap/wnm_ap.h b/src/ap/wnm_ap.h
index 7789307..a44eadb 100644
--- a/src/ap/wnm_ap.h
+++ b/src/ap/wnm_ap.h
@@ -21,6 +21,7 @@
 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);
+			const u8 *nei_rep, size_t nei_rep_len,
+			const u8 *mbo_attrs, size_t mbo_len);
 
 #endif /* WNM_AP_H */
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 9b2382f..4ded9bb 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -3298,7 +3298,7 @@
 		pmk_len = PMK_LEN;
 	}
 
-	if (pmksa_cache_auth_add(sm->wpa_auth->pmksa, pmk, pmk_len,
+	if (pmksa_cache_auth_add(sm->wpa_auth->pmksa, pmk, pmk_len, NULL,
 				 sm->PTK.kck, sm->PTK.kck_len,
 				 sm->wpa_auth->addr, sm->addr, session_timeout,
 				 eapol, sm->wpa_key_mgmt))
@@ -3316,7 +3316,7 @@
 	if (wpa_auth == NULL)
 		return -1;
 
-	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, len,
+	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, len, NULL,
 				 NULL, 0,
 				 wpa_auth->addr,
 				 sta_addr, session_timeout, eapol,
@@ -3328,12 +3328,12 @@
 
 
 int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			   const u8 *pmk)
+			   const u8 *pmk, const u8 *pmkid)
 {
 	if (wpa_auth->conf.disable_pmksa_caching)
 		return -1;
 
-	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, PMK_LEN,
+	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, PMK_LEN, pmkid,
 				 NULL, 0,
 				 wpa_auth->addr, addr, 0, NULL,
 				 WPA_KEY_MGMT_SAE))
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 75b73f0..b303324 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -286,7 +286,7 @@
 			       int session_timeout,
 			       struct eapol_state_machine *eapol);
 int wpa_auth_pmksa_add_sae(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			   const u8 *pmk);
+			   const u8 *pmk, const u8 *pmkid);
 void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth,
 			   const u8 *sta_addr);
 int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id);
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 52ccac3..f79783b 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -712,11 +712,14 @@
 		}
 	}
 	if (sm->pmksa && pmkid) {
+		struct vlan_description *vlan;
+
+		vlan = sm->pmksa->vlan_desc;
 		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
-				 "PMKID found from PMKSA cache "
-				 "eap_type=%d vlan_id=%d",
+				 "PMKID found from PMKSA cache eap_type=%d vlan=%d%s",
 				 sm->pmksa->eap_type_authsrv,
-				 sm->pmksa->vlan_id);
+				 vlan ? vlan->untagged : 0,
+				 (vlan && vlan->tagged[0]) ? "+" : "");
 		os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmkid, PMKID_LEN);
 	}
 
diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c
index ba58f3e..faf38c9 100644
--- a/src/ap/wps_hostapd.c
+++ b/src/ap/wps_hostapd.c
@@ -1627,7 +1627,8 @@
 	unsigned int pin;
 	struct wps_ap_pin_data data;
 
-	pin = wps_generate_pin();
+	if (wps_generate_pin(&pin) < 0)
+		return NULL;
 	os_snprintf(data.pin_txt, sizeof(data.pin_txt), "%08u", pin);
 	data.timeout = timeout;
 	hostapd_wps_for_each(hapd, wps_ap_pin_set, &data);
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 8dee883..2e503b9 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -115,6 +115,11 @@
 			elems->osen = pos;
 			elems->osen_len = elen;
 			break;
+		case MBO_OUI_TYPE:
+			/* MBO-OCE */
+			elems->mbo = pos;
+			elems->mbo_len = elen;
+			break;
 		default:
 			wpa_printf(MSG_MSGDUMP, "Unknown WFA "
 				   "information element ignored "
@@ -1172,3 +1177,135 @@
 
 	return mb_ies;
 }
+
+
+const struct oper_class_map global_op_class[] = {
+	{ HOSTAPD_MODE_IEEE80211G, 81, 1, 13, 1, BW20, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211G, 82, 14, 14, 1, BW20, NO_P2P_SUPP },
+
+	/* Do not enable HT40 on 2.4 GHz for P2P use for now */
+	{ HOSTAPD_MODE_IEEE80211G, 83, 1, 9, 1, BW40PLUS, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211G, 84, 5, 13, 1, BW40MINUS, NO_P2P_SUPP },
+
+	{ HOSTAPD_MODE_IEEE80211A, 115, 36, 48, 4, BW20, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 116, 36, 44, 8, BW40PLUS, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 117, 40, 48, 8, BW40MINUS, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 118, 52, 64, 4, BW20, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 119, 52, 60, 8, BW40PLUS, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 120, 56, 64, 8, BW40MINUS, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 121, 100, 140, 4, BW20, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 122, 100, 132, 8, BW40PLUS, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 123, 104, 136, 8, BW40MINUS, NO_P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 124, 149, 161, 4, BW20, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 125, 149, 169, 4, BW20, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 126, 149, 157, 8, BW40PLUS, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 127, 153, 161, 8, BW40MINUS, P2P_SUPP },
+
+	/*
+	 * IEEE P802.11ac/D7.0 Table E-4 actually talks about channel center
+	 * frequency index 42, 58, 106, 122, 138, 155 with channel spacing of
+	 * 80 MHz, but currently use the following definition for simplicity
+	 * (these center frequencies are not actual channels, which makes
+	 * wpas_p2p_allow_channel() fail). wpas_p2p_verify_80mhz() should take
+	 * care of removing invalid channels.
+	 */
+	{ HOSTAPD_MODE_IEEE80211A, 128, 36, 161, 4, BW80, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 129, 50, 114, 16, BW160, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 130, 36, 161, 4, BW80P80, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211AD, 180, 1, 4, 1, BW2160, P2P_SUPP },
+	{ -1, 0, 0, 0, 0, BW20, NO_P2P_SUPP }
+};
+
+
+static enum phy_type ieee80211_phy_type_by_freq(int freq)
+{
+	enum hostapd_hw_mode hw_mode;
+	u8 channel;
+
+	hw_mode = ieee80211_freq_to_chan(freq, &channel);
+
+	switch (hw_mode) {
+	case HOSTAPD_MODE_IEEE80211A:
+		return PHY_TYPE_OFDM;
+	case HOSTAPD_MODE_IEEE80211B:
+		return PHY_TYPE_HRDSSS;
+	case HOSTAPD_MODE_IEEE80211G:
+		return PHY_TYPE_ERP;
+	case HOSTAPD_MODE_IEEE80211AD:
+		return PHY_TYPE_DMG;
+	default:
+		return PHY_TYPE_UNSPECIFIED;
+	};
+}
+
+
+/* ieee80211_get_phy_type - Derive the phy type by freq and bandwidth */
+enum phy_type ieee80211_get_phy_type(int freq, int ht, int vht)
+{
+	if (vht)
+		return PHY_TYPE_VHT;
+	if (ht)
+		return PHY_TYPE_HT;
+
+	return ieee80211_phy_type_by_freq(freq);
+}
+
+
+size_t global_op_class_size = ARRAY_SIZE(global_op_class);
+
+
+/**
+ * get_ie - Fetch a specified information element from IEs buffer
+ * @ies: Information elements buffer
+ * @len: Information elements buffer length
+ * @eid: Information element identifier (WLAN_EID_*)
+ * Returns: Pointer to the information element (id field) or %NULL if not found
+ *
+ * This function returns the first matching information element in the IEs
+ * buffer or %NULL in case the element is not found.
+ */
+const u8 * get_ie(const u8 *ies, size_t len, u8 eid)
+{
+	const u8 *end;
+
+	if (!ies)
+		return NULL;
+
+	end = ies + len;
+
+	while (end - ies > 1) {
+		if (2 + ies[1] > end - ies)
+			break;
+
+		if (ies[0] == eid)
+			return ies;
+
+		ies += 2 + ies[1];
+	}
+
+	return NULL;
+}
+
+
+size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len)
+{
+	/*
+	 * MBO IE requires 6 bytes without the attributes: EID (1), length (1),
+	 * OUI (3), OUI type (1).
+	 */
+	if (len < 6 + attr_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MBO: Not enough room in buffer for MBO IE: buf len = %zu, attr_len = %zu",
+			   len, attr_len);
+		return 0;
+	}
+
+	*buf++ = WLAN_EID_VENDOR_SPECIFIC;
+	*buf++ = attr_len + 4;
+	WPA_PUT_BE24(buf, OUI_WFA);
+	buf += 3;
+	*buf++ = MBO_OUI_TYPE;
+	os_memcpy(buf, attr, attr_len);
+
+	return 6 + attr_len;
+}
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 55ce022..3a3dd4d 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -9,6 +9,8 @@
 #ifndef IEEE802_11_COMMON_H
 #define IEEE802_11_COMMON_H
 
+#include "defs.h"
+
 #define MAX_NOF_MB_IES_SUPPORTED 5
 
 struct mb_ies_info {
@@ -56,6 +58,7 @@
 	const u8 *bss_max_idle_period;
 	const u8 *ssid_list;
 	const u8 *osen;
+	const u8 *mbo;
 	const u8 *ampe;
 	const u8 *mic;
 	const u8 *pref_freq_list;
@@ -85,6 +88,7 @@
 	u8 ext_capab_len;
 	u8 ssid_list_len;
 	u8 osen_len;
+	u8 mbo_len;
 	u8 ampe_len;
 	u8 mic_len;
 	u8 pref_freq_list_len;
@@ -118,6 +122,7 @@
 						   int sec_channel, int vht,
 						   u8 *op_class, u8 *channel);
 int ieee80211_is_dfs(int freq);
+enum phy_type ieee80211_get_phy_type(int freq, int ht, int vht);
 
 int supp_rates_11b_only(struct ieee802_11_elems *elems);
 int mb_ies_info_by_ies(struct mb_ies_info *info, const u8 *ies_buf,
@@ -125,4 +130,22 @@
 struct wpabuf * mb_ies_by_info(struct mb_ies_info *info);
 
 const char * fc2str(u16 fc);
+
+struct oper_class_map {
+	enum hostapd_hw_mode mode;
+	u8 op_class;
+	u8 min_chan;
+	u8 max_chan;
+	u8 inc;
+	enum { BW20, BW40PLUS, BW40MINUS, BW80, BW2160, BW160, BW80P80 } bw;
+	enum { P2P_SUPP, NO_P2P_SUPP } p2p;
+};
+
+extern const struct oper_class_map global_op_class[];
+extern size_t global_op_class_size;
+
+const u8 * get_ie(const u8 *ies, size_t len, u8 eid);
+
+size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len);
+
 #endif /* IEEE802_11_COMMON_H */
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 5b8d130..73ffc99 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -94,8 +94,13 @@
 #define WLAN_CAPABILITY_PBCC BIT(6)
 #define WLAN_CAPABILITY_CHANNEL_AGILITY BIT(7)
 #define WLAN_CAPABILITY_SPECTRUM_MGMT BIT(8)
+#define WLAN_CAPABILITY_QOS BIT(9)
 #define WLAN_CAPABILITY_SHORT_SLOT_TIME BIT(10)
+#define WLAN_CAPABILITY_APSD BIT(11)
+#define WLAN_CAPABILITY_RADIO_MEASUREMENT BIT(12)
 #define WLAN_CAPABILITY_DSSS_OFDM BIT(13)
+#define WLAN_CAPABILITY_DELAYED_BLOCK_ACK BIT(14)
+#define WLAN_CAPABILITY_IMM_BLOCK_ACK BIT(15)
 
 /* Status codes (IEEE 802.11-2007, 7.3.1.9, Table 7-23) */
 #define WLAN_STATUS_SUCCESS 0
@@ -885,6 +890,8 @@
 #define WFD_OUI_TYPE 10
 #define HS20_IE_VENDOR_TYPE 0x506f9a10
 #define OSEN_IE_VENDOR_TYPE 0x506f9a12
+#define MBO_IE_VENDOR_TYPE 0x506f9a16
+#define MBO_OUI_TYPE 22
 
 #define WMM_OUI_TYPE 2
 #define WMM_OUI_SUBTYPE_INFORMATION_ELEMENT 0
@@ -1027,6 +1034,91 @@
 #define HS20_DEAUTH_REASON_CODE_BSS 0
 #define HS20_DEAUTH_REASON_CODE_ESS 1
 
+/* MBO v0.0_r19, 4.2: MBO Attributes */
+/* Table 4-5: MBO Attributes */
+enum mbo_attr_id {
+	MBO_ATTR_ID_AP_CAPA_IND = 1,
+	MBO_ATTR_ID_NON_PREF_CHAN_REPORT = 2,
+	MBO_ATTR_ID_CELL_DATA_CAPA = 3,
+	MBO_ATTR_ID_ASSOC_DISALLOW = 4,
+	MBO_ATTR_ID_CELL_DATA_PREF = 5,
+	MBO_ATTR_ID_TRANSITION_REASON = 6,
+	MBO_ATTR_ID_TRANSITION_REJECT_REASON = 7,
+	MBO_ATTR_ID_ASSOC_RETRY_DELAY = 8,
+};
+
+/* MBO v0.0_r19, 4.2.1: MBO AP Capability Indication Attribute */
+/* Table 4-7: MBO AP Capability Indication Field Values */
+#define MBO_AP_CAPA_CELL_AWARE BIT(6)
+
+/* MBO v0.0_r19, 4.2.2: Non-preferred Channel Report Attribute */
+/* Table 4-10: Reason Code Field Values */
+enum mbo_non_pref_chan_reason {
+	MBO_NON_PREF_CHAN_REASON_UNSPECIFIED = 0,
+	MBO_NON_PREF_CHAN_REASON_RSSI = 1,
+	MBO_NON_PREF_CHAN_REASON_EXT_INTERFERENCE = 2,
+	MBO_NON_PREF_CHAN_REASON_INT_INTERFERENCE = 3,
+};
+
+/* MBO v0.0_r19, 4.2.3: Cellular Data Capabilities Attribute */
+/* Table 4-13: Cellular Data Connectivity Field */
+enum mbo_cellular_capa {
+	MBO_CELL_CAPA_AVAILABLE = 1,
+	MBO_CELL_CAPA_NOT_AVAILABLE = 2,
+	MBO_CELL_CAPA_NOT_SUPPORTED = 3,
+};
+
+/* MBO v0.0_r19, 4.2.4: Association Disallowed Attribute */
+/* Table 4-15: Reason Code Field Values */
+enum mbo_assoc_disallow_reason {
+	MBO_ASSOC_DISALLOW_REASON_UNSPECIFIED = 1,
+	MBO_ASSOC_DISALLOW_REASON_MAX_STA = 2,
+	MBO_ASSOC_DISALLOW_REASON_AIR_INTERFERENCE = 3,
+	MBO_ASSOC_DISALLOW_REASON_AUTH_SERVER_OVERLOAD = 4,
+	MBO_ASSOC_DISALLOW_REASON_LOW_RSSI = 5,
+};
+
+/* MBO v0.0_r19, 4.2.5: Cellular Data Connection Preference Attribute */
+/* Table 4-17: Cellular Preference Field Values */
+enum mbo_cell_pref {
+	MBO_CELL_PREF_EXCLUDED = 0,
+	MBO_CELL_PREF_NO_USE = 1,
+	MBO_CELL_PREF_USE = 255
+};
+
+/* MBO v0.0_r19, 4.2.6: Transition Reason Code Attribute */
+/* Table 4-19: Transition Reason Code Field Values */
+enum mbo_transition_reason {
+	MBO_TRANSITION_REASON_UNSPECIFIED = 0,
+	MBO_TRANSITION_REASON_FRAME_LOSS = 1,
+	MBO_TRANSITION_REASON_DELAY = 2,
+	MBO_TRANSITION_REASON_BANDWIDTH = 3,
+	MBO_TRANSITION_REASON_LOAD_BALANCE = 4,
+	MBO_TRANSITION_REASON_RSSI = 5,
+	MBO_TRANSITION_REASON_RETRANSMISSIONS = 6,
+	MBO_TRANSITION_REASON_INTERFERENCE = 7,
+	MBO_TRANSITION_REASON_GRAY_ZONE = 8,
+	MBO_TRANSITION_REASON_PREMIUM_AP = 9,
+};
+
+/* MBO v0.0_r19, 4.2.7: Transition Rejection Reason Code Attribute */
+/* Table 4-21: Transition Rejection Reason Code Field Values */
+enum mbo_transition_reject_reason {
+	MBO_TRANSITION_REJECT_REASON_UNSPECIFIED = 0,
+	MBO_TRANSITION_REJECT_REASON_FRAME_LOSS = 1,
+	MBO_TRANSITION_REJECT_REASON_DELAY = 2,
+	MBO_TRANSITION_REJECT_REASON_QOS_CAPACITY = 3,
+	MBO_TRANSITION_REJECT_REASON_RSSI = 4,
+	MBO_TRANSITION_REJECT_REASON_INTERFERENCE = 5,
+	MBO_TRANSITION_REJECT_REASON_SERVICES = 6,
+};
+
+/* MBO v0.0_r19, 4.4: WNM-Notification vendor subelements */
+enum wfa_wnm_notif_subelem_id {
+	WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT = 2,
+	WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA = 3,
+};
+
 /* Wi-Fi Direct (P2P) */
 
 #define P2P_OUI_TYPE 9
@@ -1439,4 +1531,34 @@
 	FST_ACTION_ON_CHANNEL_TUNNEL = 5,
 };
 
+/* IEEE Std 802.11ac-2013, Annex C - dot11PHYType */
+enum phy_type {
+	PHY_TYPE_UNSPECIFIED = 0,
+	PHY_TYPE_FHSS = 1,
+	PHY_TYPE_DSSS = 2,
+	PHY_TYPE_IRBASEBAND = 3,
+	PHY_TYPE_OFDM = 4,
+	PHY_TYPE_HRDSSS = 5,
+	PHY_TYPE_ERP = 6,
+	PHY_TYPE_HT = 7,
+	PHY_TYPE_DMG = 8,
+	PHY_TYPE_VHT = 9,
+};
+
+/* IEEE Std 802.11-2012, 8.4.2.39 - Neighbor Report element */
+/* BSSID Information Field */
+#define NEI_REP_BSSID_INFO_AP_NOT_REACH BIT(0)
+#define NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH BIT(1)
+#define NEI_REP_BSSID_INFO_AP_REACHABLE (BIT(0) | BIT(1))
+#define NEI_REP_BSSID_INFO_SECURITY BIT(2)
+#define NEI_REP_BSSID_INFO_KEY_SCOPE BIT(3)
+#define NEI_REP_BSSID_INFO_SPECTRUM_MGMT BIT(4)
+#define NEI_REP_BSSID_INFO_QOS BIT(5)
+#define NEI_REP_BSSID_INFO_APSD BIT(6)
+#define NEI_REP_BSSID_INFO_RM BIT(7)
+#define NEI_REP_BSSID_INFO_DELAYED_BA BIT(8)
+#define NEI_REP_BSSID_INFO_IMM_BA BIT(9)
+#define NEI_REP_BSSID_INFO_MOBILITY_DOMAIN BIT(10)
+#define NEI_REP_BSSID_INFO_HT BIT(11)
+
 #endif /* IEEE802_11_DEFS_H */
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index 3e0a7ec..d9641bb 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -270,6 +270,12 @@
 /* BSS Transition Management Response frame received */
 #define BSS_TM_RESP "BSS-TM-RESP "
 
+/* MBO IE with cellular data connection preference received */
+#define MBO_CELL_PREFERENCE "MBO-CELL-PREFERENCE "
+
+/* BSS Transition Management Request received with MBO transition reason */
+#define MBO_TRANSITION_REASON "MBO-TRANSITION-REASON "
+
 /* BSS command information masks */
 
 #define WPA_BSS_MASK_ALL		0xFFFDFFFF
diff --git a/src/crypto/crypto_openssl.c b/src/crypto/crypto_openssl.c
index 071a4dc..ee20b37 100644
--- a/src/crypto/crypto_openssl.c
+++ b/src/crypto/crypto_openssl.c
@@ -34,12 +34,6 @@
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
 /* Compatibility wrappers for older versions. */
 
-static int EVP_CIPHER_CTX_reset(EVP_CIPHER_CTX *ctx)
-{
-	return EVP_CIPHER_CTX_cleanup(ctx);
-}
-
-
 static HMAC_CTX * HMAC_CTX_new(void)
 {
 	HMAC_CTX *ctx;
@@ -53,6 +47,7 @@
 
 static void HMAC_CTX_free(HMAC_CTX *ctx)
 {
+	HMAC_CTX_cleanup(ctx);
 	bin_clear_free(ctx, sizeof(*ctx));
 }
 
@@ -209,7 +204,7 @@
 
 out:
 	if (ctx)
-		EVP_CIPHER_CTX_reset(ctx);
+		EVP_CIPHER_CTX_free(ctx);
 	return res;
 #endif /* OPENSSL_NO_RC4 */
 }
@@ -304,7 +299,6 @@
 		wpa_printf(MSG_ERROR, "OpenSSL: Unexpected padding length %d "
 			   "in AES encrypt", len);
 	}
-	EVP_CIPHER_CTX_reset(c);
 	EVP_CIPHER_CTX_free(c);
 }
 
@@ -357,7 +351,6 @@
 		wpa_printf(MSG_ERROR, "OpenSSL: Unexpected padding length %d "
 			   "in AES decrypt", len);
 	}
-	EVP_CIPHER_CTX_reset(c);
 	EVP_CIPHER_CTX_free(c);
 }
 
@@ -416,7 +409,7 @@
 	    clen == (int) data_len &&
 	    EVP_EncryptFinal_ex(ctx, buf, &len) == 1 && len == 0)
 		res = 0;
-	EVP_CIPHER_CTX_reset(ctx);
+	EVP_CIPHER_CTX_free(ctx);
 
 	return res;
 }
@@ -443,7 +436,7 @@
 	    plen == (int) data_len &&
 	    EVP_DecryptFinal_ex(ctx, buf, &len) == 1 && len == 0)
 		res = 0;
-	EVP_CIPHER_CTX_reset(ctx);
+	EVP_CIPHER_CTX_free(ctx);
 
 	return res;
 
@@ -557,7 +550,7 @@
 	    !EVP_CIPHER_CTX_set_key_length(ctx->enc, key_len) ||
 	    !EVP_EncryptInit_ex(ctx->enc, NULL, NULL, key, iv)) {
 		if (ctx->enc)
-			EVP_CIPHER_CTX_reset(ctx->enc);
+			EVP_CIPHER_CTX_free(ctx->enc);
 		os_free(ctx);
 		return NULL;
 	}
@@ -567,9 +560,9 @@
 	    !EVP_DecryptInit_ex(ctx->dec, cipher, NULL, NULL, NULL) ||
 	    !EVP_CIPHER_CTX_set_key_length(ctx->dec, key_len) ||
 	    !EVP_DecryptInit_ex(ctx->dec, NULL, NULL, key, iv)) {
-		EVP_CIPHER_CTX_reset(ctx->enc);
+		EVP_CIPHER_CTX_free(ctx->enc);
 		if (ctx->dec)
-			EVP_CIPHER_CTX_reset(ctx->dec);
+			EVP_CIPHER_CTX_free(ctx->dec);
 		os_free(ctx);
 		return NULL;
 	}
@@ -601,8 +594,8 @@
 
 void crypto_cipher_deinit(struct crypto_cipher *ctx)
 {
-	EVP_CIPHER_CTX_reset(ctx->enc);
-	EVP_CIPHER_CTX_reset(ctx->dec);
+	EVP_CIPHER_CTX_free(ctx->enc);
+	EVP_CIPHER_CTX_free(ctx->dec);
 	os_free(ctx);
 }
 
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index e34a3d0..ebcc545 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -900,6 +900,7 @@
 		}
 #endif /* OPENSSL_FIPS */
 #endif /* CONFIG_FIPS */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
 		SSL_load_error_strings();
 		SSL_library_init();
 #ifndef OPENSSL_NO_SHA256
@@ -921,6 +922,7 @@
 #endif /* OPENSSL_NO_RC2 */
 		PKCS12_PBE_add();
 #endif  /* PKCS12_FUNCS */
+#endif /* < 1.1.0 */
 	} else {
 		context = tls_context_new(conf);
 		if (context == NULL)
@@ -941,6 +943,7 @@
 			os_free(tls_global);
 			tls_global = NULL;
 		}
+		os_free(data);
 		return NULL;
 	}
 	data->ssl = ssl;
@@ -1021,6 +1024,7 @@
 
 	tls_openssl_ref_count--;
 	if (tls_openssl_ref_count == 0) {
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
 #ifndef OPENSSL_NO_ENGINE
 		ENGINE_cleanup();
 #endif /* OPENSSL_NO_ENGINE */
@@ -1028,6 +1032,7 @@
 		ERR_remove_thread_state(NULL);
 		ERR_free_strings();
 		EVP_cleanup();
+#endif /* < 1.1.0 */
 		os_free(tls_global->ocsp_stapling_response);
 		tls_global->ocsp_stapling_response = NULL;
 		os_free(tls_global);
@@ -1419,6 +1424,8 @@
 			found++;
 	}
 
+	sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free);
+
 	return found;
 }
 
@@ -1531,9 +1538,11 @@
 		    1) {
 			wpa_printf(MSG_DEBUG, "TLS: %s in dNSName found",
 				   full ? "Match" : "Suffix match");
+			sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free);
 			return 1;
 		}
 	}
+	sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free);
 
 	if (dns_name) {
 		wpa_printf(MSG_DEBUG, "TLS: None of the dNSName(s) matched");
@@ -1730,6 +1739,7 @@
 		pos += gen->d.ia5->length;
 		*pos = '\0';
 	}
+	sk_GENERAL_NAME_pop_free(ext, GENERAL_NAME_free);
 
 	for (alt = 0; alt < num_altsubject; alt++)
 		ev.peer_cert.altsubject[alt] = altsubject[alt];
@@ -2478,13 +2488,15 @@
 				tls_show_errors(MSG_DEBUG, __func__,
 						"Failed to add additional certificate");
 				res = -1;
+				X509_free(cert);
 				break;
 			}
+			X509_free(cert);
 		}
 		if (!res) {
 			/* Try to continue anyway */
 		}
-		sk_X509_free(certs);
+		sk_X509_pop_free(certs, X509_free);
 #ifndef OPENSSL_IS_BORINGSSL
 		if (ssl)
 			res = SSL_build_cert_chain(
@@ -2522,11 +2534,12 @@
 			 */
 			if (SSL_CTX_add_extra_chain_cert(data->ssl, cert) != 1)
 			{
+				X509_free(cert);
 				res = -1;
 				break;
 			}
 		}
-		sk_X509_free(certs);
+		sk_X509_pop_free(certs, X509_free);
 #endif /* OPENSSL_VERSION_NUMBER >= 0x10002000L */
 	}
 
@@ -3842,10 +3855,12 @@
 		wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s",
 			   (conn->flags & TLS_CONN_REQUIRE_OCSP) ? "" :
 			   " (OCSP not required)");
+		OCSP_CERTID_free(id);
 		OCSP_BASICRESP_free(basic);
 		OCSP_RESPONSE_free(rsp);
 		return (conn->flags & TLS_CONN_REQUIRE_OCSP) ? 0 : 1;
 	}
+	OCSP_CERTID_free(id);
 
 	if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) {
 		tls_show_errors(MSG_INFO, __func__,
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index aad0e04..2315226 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -860,6 +860,12 @@
 	 * RRM (Radio Resource Measurements)
 	 */
 	int rrm_used;
+
+	/**
+	 * pbss - If set, connect to a PCP in a PBSS. Otherwise, connect to an
+	 * AP as usual. Valid for DMG network only.
+	 */
+	int pbss;
 };
 
 enum hide_ssid {
@@ -1087,6 +1093,12 @@
 	 * reenable - Whether this is to re-enable beaconing
 	 */
 	int reenable;
+
+	/**
+	 * pbss - Whether to start a PCP (in PBSS) instead of an AP in
+	 * infrastructure BSS. Valid only for DMG network.
+	 */
+	int pbss;
 };
 
 struct wpa_driver_mesh_bss_params {
@@ -1248,8 +1260,13 @@
 #define WPA_DRIVER_FLAGS_SUPPORT_HW_MODE_ANY	0x0000004000000000ULL
 /** Driver supports simultaneous off-channel operations */
 #define WPA_DRIVER_FLAGS_OFFCHANNEL_SIMULTANEOUS	0x0000008000000000ULL
+/** Driver supports full AP client state */
+#define WPA_DRIVER_FLAGS_FULL_AP_CLIENT_STATE	0x0000010000000000ULL
 	u64 flags;
 
+#define FULL_AP_CLIENT_STATE_SUPP(drv_flags) \
+	(drv_flags & WPA_DRIVER_FLAGS_FULL_AP_CLIENT_STATE)
+
 #define WPA_DRIVER_SMPS_MODE_STATIC			0x00000001
 #define WPA_DRIVER_SMPS_MODE_DYNAMIC			0x00000002
 	unsigned int smps_modes;
@@ -1356,7 +1373,9 @@
 struct hostapd_data;
 
 struct hostap_sta_driver_data {
-	unsigned long rx_packets, tx_packets, rx_bytes, tx_bytes;
+	unsigned long rx_packets, tx_packets;
+	unsigned long long rx_bytes, tx_bytes;
+	int bytes_64bit; /* whether 64-bit byte counters are supported */
 	unsigned long current_tx_rate;
 	unsigned long inactive_msec;
 	unsigned long flags;
@@ -1496,6 +1515,7 @@
 #define WPA_STA_MFP BIT(3)
 #define WPA_STA_TDLS_PEER BIT(4)
 #define WPA_STA_AUTHENTICATED BIT(5)
+#define WPA_STA_ASSOCIATED BIT(6)
 
 enum tdls_oper {
 	TDLS_DISCOVERY_REQ,
@@ -2357,12 +2377,17 @@
 	 * @params: Station parameters
 	 * Returns: 0 on success, -1 on failure
 	 *
-	 * This function is used to add a station entry to the driver once the
-	 * station has completed association. This is only used if the driver
+	 * This function is used to add or set (params->set 1) a station
+	 * entry in the driver. Adding STA entries is used only if the driver
 	 * does not take care of association processing.
 	 *
-	 * With TDLS, this function is also used to add or set (params->set 1)
-	 * TDLS peer entries.
+	 * With drivers that don't support full AP client state, this function
+	 * is used to add a station entry to the driver once the station has
+	 * completed association.
+	 *
+	 * With TDLS, this function is used to add or set (params->set 1)
+	 * TDLS peer entries (even with drivers that do not support full AP
+	 * client state).
 	 */
 	int (*sta_add)(void *priv, struct hostapd_sta_add_params *params);
 
diff --git a/src/drivers/driver_common.c b/src/drivers/driver_common.c
index aebea8c..b32d35f 100644
--- a/src/drivers/driver_common.c
+++ b/src/drivers/driver_common.c
@@ -183,12 +183,12 @@
 
 	start = buf;
 	while (*start != '\0') {
-		while (isblank(*start))
+		while (isblank((unsigned char) *start))
 			start++;
 		if (*start == '\0')
 			break;
 		end = start;
-		while (!isblank(*end) && *end != '\0')
+		while (!isblank((unsigned char) *end) && *end != '\0')
 			end++;
 		last = *end == '\0';
 		*end = '\0';
diff --git a/src/drivers/driver_ndis.c b/src/drivers/driver_ndis.c
index fd32134..9440f01 100644
--- a/src/drivers/driver_ndis.c
+++ b/src/drivers/driver_ndis.c
@@ -35,6 +35,7 @@
 #include "driver.h"
 #include "eloop.h"
 #include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
 #include "driver_ndis.h"
 
 int wpa_driver_register_event_cb(struct wpa_driver_ndis_data *drv);
@@ -780,20 +781,7 @@
 
 static const u8 * wpa_scan_get_ie(const struct wpa_scan_res *res, u8 ie)
 {
-	const u8 *end, *pos;
-
-	pos = (const u8 *) (res + 1);
-	end = pos + res->ie_len;
-
-	while (end - pos > 1) {
-		if (2 + pos[1] > end - pos)
-			break;
-		if (pos[0] == ie)
-			return pos;
-		pos += 2 + pos[1];
-	}
-
-	return NULL;
+	return get_ie((const u8 *) (res + 1), res->ie_len, ie);
 }
 
 
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 4cb92d0..5fec430 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -3584,6 +3584,12 @@
 	}
 #endif /* CONFIG_P2P */
 
+	if (params->pbss) {
+		wpa_printf(MSG_DEBUG, "nl80211: PBSS");
+		if (nla_put_flag(msg, NL80211_ATTR_PBSS))
+			goto fail;
+	}
+
 	ret = send_and_recv_msgs(drv, msg, NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
@@ -3742,6 +3748,8 @@
 		f |= BIT(NL80211_STA_FLAG_TDLS_PEER);
 	if (flags & WPA_STA_AUTHENTICATED)
 		f |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+	if (flags & WPA_STA_ASSOCIATED)
+		f |= BIT(NL80211_STA_FLAG_ASSOCIATED);
 
 	return f;
 }
@@ -3794,7 +3802,17 @@
 	if (!msg || nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, params->addr))
 		goto fail;
 
-	if (!params->set || (params->flags & WPA_STA_TDLS_PEER)) {
+	/*
+	 * Set the below properties only in one of the following cases:
+	 * 1. New station is added, already associated.
+	 * 2. Set WPA_STA_TDLS_PEER station.
+	 * 3. Set an already added unassociated station, if driver supports
+	 * full AP client state. (Set these properties after station became
+	 * associated will be rejected by the driver).
+	 */
+	if (!params->set || (params->flags & WPA_STA_TDLS_PEER) ||
+	    (params->set && FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags) &&
+	     (params->flags & WPA_STA_ASSOCIATED))) {
 		wpa_hexdump(MSG_DEBUG, "  * supported rates",
 			    params->supp_rates, params->supp_rates_len);
 		wpa_printf(MSG_DEBUG, "  * capability=0x%x",
@@ -3842,9 +3860,12 @@
 			/*
 			 * cfg80211 validates that AID is non-zero, so we have
 			 * to make this a non-zero value for the TDLS case where
-			 * a dummy STA entry is used for now.
+			 * a dummy STA entry is used for now and for a station
+			 * that is still not associated.
 			 */
-			wpa_printf(MSG_DEBUG, "  * aid=1 (TDLS workaround)");
+			wpa_printf(MSG_DEBUG, "  * aid=1 (%s workaround)",
+				   (params->flags & WPA_STA_TDLS_PEER) ?
+				   "TDLS" : "UNASSOC_STA");
 			if (nla_put_u16(msg, NL80211_ATTR_STA_AID, 1))
 				goto fail;
 		}
@@ -3857,6 +3878,15 @@
 		wpa_printf(MSG_DEBUG, "  * peer_aid=%u", params->aid);
 		if (nla_put_u16(msg, NL80211_ATTR_PEER_AID, params->aid))
 			goto fail;
+	} else if (FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags) &&
+		   (params->flags & WPA_STA_ASSOCIATED)) {
+		wpa_printf(MSG_DEBUG, "  * aid=%u", params->aid);
+		wpa_printf(MSG_DEBUG, "  * listen_interval=%u",
+			   params->listen_interval);
+		if (nla_put_u16(msg, NL80211_ATTR_STA_AID, params->aid) ||
+		    nla_put_u16(msg, NL80211_ATTR_STA_LISTEN_INTERVAL,
+				params->listen_interval))
+			goto fail;
 	}
 
 	if (params->vht_opmode_enabled) {
@@ -3887,6 +3917,36 @@
 	os_memset(&upd, 0, sizeof(upd));
 	upd.set = sta_flags_nl80211(params->flags);
 	upd.mask = upd.set | sta_flags_nl80211(params->flags_mask);
+
+	/*
+	 * If the driver doesn't support full AP client state, ignore ASSOC/AUTH
+	 * flags, as nl80211 driver moves a new station, by default, into
+	 * associated state.
+	 *
+	 * On the other hand, if the driver supports that feature and the
+	 * station is added in unauthenticated state, set the
+	 * authenticated/associated bits in the mask to prevent moving this
+	 * station to associated state before it is actually associated.
+	 *
+	 * This is irrelevant for mesh mode where the station is added to the
+	 * driver as authenticated already, and ASSOCIATED isn't part of the
+	 * nl80211 API.
+	 */
+	if (!is_mesh_interface(drv->nlmode)) {
+		if (!FULL_AP_CLIENT_STATE_SUPP(drv->capa.flags)) {
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Ignore ASSOC/AUTH flags since driver doesn't support full AP client state");
+			upd.mask &= ~(BIT(NL80211_STA_FLAG_ASSOCIATED) |
+				      BIT(NL80211_STA_FLAG_AUTHENTICATED));
+		} else if (!params->set &&
+			   !(params->flags & WPA_STA_TDLS_PEER)) {
+			if (!(params->flags & WPA_STA_AUTHENTICATED))
+				upd.mask |= BIT(NL80211_STA_FLAG_AUTHENTICATED);
+			if (!(params->flags & WPA_STA_ASSOCIATED))
+				upd.mask |= BIT(NL80211_STA_FLAG_ASSOCIATED);
+		}
+	}
+
 	wpa_printf(MSG_DEBUG, "  * flags set=0x%x mask=0x%x",
 		   upd.set, upd.mask);
 	if (nla_put(msg, NL80211_ATTR_STA_FLAGS2, sizeof(upd), &upd))
@@ -4744,6 +4804,12 @@
 	if (params->p2p)
 		wpa_printf(MSG_DEBUG, "  * P2P group");
 
+	if (params->pbss) {
+		wpa_printf(MSG_DEBUG, "  * PBSS");
+		if (nla_put_flag(msg, NL80211_ATTR_PBSS))
+			return -1;
+	}
+
 	return 0;
 }
 
@@ -5315,6 +5381,8 @@
 		[NL80211_STA_INFO_RX_PACKETS] = { .type = NLA_U32 },
 		[NL80211_STA_INFO_TX_PACKETS] = { .type = NLA_U32 },
 		[NL80211_STA_INFO_TX_FAILED] = { .type = NLA_U32 },
+		[NL80211_STA_INFO_RX_BYTES64] = { .type = NLA_U64 },
+		[NL80211_STA_INFO_TX_BYTES64] = { .type = NLA_U64 },
 	};
 
 	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
@@ -5340,10 +5408,23 @@
 	if (stats[NL80211_STA_INFO_INACTIVE_TIME])
 		data->inactive_msec =
 			nla_get_u32(stats[NL80211_STA_INFO_INACTIVE_TIME]);
+	/* For backwards compatibility, fetch the 32-bit counters first. */
 	if (stats[NL80211_STA_INFO_RX_BYTES])
 		data->rx_bytes = nla_get_u32(stats[NL80211_STA_INFO_RX_BYTES]);
 	if (stats[NL80211_STA_INFO_TX_BYTES])
 		data->tx_bytes = nla_get_u32(stats[NL80211_STA_INFO_TX_BYTES]);
+	if (stats[NL80211_STA_INFO_RX_BYTES64] &&
+	    stats[NL80211_STA_INFO_TX_BYTES64]) {
+		/*
+		 * The driver supports 64-bit counters, so use them to override
+		 * the 32-bit values.
+		 */
+		data->rx_bytes =
+			nla_get_u64(stats[NL80211_STA_INFO_RX_BYTES64]);
+		data->tx_bytes =
+			nla_get_u64(stats[NL80211_STA_INFO_TX_BYTES64]);
+		data->bytes_64bit = 1;
+	}
 	if (stats[NL80211_STA_INFO_RX_PACKETS])
 		data->rx_packets =
 			nla_get_u32(stats[NL80211_STA_INFO_RX_PACKETS]);
@@ -6679,9 +6760,9 @@
 
 static int nl80211_set_param(void *priv, const char *param)
 {
-	wpa_printf(MSG_DEBUG, "nl80211: driver param='%s'", param);
 	if (param == NULL)
 		return 0;
+	wpa_printf(MSG_DEBUG, "nl80211: driver param='%s'", param);
 
 #ifdef CONFIG_P2P
 	if (os_strstr(param, "use_p2p_group_interface=1")) {
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index 09e03b3..4fa7d5f 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -285,7 +285,6 @@
 struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv);
 void nl80211_dump_scan(struct wpa_driver_nl80211_data *drv);
 int wpa_driver_nl80211_abort_scan(void *priv);
-const u8 * nl80211_get_ie(const u8 *ies, size_t ies_len, u8 ie);
 int wpa_driver_nl80211_vendor_scan(struct i802_bss *bss,
 				   struct wpa_driver_scan_params *params);
 
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 8c3ba49..14a93a0 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -428,6 +428,9 @@
 
 	if (flags & NL80211_FEATURE_HT_IBSS)
 		capa->flags |= WPA_DRIVER_FLAGS_HT_IBSS;
+
+	if (flags & NL80211_FEATURE_FULL_AP_CLIENT_STATE)
+		capa->flags |= WPA_DRIVER_FLAGS_FULL_AP_CLIENT_STATE;
 }
 
 
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 2b2086c..98a2744 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -335,9 +335,9 @@
 		event.assoc_info.req_ies_len = nla_len(req_ie);
 
 		if (cmd == NL80211_CMD_ROAM) {
-			ssid = nl80211_get_ie(event.assoc_info.req_ies,
-					      event.assoc_info.req_ies_len,
-					      WLAN_EID_SSID);
+			ssid = get_ie(event.assoc_info.req_ies,
+				      event.assoc_info.req_ies_len,
+				      WLAN_EID_SSID);
 			if (ssid && ssid[1] > 0 && ssid[1] <= 32) {
 				drv->ssid_len = ssid[1];
 				os_memcpy(drv->ssid, ssid + 2, ssid[1]);
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index 2ff254e..2145022 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -15,6 +15,7 @@
 #include "utils/common.h"
 #include "utils/eloop.h"
 #include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
 #include "common/qca-vendor.h"
 #include "driver_nl80211.h"
 
@@ -522,28 +523,6 @@
 }
 
 
-const u8 * nl80211_get_ie(const u8 *ies, size_t ies_len, u8 ie)
-{
-	const u8 *end, *pos;
-
-	if (ies == NULL)
-		return NULL;
-
-	pos = ies;
-	end = ies + ies_len;
-
-	while (end - pos > 1) {
-		if (2 + pos[1] > end - pos)
-			break;
-		if (pos[0] == ie)
-			return pos;
-		pos += 2 + pos[1];
-	}
-
-	return NULL;
-}
-
-
 static int nl80211_scan_filtered(struct wpa_driver_nl80211_data *drv,
 				 const u8 *ie, size_t ie_len)
 {
@@ -553,7 +532,7 @@
 	if (drv->filter_ssids == NULL)
 		return 0;
 
-	ssid = nl80211_get_ie(ie, ie_len, WLAN_EID_SSID);
+	ssid = get_ie(ie, ie_len, WLAN_EID_SSID);
 	if (ssid == NULL)
 		return 1;
 
@@ -714,9 +693,9 @@
 		if (os_memcmp(res->res[i]->bssid, r->bssid, ETH_ALEN) != 0)
 			continue;
 
-		s1 = nl80211_get_ie((u8 *) (res->res[i] + 1),
-				    res->res[i]->ie_len, WLAN_EID_SSID);
-		s2 = nl80211_get_ie((u8 *) (r + 1), r->ie_len, WLAN_EID_SSID);
+		s1 = get_ie((u8 *) (res->res[i] + 1),
+			    res->res[i]->ie_len, WLAN_EID_SSID);
+		s2 = get_ie((u8 *) (r + 1), r->ie_len, WLAN_EID_SSID);
 		if (s1 == NULL || s2 == NULL || s1[1] != s2[1] ||
 		    os_memcmp(s1, s2, 2 + s1[1]) != 0)
 			continue;
diff --git a/src/drivers/driver_wired.c b/src/drivers/driver_wired.c
index f95f3cc..15e82df 100644
--- a/src/drivers/driver_wired.c
+++ b/src/drivers/driver_wired.c
@@ -8,6 +8,11 @@
  */
 
 #include "includes.h"
+
+#include "common.h"
+#include "eloop.h"
+#include "driver.h"
+
 #include <sys/ioctl.h>
 #include <net/if.h>
 #ifdef __linux__
@@ -23,10 +28,6 @@
 #include <sys/sockio.h>
 #endif /* __sun__ */
 
-#include "common.h"
-#include "eloop.h"
-#include "driver.h"
-
 #ifdef _MSC_VER
 #pragma pack(push, 1)
 #endif /* _MSC_VER */
diff --git a/src/drivers/nl80211_copy.h b/src/drivers/nl80211_copy.h
index 5b7b5eb..7758969 100644
--- a/src/drivers/nl80211_copy.h
+++ b/src/drivers/nl80211_copy.h
@@ -1789,6 +1789,10 @@
  *	thus it must not specify the number of iterations, only the interval
  *	between scans. The scan plans are executed sequentially.
  *	Each scan plan is a nested attribute of &enum nl80211_sched_scan_plan.
+ * @NL80211_ATTR_PBSS: flag attribute. If set it means operate
+ *	in a PBSS. Specified in %NL80211_CMD_CONNECT to request
+ *	connecting to a PCP, and in %NL80211_CMD_START_AP to start
+ *	a PCP instead of AP. Relevant for DMG networks only.
  *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
@@ -2164,6 +2168,8 @@
 	NL80211_ATTR_MAX_SCAN_PLAN_ITERATIONS,
 	NL80211_ATTR_SCHED_SCAN_PLANS,
 
+	NL80211_ATTR_PBSS,
+
 	/* add attributes here, update the policy in nl80211.c */
 
 	__NL80211_ATTR_AFTER_LAST,
diff --git a/src/drivers/rfkill.c b/src/drivers/rfkill.c
index 464cf78..4d4d1b4 100644
--- a/src/drivers/rfkill.c
+++ b/src/drivers/rfkill.c
@@ -185,6 +185,7 @@
 	if (!found)
 		goto fail2;
 
+	free(phy);
 	eloop_register_read_sock(rfkill->fd, rfkill_receive, rfkill, NULL);
 
 	return rfkill;
diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c
index 5c18978..9110ca5 100644
--- a/src/eap_peer/eap.c
+++ b/src/eap_peer/eap.c
@@ -1386,13 +1386,10 @@
 	return 0;
 }
 
-#endif /* PCSC_FUNCS */
-
 
 static int eap_sm_set_scard_pin(struct eap_sm *sm,
 				struct eap_peer_config *conf)
 {
-#ifdef PCSC_FUNCS
 	if (scard_set_pin(sm->scard_ctx, conf->pin)) {
 		/*
 		 * Make sure the same PIN is not tried again in order to avoid
@@ -1406,24 +1403,20 @@
 		return -1;
 	}
 	return 0;
-#else /* PCSC_FUNCS */
-	return -1;
-#endif /* PCSC_FUNCS */
 }
 
+
 static int eap_sm_get_scard_identity(struct eap_sm *sm,
 				     struct eap_peer_config *conf)
 {
-#ifdef PCSC_FUNCS
 	if (eap_sm_set_scard_pin(sm, conf))
 		return -1;
 
 	return eap_sm_imsi_identity(sm, conf);
-#else /* PCSC_FUNCS */
-	return -1;
-#endif /* PCSC_FUNCS */
 }
 
+#endif /* PCSC_FUNCS */
+
 
 /**
  * eap_sm_buildIdentity - Build EAP-Identity/Response for the current network
@@ -1466,23 +1459,27 @@
 				  identity, identity_len);
 	}
 
-	if (identity == NULL) {
-		wpa_printf(MSG_WARNING, "EAP: buildIdentity: identity "
-			   "configuration was not available");
-		if (config->pcsc) {
+	if (config->pcsc) {
+#ifdef PCSC_FUNCS
+		if (!identity) {
 			if (eap_sm_get_scard_identity(sm, config) < 0)
 				return NULL;
 			identity = config->identity;
 			identity_len = config->identity_len;
-			wpa_hexdump_ascii(MSG_DEBUG, "permanent identity from "
-					  "IMSI", identity, identity_len);
-		} else {
-			eap_sm_request_identity(sm);
+			wpa_hexdump_ascii(MSG_DEBUG,
+					  "permanent identity from IMSI",
+					  identity, identity_len);
+		} else if (eap_sm_set_scard_pin(sm, config) < 0) {
 			return NULL;
 		}
-	} else if (config->pcsc) {
-		if (eap_sm_set_scard_pin(sm, config) < 0)
-			return NULL;
+#else /* PCSC_FUNCS */
+		return NULL;
+#endif /* PCSC_FUNCS */
+	} else if (!identity) {
+		wpa_printf(MSG_WARNING,
+			"EAP: buildIdentity: identity configuration was not available");
+		eap_sm_request_identity(sm);
+		return NULL;
 	}
 
 	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_IDENTITY, identity_len,
@@ -1523,15 +1520,9 @@
 
 static struct wpabuf * eap_sm_buildNotify(int id)
 {
-	struct wpabuf *resp;
-
 	wpa_printf(MSG_DEBUG, "EAP: Generating EAP-Response Notification");
-	resp = eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_NOTIFICATION, 0,
-			     EAP_CODE_RESPONSE, id);
-	if (resp == NULL)
-		return NULL;
-
-	return resp;
+	return eap_msg_alloc(EAP_VENDOR_IETF, EAP_TYPE_NOTIFICATION, 0,
+			EAP_CODE_RESPONSE, id);
 }
 
 
diff --git a/src/eap_peer/eap_fast.c b/src/eap_peer/eap_fast.c
index 8bd1bdb..f03cd4a 100644
--- a/src/eap_peer/eap_fast.c
+++ b/src/eap_peer/eap_fast.c
@@ -113,8 +113,8 @@
 }
 
 
-static int eap_fast_parse_phase1(struct eap_fast_data *data,
-				 const char *phase1)
+static void eap_fast_parse_phase1(struct eap_fast_data *data,
+				  const char *phase1)
 {
 	const char *pos;
 
@@ -140,8 +140,6 @@
 		wpa_printf(MSG_DEBUG, "EAP-FAST: Using binary format for PAC "
 			   "list");
 	}
-
-	return 0;
 }
 
 
@@ -159,10 +157,8 @@
 	data->fast_version = EAP_FAST_VERSION;
 	data->max_pac_list_len = 10;
 
-	if (config->phase1 && eap_fast_parse_phase1(data, config->phase1) < 0) {
-		eap_fast_deinit(sm, data);
-		return NULL;
-	}
+	if (config->phase1)
+		eap_fast_parse_phase1(data, config->phase1);
 
 	if (eap_peer_select_phase2_methods(config, "auth=",
 					   &data->phase2_types,
diff --git a/src/eap_server/eap_server_pwd.c b/src/eap_server/eap_server_pwd.c
index 36ac555..64bf708 100644
--- a/src/eap_server/eap_server_pwd.c
+++ b/src/eap_server/eap_server_pwd.c
@@ -178,8 +178,13 @@
 		return;
 	}
 
-	/* an lfsr is good enough to generate unpredictable tokens */
-	data->token = os_random();
+	if (os_get_random((u8 *) &data->token, sizeof(data->token)) < 0) {
+		wpabuf_free(data->outbuf);
+		data->outbuf = NULL;
+		eap_pwd_state(data, FAILURE);
+		return;
+	}
+
 	wpabuf_put_be16(data->outbuf, data->group_num);
 	wpabuf_put_u8(data->outbuf, EAP_PWD_DEFAULT_RAND_FUNC);
 	wpabuf_put_u8(data->outbuf, EAP_PWD_DEFAULT_PRF);
diff --git a/src/radius/radius.c b/src/radius/radius.c
index a6304e1..da978db 100644
--- a/src/radius/radius.c
+++ b/src/radius/radius.c
@@ -173,6 +173,7 @@
 	{ RADIUS_ATTR_USER_PASSWORD, "User-Password", RADIUS_ATTR_UNDIST },
 	{ RADIUS_ATTR_NAS_IP_ADDRESS, "NAS-IP-Address", RADIUS_ATTR_IP },
 	{ RADIUS_ATTR_NAS_PORT, "NAS-Port", RADIUS_ATTR_INT32 },
+	{ RADIUS_ATTR_SERVICE_TYPE, "Service-Type", RADIUS_ATTR_INT32 },
 	{ RADIUS_ATTR_FRAMED_IP_ADDRESS, "Framed-IP-Address", RADIUS_ATTR_IP },
 	{ RADIUS_ATTR_FRAMED_MTU, "Framed-MTU", RADIUS_ATTR_INT32 },
 	{ RADIUS_ATTR_REPLY_MESSAGE, "Reply-Message", RADIUS_ATTR_TEXT },
@@ -215,6 +216,7 @@
 	  RADIUS_ATTR_INT32 },
 	{ RADIUS_ATTR_EVENT_TIMESTAMP, "Event-Timestamp",
 	  RADIUS_ATTR_INT32 },
+	{ RADIUS_ATTR_EGRESS_VLANID, "EGRESS-VLANID", RADIUS_ATTR_HEXDUMP },
 	{ RADIUS_ATTR_NAS_PORT_TYPE, "NAS-Port-Type", RADIUS_ATTR_INT32 },
 	{ RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP },
 	{ RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type",
@@ -1411,12 +1413,30 @@
 };
 
 
+static int cmp_int(const void *a, const void *b)
+{
+	int x, y;
+
+	x = *((int *) a);
+	y = *((int *) b);
+	return (x - y);
+}
+
+
 /**
  * radius_msg_get_vlanid - Parse RADIUS attributes for VLAN tunnel information
+ * The k tagged vlans found are sorted by vlan_id and stored in the first k
+ * items of tagged.
+ *
  * @msg: RADIUS message
- * Returns: VLAN ID for the first tunnel configuration or 0 if none is found
+ * @untagged: Pointer to store untagged vid
+ * @numtagged: Size of tagged
+ * @tagged: Pointer to store tagged list
+ *
+ * Returns: 0 if neither tagged nor untagged configuration is found, 1 otherwise
  */
-int radius_msg_get_vlanid(struct radius_msg *msg)
+int radius_msg_get_vlanid(struct radius_msg *msg, int *untagged, int numtagged,
+			  int *tagged)
 {
 	struct radius_tunnel_attrs tunnel[RADIUS_TUNNEL_TAGS], *tun;
 	size_t i;
@@ -1424,8 +1444,12 @@
 	const u8 *data;
 	char buf[10];
 	size_t dlen;
+	int j, taggedidx = 0, vlan_id;
 
 	os_memset(&tunnel, 0, sizeof(tunnel));
+	for (j = 0; j < numtagged; j++)
+		tagged[j] = 0;
+	*untagged = 0;
 
 	for (i = 0; i < msg->attr_used; i++) {
 		attr = radius_get_attr_hdr(msg, i);
@@ -1462,21 +1486,44 @@
 				break;
 			os_memcpy(buf, data, dlen);
 			buf[dlen] = '\0';
+			vlan_id = atoi(buf);
+			if (vlan_id <= 0)
+				break;
 			tun->tag_used++;
-			tun->vlanid = atoi(buf);
+			tun->vlanid = vlan_id;
+			break;
+		case RADIUS_ATTR_EGRESS_VLANID: /* RFC 4675 */
+			if (attr->length != 6)
+				break;
+			vlan_id = WPA_GET_BE24(data + 1);
+			if (vlan_id <= 0)
+				break;
+			if (data[0] == 0x32)
+				*untagged = vlan_id;
+			else if (data[0] == 0x31 && tagged &&
+				 taggedidx < numtagged)
+				tagged[taggedidx++] = vlan_id;
 			break;
 		}
 	}
 
+	/* Use tunnel with the lowest tag for untagged VLAN id */
 	for (i = 0; i < RADIUS_TUNNEL_TAGS; i++) {
 		tun = &tunnel[i];
 		if (tun->tag_used &&
 		    tun->type == RADIUS_TUNNEL_TYPE_VLAN &&
 		    tun->medium_type == RADIUS_TUNNEL_MEDIUM_TYPE_802 &&
-		    tun->vlanid > 0)
-			return tun->vlanid;
+		    tun->vlanid > 0) {
+			*untagged = tun->vlanid;
+			break;
+		}
 	}
 
+	if (taggedidx)
+		qsort(tagged, taggedidx, sizeof(int), cmp_int);
+
+	if (*untagged > 0 || taggedidx)
+		return 1;
 	return 0;
 }
 
diff --git a/src/radius/radius.h b/src/radius/radius.h
index 313fc65..cba2b91 100644
--- a/src/radius/radius.h
+++ b/src/radius/radius.h
@@ -52,6 +52,7 @@
        RADIUS_ATTR_USER_PASSWORD = 2,
        RADIUS_ATTR_NAS_IP_ADDRESS = 4,
        RADIUS_ATTR_NAS_PORT = 5,
+       RADIUS_ATTR_SERVICE_TYPE = 6,
        RADIUS_ATTR_FRAMED_IP_ADDRESS = 8,
        RADIUS_ATTR_FRAMED_MTU = 12,
        RADIUS_ATTR_REPLY_MESSAGE = 18,
@@ -80,6 +81,7 @@
        RADIUS_ATTR_ACCT_INPUT_GIGAWORDS = 52,
        RADIUS_ATTR_ACCT_OUTPUT_GIGAWORDS = 53,
        RADIUS_ATTR_EVENT_TIMESTAMP = 55,
+       RADIUS_ATTR_EGRESS_VLANID = 56,
        RADIUS_ATTR_NAS_PORT_TYPE = 61,
        RADIUS_ATTR_TUNNEL_TYPE = 64,
        RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65,
@@ -109,6 +111,9 @@
 };
 
 
+/* Service-Type values (RFC 2865, 5.6) */
+#define RADIUS_SERVICE_TYPE_FRAMED 2
+
 /* Termination-Action */
 #define RADIUS_TERMINATION_ACTION_DEFAULT 0
 #define RADIUS_TERMINATION_ACTION_RADIUS_REQUEST 1
@@ -274,7 +279,8 @@
 				  const u8 *data, size_t data_len,
 				  const u8 *secret, size_t secret_len);
 int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len);
-int radius_msg_get_vlanid(struct radius_msg *msg);
+int radius_msg_get_vlanid(struct radius_msg *msg, int *untagged, int numtagged,
+			  int *tagged);
 char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen,
 				      const u8 *secret, size_t secret_len,
 				      struct radius_msg *sent_msg, size_t n);
diff --git a/src/rsn_supp/pmksa_cache.c b/src/rsn_supp/pmksa_cache.c
index 1afeaa2..3d8d122 100644
--- a/src/rsn_supp/pmksa_cache.c
+++ b/src/rsn_supp/pmksa_cache.c
@@ -109,6 +109,7 @@
  * @pmksa: Pointer to PMKSA cache data from pmksa_cache_init()
  * @pmk: The new pairwise master key
  * @pmk_len: PMK length in bytes, usually PMK_LEN (32)
+ * @pmkid: Calculated PMKID
  * @kck: Key confirmation key or %NULL if not yet derived
  * @kck_len: KCK length in bytes
  * @aa: Authenticator address
@@ -124,7 +125,7 @@
  */
 struct rsn_pmksa_cache_entry *
 pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
-		const u8 *kck, size_t kck_len,
+		const u8 *pmkid, const u8 *kck, size_t kck_len,
 		const u8 *aa, const u8 *spa, void *network_ctx, int akmp)
 {
 	struct rsn_pmksa_cache_entry *entry, *pos, *prev;
@@ -141,7 +142,9 @@
 		return NULL;
 	os_memcpy(entry->pmk, pmk, pmk_len);
 	entry->pmk_len = pmk_len;
-	if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
+	if (pmkid)
+		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
+	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
 		rsn_pmkid_suite_b_192(kck, kck_len, aa, spa, entry->pmkid);
 	else if (wpa_key_mgmt_suite_b(akmp))
 		rsn_pmkid_suite_b(kck, kck_len, aa, spa, entry->pmkid);
@@ -344,7 +347,7 @@
 	struct rsn_pmksa_cache_entry *new_entry;
 
 	new_entry = pmksa_cache_add(pmksa, old_entry->pmk, old_entry->pmk_len,
-				    NULL, 0,
+				    NULL, NULL, 0,
 				    aa, pmksa->sm->own_addr,
 				    old_entry->network_ctx, old_entry->akmp);
 	if (new_entry == NULL)
diff --git a/src/rsn_supp/pmksa_cache.h b/src/rsn_supp/pmksa_cache.h
index d559430..daede6d 100644
--- a/src/rsn_supp/pmksa_cache.h
+++ b/src/rsn_supp/pmksa_cache.h
@@ -57,7 +57,7 @@
 int pmksa_cache_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len);
 struct rsn_pmksa_cache_entry *
 pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
-		const u8 *kck, size_t kck_len,
+		const u8 *pmkid, const u8 *kck, size_t kck_len,
 		const u8 *aa, const u8 *spa, void *network_ctx, int akmp);
 struct rsn_pmksa_cache_entry * pmksa_cache_get_current(struct wpa_sm *sm);
 void pmksa_cache_clear_current(struct wpa_sm *sm);
@@ -105,7 +105,7 @@
 
 static inline struct rsn_pmksa_cache_entry *
 pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
-		const u8 *kck, size_t kck_len,
+		const u8 *pmkid, const u8 *kck, size_t kck_len,
 		const u8 *aa, const u8 *spa, void *network_ctx, int akmp)
 {
 	return NULL;
diff --git a/src/rsn_supp/preauth.c b/src/rsn_supp/preauth.c
index 55a4a74..4c9a4fb 100644
--- a/src/rsn_supp/preauth.c
+++ b/src/rsn_supp/preauth.c
@@ -93,7 +93,7 @@
 			wpa_hexdump_key(MSG_DEBUG, "RSN: PMK from pre-auth",
 					pmk, pmk_len);
 			sm->pmk_len = pmk_len;
-			pmksa_cache_add(sm->pmksa, pmk, pmk_len,
+			pmksa_cache_add(sm->pmksa, pmk, pmk_len, NULL,
 					NULL, 0,
 					sm->preauth_bssid, sm->own_addr,
 					sm->network_ctx,
diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c
index 86e509e..e424168 100644
--- a/src/rsn_supp/tdls.c
+++ b/src/rsn_supp/tdls.c
@@ -2339,7 +2339,7 @@
 		    kde.ftie, sizeof(*ftie));
 	ftie = (struct wpa_tdls_ftie *) kde.ftie;
 
-	if (!os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) == 0) {
+	if (os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) != 0) {
 		wpa_printf(MSG_INFO, "TDLS: FTIE SNonce in TPK M2 does "
 			   "not match with FTIE SNonce used in TPK M1");
 		/* Silently discard the frame */
@@ -2518,13 +2518,13 @@
 		goto error;
 	}
 
-	if (!os_memcmp(peer->rnonce, ftie->Anonce, WPA_NONCE_LEN) == 0) {
+	if (os_memcmp(peer->rnonce, ftie->Anonce, WPA_NONCE_LEN) != 0) {
 		wpa_printf(MSG_INFO, "TDLS: FTIE ANonce in TPK M3 does "
 			   "not match with FTIE ANonce used in TPK M2");
 		goto error;
 	}
 
-	if (!os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) == 0) {
+	if (os_memcmp(peer->inonce, ftie->Snonce, WPA_NONCE_LEN) != 0) {
 		wpa_printf(MSG_INFO, "TDLS: FTIE SNonce in TPK M3 does not "
 			   "match with FTIE SNonce used in TPK M1");
 		goto error;
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 64ef933..e850119 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -249,7 +249,7 @@
 			    !wpa_key_mgmt_suite_b(sm->key_mgmt) &&
 			    !wpa_key_mgmt_ft(sm->key_mgmt)) {
 				sa = pmksa_cache_add(sm->pmksa,
-						     sm->pmk, pmk_len,
+						     sm->pmk, pmk_len, NULL,
 						     NULL, 0,
 						     src_addr, sm->own_addr,
 						     sm->network_ctx,
@@ -1297,7 +1297,7 @@
 	if (sm->proto == WPA_PROTO_RSN && wpa_key_mgmt_suite_b(sm->key_mgmt)) {
 		struct rsn_pmksa_cache_entry *sa;
 
-		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len,
+		sa = pmksa_cache_add(sm->pmksa, sm->pmk, sm->pmk_len, NULL,
 				     sm->ptk.kck, sm->ptk.kck_len,
 				     sm->bssid, sm->own_addr,
 				     sm->network_ctx, sm->key_mgmt);
@@ -2391,12 +2391,13 @@
  * @sm: Pointer to WPA state machine data from wpa_sm_init()
  * @pmk: The new PMK
  * @pmk_len: The length of the new PMK in bytes
+ * @pmkid: Calculated PMKID
  * @bssid: AA to add into PMKSA cache or %NULL to not cache the PMK
  *
  * Configure the PMK for WPA state machine.
  */
 void wpa_sm_set_pmk(struct wpa_sm *sm, const u8 *pmk, size_t pmk_len,
-		    const u8 *bssid)
+		    const u8 *pmkid, const u8 *bssid)
 {
 	if (sm == NULL)
 		return;
@@ -2411,7 +2412,7 @@
 #endif /* CONFIG_IEEE80211R */
 
 	if (bssid) {
-		pmksa_cache_add(sm->pmksa, pmk, pmk_len, NULL, 0,
+		pmksa_cache_add(sm->pmksa, pmk, pmk_len, pmkid, NULL, 0,
 				bssid, sm->own_addr,
 				sm->network_ctx, sm->key_mgmt);
 	}
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index f9c89b3..c89799a 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -114,7 +114,7 @@
 void wpa_sm_notify_assoc(struct wpa_sm *sm, const u8 *bssid);
 void wpa_sm_notify_disassoc(struct wpa_sm *sm);
 void wpa_sm_set_pmk(struct wpa_sm *sm, const u8 *pmk, size_t pmk_len,
-		    const u8 *bssid);
+		    const u8 *pmkid, const u8 *bssid);
 void wpa_sm_set_pmk_from_pmksa(struct wpa_sm *sm);
 void wpa_sm_set_fast_reauth(struct wpa_sm *sm, int fast_reauth);
 void wpa_sm_set_scard_ctx(struct wpa_sm *sm, void *scard_ctx);
@@ -181,7 +181,8 @@
 }
 
 static inline void wpa_sm_set_pmk(struct wpa_sm *sm, const u8 *pmk,
-				  size_t pmk_len, const u8 *bssid)
+				  size_t pmk_len, const u8 *pmkid,
+				  const u8 *bssid)
 {
 }
 
diff --git a/src/utils/common.c b/src/utils/common.c
index e74a2ae..9c7d0d4 100644
--- a/src/utils/common.c
+++ b/src/utils/common.c
@@ -86,7 +86,7 @@
 		return -1;
 
 	/* check for optional mask */
-	if (*r == '\0' || isspace(*r)) {
+	if (*r == '\0' || isspace((unsigned char) *r)) {
 		/* no mask specified, assume default */
 		os_memset(mask, 0xff, ETH_ALEN);
 	} else if (maskable && *r == '/') {
diff --git a/src/utils/eloop.c b/src/utils/eloop.c
index 831da12..436bc8c 100644
--- a/src/utils/eloop.c
+++ b/src/utils/eloop.c
@@ -239,7 +239,7 @@
 	default:
 		filter = 0;
 	}
-	EV_SET(&ke, sock, filter, EV_ADD, 0, 0, NULL);
+	EV_SET(&ke, sock, filter, EV_ADD, 0, 0, 0);
 	if (kevent(eloop.kqueuefd, &ke, 1, NULL, 0, NULL) == -1) {
 		wpa_printf(MSG_ERROR, "%s: kevent(ADD) for fd=%d failed: %s",
 			   __func__, sock, strerror(errno));
@@ -411,7 +411,7 @@
 	os_memset(&eloop.fd_table[sock], 0, sizeof(struct eloop_sock));
 #endif /* CONFIG_ELOOP_EPOLL */
 #ifdef CONFIG_ELOOP_KQUEUE
-	EV_SET(&ke, sock, 0, EV_DELETE, 0, 0, NULL);
+	EV_SET(&ke, sock, 0, EV_DELETE, 0, 0, 0);
 	if (kevent(eloop.kqueuefd, &ke, 1, NULL, 0, NULL) < 0) {
 		wpa_printf(MSG_ERROR, "%s: kevent(DEL) for fd=%d failed: %s",
 			   __func__, sock, strerror(errno));
@@ -1333,7 +1333,7 @@
 	kfd = kqueue();
 	if (kfd == -1)
 		return;
-	EV_SET(&ke1, sock, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, NULL);
+	EV_SET(&ke1, sock, EVFILT_READ, EV_ADD | EV_ONESHOT, 0, 0, 0);
 	kevent(kfd, &ke1, 1, &ke2, 1, NULL);
 	close(kfd);
 #endif /* CONFIG_ELOOP_KQUEUE */
diff --git a/src/utils/http_curl.c b/src/utils/http_curl.c
index bf32ab8..d594398 100644
--- a/src/utils/http_curl.c
+++ b/src/utils/http_curl.c
@@ -857,8 +857,10 @@
 	os_memset(hcert, 0, sizeof(*hcert));
 
 	*names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
-	if (*names)
+	if (*names) {
 		add_alt_names(ctx, hcert, *names);
+		sk_GENERAL_NAME_pop_free(*names, GENERAL_NAME_free);
+	}
 
 	add_logotype_ext(ctx, hcert, cert);
 }
@@ -1214,6 +1216,7 @@
 		wpa_printf(MSG_INFO, "OpenSSL: Could not find current server certificate from OCSP response%s",
 			   (ctx->ocsp == MANDATORY_OCSP) ? "" :
 			   " (OCSP not required)");
+		OCSP_CERTID_free(id);
 		OCSP_BASICRESP_free(basic);
 		OCSP_RESPONSE_free(rsp);
 		if (ctx->ocsp == MANDATORY_OCSP)
@@ -1221,6 +1224,7 @@
 			ctx->last_err = "Could not find current server certificate from OCSP response";
 		return (ctx->ocsp == MANDATORY_OCSP) ? 0 : 1;
 	}
+	OCSP_CERTID_free(id);
 
 	if (!OCSP_check_validity(this_update, next_update, 5 * 60, -1)) {
 		tls_show_errors(__func__, "OpenSSL: OCSP status times invalid");
diff --git a/src/utils/os_unix.c b/src/utils/os_unix.c
index 8f8dc5b..0118d98 100644
--- a/src/utils/os_unix.c
+++ b/src/utils/os_unix.c
@@ -372,6 +372,7 @@
 	if (total)
 		wpa_printf(MSG_INFO, "MEMLEAK: total %lu bytes",
 			   (unsigned long) total);
+	wpa_trace_deinit();
 #endif /* WPA_TRACE */
 }
 
diff --git a/src/utils/trace.c b/src/utils/trace.c
index 8484d27..d98c4b0 100644
--- a/src/utils/trace.c
+++ b/src/utils/trace.c
@@ -366,4 +366,11 @@
 	}
 }
 
+
+void wpa_trace_deinit(void)
+{
+	free(syms);
+	syms = NULL;
+}
+
 #endif /* WPA_TRACE */
diff --git a/src/utils/trace.h b/src/utils/trace.h
index 43ed86c..d1636de 100644
--- a/src/utils/trace.h
+++ b/src/utils/trace.h
@@ -66,4 +66,6 @@
 
 #endif /* WPA_TRACE_BFD */
 
+void wpa_trace_deinit(void);
+
 #endif /* TRACE_H */
diff --git a/src/wps/wps.h b/src/wps/wps.h
index ff4dd10..2505d2d 100644
--- a/src/wps/wps.h
+++ b/src/wps/wps.h
@@ -837,7 +837,7 @@
 
 unsigned int wps_pin_checksum(unsigned int pin);
 unsigned int wps_pin_valid(unsigned int pin);
-unsigned int wps_generate_pin(void);
+int wps_generate_pin(unsigned int *pin);
 int wps_pin_str_valid(const char *pin);
 void wps_free_pending_msgs(struct upnp_pending_message *msgs);
 
diff --git a/src/wps/wps_common.c b/src/wps/wps_common.c
index 88f85fe..c6a1cfd 100644
--- a/src/wps/wps_common.c
+++ b/src/wps/wps_common.c
@@ -235,20 +235,18 @@
  * wps_generate_pin - Generate a random PIN
  * Returns: Eight digit PIN (i.e., including the checksum digit)
  */
-unsigned int wps_generate_pin(void)
+int wps_generate_pin(unsigned int *pin)
 {
 	unsigned int val;
 
 	/* Generate seven random digits for the PIN */
-	if (random_get_bytes((unsigned char *) &val, sizeof(val)) < 0) {
-		struct os_time now;
-		os_get_time(&now);
-		val = os_random() ^ now.sec ^ now.usec;
-	}
+	if (random_get_bytes((unsigned char *) &val, sizeof(val)) < 0)
+		return -1;
 	val %= 10000000;
 
 	/* Append checksum digit */
-	return val * 10 + wps_pin_checksum(val);
+	*pin = val * 10 + wps_pin_checksum(val);
+	return 0;
 }