[wpa_supplicant] Cumulative patch from c4e90da6d

Bug: 124017368
Test: Device boots up and connects to WPA3/OWE wifi networks, run traffic.
Test: Able to turn on/off softap, associate wifi STA, run traffic.
Test: DPP functional test.
Test: Regression test passed (Bug: 124052942)

c4e90da6d MBO: Move the WNM-Notification subtype definitions to common location
105b14f54 HS 2.0: Update the T&C Acceptance subtype value
65b487ae5 HS 2.0: Add QUIET=1 support for building hs20-osu-client
73f285dad Add FT-PSK to GET_CAPABILITY key_mgmt
6110753b1 nl80211: Clear PMKID add command message buffer
0fa33e05b nl80211: Clear connect command message buffer
b14e8ea1d nl80211: Request kernel to trim off payload of netlink requests from acks
789b48bb4 EAP peer: Clear temporary message buffers before freeing
8f99a3c26 Clear config item writing buffer before freeing it
a68e9b698 D-Bus: Fix P2P DeleteService dict iteration
0607346f1 D-Bus: Fix a memory leak in DeleteService handler
d05dda61d PEAP: Explicitly clear temporary keys from memory when using CMK
4e1cd3468 EAP-PEAP: Derive EMSK and use 128-octet derivation for MSK
d8c20ec59 DPP: Clear dpp_listen_freq on remain-on-channel failure
59fa20538 P2P: Allow the avoid channels for P2P discovery/negotiation
e34cd9f06 WNM: Fix WNM-Sleep Mode Request bounds checking
159a7fbde crl_reload_interval: Add CRL reloading support
83c860813 AP: Add wpa_psk_file reloading in runtime
ec5c39a55 AP: Allow identifying which passphrase station used with wpa_psk_file
b08c9ad0c AP: Expose PMK outside of wpa_auth module
89896c000 tests: Use python3 compatible print statement
bab493b90 tests: Use python3 compatible "except" statement
0dab47733 Write multi_ap_backhaul_sta to wpa_supplicant config
98251c6f2 dbus: Document more possible BSS/RSA/KeyMgmt values
1e591df06 Check supported types in wpas_mac_addr_rand_scan_set()
c85249aa1 Fix test compilation error related to sme_event_unprot_disconnect()
42d308635 SAE: Advertise Password Identifier use
59c693064 HS 2.0 server: Command line option to fetch the version information
2d1762fa4 HS 2.0 server: Alternative subrem updateNode for certificate credentials
d97cf2a11 HS 2.0 server: Use noMOUpdate in client certificate subrem
13a200a92 FILS: Remove notes about experimental implementation
86d4e0537 dbus: Expose support of SAE key management in BSS properties

Change-Id: I83ffca34ff5349c226db6215ff1ae35c3b7ab335
diff --git a/src/ap/acs.c b/src/ap/acs.c
index 6d4c041..3b45075 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -13,6 +13,7 @@
 #include "utils/common.h"
 #include "utils/list.h"
 #include "common/ieee802_11_defs.h"
+#include "common/hw_features_common.h"
 #include "common/wpa_ctrl.h"
 #include "drivers/driver.h"
 #include "hostapd.h"
@@ -362,7 +363,7 @@
 }
 
 
-static int acs_usable_ht40_chan(struct hostapd_channel_data *chan)
+static int acs_usable_ht40_chan(const struct hostapd_channel_data *chan)
 {
 	const int allowed[] = { 36, 44, 52, 60, 100, 108, 116, 124, 132, 149,
 				157, 184, 192 };
@@ -376,7 +377,7 @@
 }
 
 
-static int acs_usable_vht80_chan(struct hostapd_channel_data *chan)
+static int acs_usable_vht80_chan(const struct hostapd_channel_data *chan)
 {
 	const int allowed[] = { 36, 52, 100, 116, 132, 149 };
 	unsigned int i;
@@ -389,6 +390,19 @@
 }
 
 
+static int acs_usable_vht160_chan(const struct hostapd_channel_data *chan)
+{
+	const int allowed[] = { 36, 100 };
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(allowed); i++)
+		if (chan->chan == allowed[i])
+			return 1;
+
+	return 0;
+}
+
+
 static int acs_survey_is_sufficient(struct freq_survey *survey)
 {
 	if (!(survey->filled & SURVEY_HAS_NF)) {
@@ -565,6 +579,7 @@
 	long double factor, ideal_factor = 0;
 	int i, j;
 	int n_chans = 1;
+	u32 bw;
 	unsigned int k;
 
 	/* TODO: HT40- support */
@@ -579,16 +594,23 @@
 	    iface->conf->secondary_channel)
 		n_chans = 2;
 
-	if (iface->conf->ieee80211ac &&
-	    iface->conf->vht_oper_chwidth == 1)
-		n_chans = 4;
+	if (iface->conf->ieee80211ac) {
+		switch (iface->conf->vht_oper_chwidth) {
+		case VHT_CHANWIDTH_80MHZ:
+			n_chans = 4;
+			break;
+		case VHT_CHANWIDTH_160MHZ:
+			n_chans = 8;
+			break;
+		}
+	}
 
-	/* TODO: VHT80+80, VHT160. Update acs_adjust_vht_center_freq() too. */
+	bw = num_chan_to_bw(n_chans);
 
-	wpa_printf(MSG_DEBUG, "ACS: Survey analysis for selected bandwidth %d MHz",
-		   n_chans == 1 ? 20 :
-		   n_chans == 2 ? 40 :
-		   80);
+	/* TODO: VHT80+80. Update acs_adjust_vht_center_freq() too. */
+
+	wpa_printf(MSG_DEBUG,
+		   "ACS: Survey analysis for selected bandwidth %d MHz", bw);
 
 	for (i = 0; i < iface->current_mode->num_channels; i++) {
 		double total_weight;
@@ -596,12 +618,23 @@
 
 		chan = &iface->current_mode->channels[i];
 
-		if (chan->flag & HOSTAPD_CHAN_DISABLED)
+		/* Since in the current ACS implementation the first channel is
+		 * always a primary channel, skip channels not available as
+		 * primary until more sophisticated channel selection is
+		 * implemented. */
+		if (!chan_pri_allowed(chan))
 			continue;
 
 		if (!is_in_chanlist(iface, chan))
 			continue;
 
+		if (!chan_bw_allowed(chan, bw, 1, 1)) {
+			wpa_printf(MSG_DEBUG,
+				   "ACS: Channel %d: BW %u is not supported",
+				   chan->chan, bw);
+			continue;
+		}
+
 		/* HT40 on 5 GHz has a limited set of primary channels as per
 		 * 11n Annex J */
 		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
@@ -614,12 +647,24 @@
 		}
 
 		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
-		    iface->conf->ieee80211ac &&
-		    iface->conf->vht_oper_chwidth == 1 &&
-		    !acs_usable_vht80_chan(chan)) {
-			wpa_printf(MSG_DEBUG, "ACS: Channel %d: not allowed as primary channel for VHT80",
-				   chan->chan);
-			continue;
+		    iface->conf->ieee80211ac) {
+			if (iface->conf->vht_oper_chwidth ==
+			    VHT_CHANWIDTH_80MHZ &&
+			    !acs_usable_vht80_chan(chan)) {
+				wpa_printf(MSG_DEBUG,
+					   "ACS: Channel %d: not allowed as primary channel for VHT80",
+					   chan->chan);
+				continue;
+			}
+
+			if (iface->conf->vht_oper_chwidth ==
+			    VHT_CHANWIDTH_160MHZ &&
+			    !acs_usable_vht160_chan(chan)) {
+				wpa_printf(MSG_DEBUG,
+					   "ACS: Channel %d: not allowed as primary channel for VHT160",
+					   chan->chan);
+				continue;
+			}
 		}
 
 		factor = 0;
@@ -632,6 +677,13 @@
 			if (!adj_chan)
 				break;
 
+			if (!chan_bw_allowed(adj_chan, bw, 1, 0)) {
+				wpa_printf(MSG_DEBUG,
+					   "ACS: PRI Channel %d: secondary channel %d BW %u is not supported",
+					   chan->chan, adj_chan->chan, bw);
+				break;
+			}
+
 			if (acs_usable_chan(adj_chan)) {
 				factor += adj_chan->interference_factor;
 				total_weight += 1;
@@ -744,10 +796,14 @@
 	case VHT_CHANWIDTH_80MHZ:
 		offset = 6;
 		break;
+	case VHT_CHANWIDTH_160MHZ:
+		offset = 14;
+		break;
 	default:
 		/* TODO: How can this be calculated? Adjust
 		 * acs_find_ideal_chan() */
-		wpa_printf(MSG_INFO, "ACS: Only VHT20/40/80 is supported now");
+		wpa_printf(MSG_INFO,
+			   "ACS: Only VHT20/40/80/160 is supported now");
 		return;
 	}
 
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index f9b6f29..9611dc0 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -131,6 +131,15 @@
 	 * This can be enabled by default once the implementation has been fully
 	 * completed and tested with other implementations. */
 	bss->tls_flags = TLS_CONN_DISABLE_TLSv1_3;
+
+	bss->send_probe_response = 1;
+
+#ifdef CONFIG_HS20
+	bss->hs20_release = (HS20_VERSION >> 4) + 1;
+#endif /* CONFIG_HS20 */
+
+	/* Default to strict CRL checking. */
+	bss->check_crl_strict = 1;
 }
 
 
@@ -193,7 +202,6 @@
 	conf->beacon_int = 100;
 	conf->rts_threshold = -1; /* use driver default: 2347 */
 	conf->fragm_threshold = -1; /* user driver default: 2346 */
-	conf->send_probe_response = 1;
 	/* Set to invalid value means do not add Power Constraint IE */
 	conf->local_pwr_constraint = -1;
 
@@ -233,6 +241,9 @@
 	 * environments for the current frequency band in the country. */
 	conf->country[2] = ' ';
 
+	conf->rssi_reject_assoc_rssi = 0;
+	conf->rssi_reject_assoc_timeout = 30;
+
 	return conf;
 }
 
@@ -248,6 +259,12 @@
 {
 	FILE *f;
 	char buf[128], *pos;
+	const char *keyid;
+	char *context;
+	char *context2;
+	char *token;
+	char *name;
+	char *value;
 	int line = 0, ret = 0, len, ok;
 	u8 addr[ETH_ALEN];
 	struct hostapd_wpa_psk *psk;
@@ -277,9 +294,35 @@
 		if (buf[0] == '\0')
 			continue;
 
-		if (hwaddr_aton(buf, addr)) {
+		context = NULL;
+		keyid = NULL;
+		while ((token = str_token(buf, " ", &context))) {
+			if (!os_strchr(token, '='))
+				break;
+			context2 = NULL;
+			name = str_token(token, "=", &context2);
+			value = str_token(token, "", &context2);
+			if (!value)
+				value = "";
+			if (!os_strcmp(name, "keyid")) {
+				keyid = value;
+			} else {
+				wpa_printf(MSG_ERROR,
+					   "Unrecognized '%s=%s' on line %d in '%s'",
+					   name, value, line, fname);
+				ret = -1;
+				break;
+			}
+		}
+
+		if (ret == -1)
+			break;
+
+		if (!token)
+			token = "";
+		if (hwaddr_aton(token, addr)) {
 			wpa_printf(MSG_ERROR, "Invalid MAC address '%s' on "
-				   "line %d in '%s'", buf, line, fname);
+				   "line %d in '%s'", token, line, fname);
 			ret = -1;
 			break;
 		}
@@ -295,15 +338,14 @@
 		else
 			os_memcpy(psk->addr, addr, ETH_ALEN);
 
-		pos = buf + 17;
-		if (*pos == '\0') {
+		pos = str_token(buf, "", &context);
+		if (!pos) {
 			wpa_printf(MSG_ERROR, "No PSK on line %d in '%s'",
 				   line, fname);
 			os_free(psk);
 			ret = -1;
 			break;
 		}
-		pos++;
 
 		ok = 0;
 		len = os_strlen(pos);
@@ -322,6 +364,18 @@
 			break;
 		}
 
+		if (keyid) {
+			len = os_strlcpy(psk->keyid, keyid, sizeof(psk->keyid));
+			if ((size_t) len >= sizeof(psk->keyid)) {
+				wpa_printf(MSG_ERROR,
+					   "PSK keyid too long on line %d in '%s'",
+					   line, fname);
+				os_free(psk);
+				ret = -1;
+				break;
+			}
+		}
+
 		psk->next = ssid->wpa_psk;
 		ssid->wpa_psk = psk;
 	}
@@ -538,6 +592,7 @@
 	os_free(conf->ocsp_stapling_response_multi);
 	os_free(conf->dh_file);
 	os_free(conf->openssl_ciphers);
+	os_free(conf->openssl_ecdh_curves);
 	os_free(conf->pac_opaque_encr_key);
 	os_free(conf->eap_fast_a_id);
 	os_free(conf->eap_fast_a_id_info);
@@ -644,6 +699,7 @@
 		os_free(conf->hs20_operator_icon);
 	}
 	os_free(conf->subscr_remediation_url);
+	os_free(conf->hs20_sim_provisioning_url);
 	os_free(conf->t_c_filename);
 	os_free(conf->t_c_server_url);
 #endif /* CONFIG_HS20 */
@@ -1003,6 +1059,15 @@
 	}
 #endif /* CONFIG_MBO */
 
+#ifdef CONFIG_OCV
+	if (full_config && bss->ieee80211w == NO_MGMT_FRAME_PROTECTION &&
+	    bss->ocv) {
+		wpa_printf(MSG_ERROR,
+			   "OCV: PMF needs to be enabled whenever using OCV");
+		return -1;
+	}
+#endif /* CONFIG_OCV */
+
 	return 0;
 }
 
@@ -1146,3 +1211,26 @@
 		}
 	}
 }
+
+
+int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf)
+{
+	int with_id = 0, without_id = 0;
+	struct sae_password_entry *pw;
+
+	if (conf->ssid.wpa_passphrase)
+		without_id = 1;
+
+	for (pw = conf->sae_passwords; pw; pw = pw->next) {
+		if (pw->identifier)
+			with_id = 1;
+		else
+			without_id = 1;
+		if (with_id && without_id)
+			break;
+	}
+
+	if (with_id && !without_id)
+		return 2;
+	return with_id;
+}
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 778366d..6963df4 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -42,6 +42,7 @@
 #define MESH_CONF_SEC_AMPE BIT(2)
 	unsigned int security;
 	enum mfp_options ieee80211w;
+	int ocv;
 	unsigned int pairwise_cipher;
 	unsigned int group_cipher;
 	unsigned int mgmt_group_cipher;
@@ -122,6 +123,7 @@
 	int vlan_id; /* VLAN ID or -1 (VLAN_ID_WILDCARD) for wildcard entry */
 	struct vlan_description vlan_desc;
 	char ifname[IFNAMSIZ + 1];
+	char bridge[IFNAMSIZ + 1];
 	int configured;
 	int dynamic_vlan;
 #ifdef CONFIG_FULL_DYNAMIC_VLAN
@@ -132,6 +134,7 @@
 };
 
 #define PMK_LEN 32
+#define KEYID_LEN 32
 #define MIN_PASSPHRASE_LEN 8
 #define MAX_PASSPHRASE_LEN 63
 struct hostapd_sta_wpa_psk_short {
@@ -145,6 +148,7 @@
 struct hostapd_wpa_psk {
 	struct hostapd_wpa_psk *next;
 	int group;
+	char keyid[KEYID_LEN];
 	u8 psk[PMK_LEN];
 	u8 addr[ETH_ALEN];
 	u8 p2p_dev_addr[ETH_ALEN];
@@ -335,6 +339,9 @@
 	/* dot11AssociationSAQueryRetryTimeout (in TUs) */
 	int assoc_sa_query_retry_timeout;
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+	int ocv; /* Operating Channel Validation */
+#endif /* CONFIG_OCV */
 	enum {
 		PSK_RADIUS_IGNORED = 0,
 		PSK_RADIUS_ACCEPTED = 1,
@@ -384,12 +391,15 @@
 	char *private_key;
 	char *private_key_passwd;
 	int check_crl;
+	int check_crl_strict;
+	unsigned int crl_reload_interval;
 	unsigned int tls_session_lifetime;
 	unsigned int tls_flags;
 	char *ocsp_stapling_response;
 	char *ocsp_stapling_response_multi;
 	char *dh_file;
 	char *openssl_ciphers;
+	char *openssl_ecdh_curves;
 	u8 *pac_opaque_encr_key;
 	u8 *eap_fast_a_id;
 	size_t eap_fast_a_id_len;
@@ -557,6 +567,7 @@
 	int na_mcast_to_ucast;
 #ifdef CONFIG_HS20
 	int hs20;
+	int hs20_release;
 	int disable_dgaf;
 	u16 anqp_domain_id;
 	unsigned int hs20_oper_friendly_name_count;
@@ -596,6 +607,7 @@
 	unsigned int hs20_deauth_req_timeout;
 	char *subscr_remediation_url;
 	u8 subscr_remediation_method;
+	char *hs20_sim_provisioning_url;
 	char *t_c_filename;
 	u32 t_c_timestamp;
 	char *t_c_server_url;
@@ -686,6 +698,12 @@
 #endif /* CONFIG_OWE */
 
 	int coloc_intf_reporting;
+
+	u8 send_probe_response;
+
+#define BACKHAUL_BSS 1
+#define FRONTHAUL_BSS 2
+	int multi_ap; /* bitmap of BACKHAUL_BSS, FRONTHAUL_BSS */
 };
 
 /**
@@ -717,7 +735,6 @@
 	u16 beacon_int;
 	int rts_threshold;
 	int fragm_threshold;
-	u8 send_probe_response;
 	u8 channel;
 	u8 acs;
 	struct wpa_freq_range_list acs_ch_list;
@@ -829,12 +846,16 @@
 #ifdef CONFIG_IEEE80211AX
 	struct he_phy_capabilities_info he_phy_capab;
 	struct he_operation he_op;
+	struct ieee80211_he_mu_edca_parameter_set he_mu_edca;
 #endif /* CONFIG_IEEE80211AX */
 
 	/* VHT enable/disable config from CHAN_SWITCH */
 #define CH_SWITCH_VHT_ENABLED BIT(0)
 #define CH_SWITCH_VHT_DISABLED BIT(1)
 	unsigned int ch_switch_vht_config;
+
+	int rssi_reject_assoc_rssi;
+	int rssi_reject_assoc_timeout;
 };
 
 
@@ -862,5 +883,6 @@
 int hostapd_config_check(struct hostapd_config *conf, int full_config);
 void hostapd_set_security_params(struct hostapd_bss_config *bss,
 				 int full_config);
+int hostapd_sae_pw_id_in_use(struct hostapd_bss_config *conf);
 
 #endif /* HOSTAPD_CONFIG_H */
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 728d7f0..067cf86 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -176,7 +176,8 @@
 #endif /* CONFIG_HS20 */
 
 #ifdef CONFIG_MBO
-	if (hapd->conf->mbo_enabled || hapd->enable_oce) {
+	if (hapd->conf->mbo_enabled ||
+	    OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd)) {
 		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 ||
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index db93fde..d45ab84 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -356,4 +356,12 @@
 	return hapd->driver->stop_ap(hapd->drv_priv);
 }
 
+static inline int hostapd_drv_channel_info(struct hostapd_data *hapd,
+					   struct wpa_channel_info *ci)
+{
+	if (!hapd->driver || !hapd->driver->channel_info)
+		return -1;
+	return hapd->driver->channel_info(hapd->drv_priv, ci);
+}
+
 #endif /* AP_DRV_OPS */
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 95d004e..1bb3d9f 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -136,6 +136,7 @@
 #ifdef CONFIG_HS20
 	srv.subscr_remediation_url = conf->subscr_remediation_url;
 	srv.subscr_remediation_method = conf->subscr_remediation_method;
+	srv.hs20_sim_provisioning_url = conf->hs20_sim_provisioning_url;
 	srv.t_c_server_url = conf->t_c_server_url;
 #endif /* CONFIG_HS20 */
 	srv.erp = conf->eap_server_erp;
@@ -200,6 +201,16 @@
 
 		os_memset(&conf, 0, sizeof(conf));
 		conf.tls_session_lifetime = hapd->conf->tls_session_lifetime;
+		if (hapd->conf->crl_reload_interval > 0 &&
+		    hapd->conf->check_crl <= 0) {
+			wpa_printf(MSG_INFO,
+				   "Cannot enable CRL reload functionality - it depends on check_crl being set");
+		} else if (hapd->conf->crl_reload_interval > 0) {
+			conf.crl_reload_interval =
+				hapd->conf->crl_reload_interval;
+			wpa_printf(MSG_INFO,
+				   "Enabled CRL reload functionality");
+		}
 		conf.tls_flags = hapd->conf->tls_flags;
 		conf.event_cb = authsrv_tls_event;
 		conf.cb_ctx = hapd;
@@ -217,6 +228,7 @@
 		params.private_key_passwd = hapd->conf->private_key_passwd;
 		params.dh_file = hapd->conf->dh_file;
 		params.openssl_ciphers = hapd->conf->openssl_ciphers;
+		params.openssl_ecdh_curves = hapd->conf->openssl_ecdh_curves;
 		params.ocsp_stapling_response =
 			hapd->conf->ocsp_stapling_response;
 		params.ocsp_stapling_response_multi =
@@ -229,7 +241,8 @@
 		}
 
 		if (tls_global_set_verify(hapd->ssl_ctx,
-					  hapd->conf->check_crl)) {
+					  hapd->conf->check_crl,
+					  hapd->conf->check_crl_strict)) {
 			wpa_printf(MSG_ERROR, "Failed to enable check_crl");
 			authsrv_deinit(hapd);
 			return -1;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 59bd4af..3e62991 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -397,7 +397,8 @@
 #ifdef CONFIG_IEEE80211AX
 	if (hapd->iconf->ieee80211ax) {
 		buflen += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation);
+			3 + sizeof(struct ieee80211_he_operation) +
+			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set);
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -510,6 +511,7 @@
 	if (hapd->iconf->ieee80211ax) {
 		pos = hostapd_eid_he_capab(hapd, pos);
 		pos = hostapd_eid_he_operation(hapd, pos);
+		pos = hostapd_eid_he_mu_edca_parameter_set(hapd, pos);
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -767,7 +769,7 @@
 					    ie, ie_len, ssi_signal) > 0)
 			return;
 
-	if (!hapd->iconf->send_probe_response)
+	if (!hapd->conf->send_probe_response)
 		return;
 
 	if (ieee802_11_parse_elems(ie, ie_len, &elems, 0) == ParseFailed) {
@@ -1085,7 +1087,8 @@
 #ifdef CONFIG_IEEE80211AX
 	if (hapd->iconf->ieee80211ax) {
 		tail_len += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation);
+			3 + sizeof(struct ieee80211_he_operation) +
+			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set);
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1222,6 +1225,7 @@
 	if (hapd->iconf->ieee80211ax) {
 		tailpos = hostapd_eid_he_capab(hapd, tailpos);
 		tailpos = hostapd_eid_he_operation(hapd, tailpos);
+		tailpos = hostapd_eid_he_mu_edca_parameter_set(hapd, tailpos);
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1357,6 +1361,18 @@
 #endif /* CONFIG_HS20 */
 	params->multicast_to_unicast = hapd->conf->multicast_to_unicast;
 	params->pbss = hapd->conf->pbss;
+
+	if (hapd->conf->ftm_responder) {
+		if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_FTM_RESPONDER) {
+			params->ftm_responder = 1;
+			params->lci = hapd->iface->conf->lci;
+			params->civic = hapd->iface->conf->civic;
+		} else {
+			wpa_printf(MSG_WARNING,
+				   "Not configuring FTM responder as the driver doesn't advertise support for it");
+		}
+	}
+
 	return 0;
 }
 
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 21b813e..3128aed 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -207,6 +207,7 @@
 				      char *buf, size_t buflen)
 {
 	int len, res, ret, i;
+	const char *keyid;
 
 	if (!sta)
 		return 0;
@@ -341,6 +342,13 @@
 			len += ret;
 	}
 
+	keyid = ap_sta_wpa_get_keyid(hapd, sta);
+	if (keyid) {
+		ret = os_snprintf(buf + len, buflen - len, "keyid=%s\n", keyid);
+		if (!os_snprintf_error(buflen - len, ret))
+			len += ret;
+	}
+
 	return len;
 }
 
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 993dd19..79cd00f 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -142,18 +142,30 @@
 {
 	struct hostapd_channel_data *first_chan, *chan;
 	int i;
+	u32 bw = num_chan_to_bw(num_chans);
 
 	if (first_chan_idx + num_chans > mode->num_channels)
 		return 0;
 
 	first_chan = &mode->channels[first_chan_idx];
 
+	/* hostapd DFS implementation assumes the first channel as primary.
+	 * If it's not allowed to use the first channel as primary, decline the
+	 * whole channel range. */
+	if (!chan_pri_allowed(first_chan))
+		return 0;
+
 	for (i = 0; i < num_chans; i++) {
 		chan = dfs_get_chan_data(mode, first_chan->freq + i * 20,
 					 first_chan_idx);
 		if (!chan)
 			return 0;
 
+		/* HT 40 MHz secondary channel availability checked only for
+		 * primary channel */
+		if (!chan_bw_allowed(chan, bw, 1, !i))
+			return 0;
+
 		if (!dfs_channel_available(chan, skip_radar))
 			return 0;
 	}
@@ -197,7 +209,8 @@
 		/* Skip HT40/VHT incompatible channels */
 		if (iface->conf->ieee80211n &&
 		    iface->conf->secondary_channel &&
-		    !dfs_is_chan_allowed(chan, n_chans))
+		    (!dfs_is_chan_allowed(chan, n_chans) ||
+		     !(chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)))
 			continue;
 
 		/* Skip incompatible chandefs */
diff --git a/src/ap/dhcp_snoop.c b/src/ap/dhcp_snoop.c
index 6d8c2f4..ed37fc8 100644
--- a/src/ap/dhcp_snoop.c
+++ b/src/ap/dhcp_snoop.c
@@ -88,6 +88,15 @@
 		}
 	}
 
+	if (hapd->conf->disable_dgaf && is_broadcast_ether_addr(buf)) {
+		for (sta = hapd->sta_list; sta; sta = sta->next) {
+			if (!(sta->flags & WLAN_STA_AUTHORIZED))
+				continue;
+			x_snoop_mcast_to_ucast_convert_send(hapd, sta,
+							    (u8 *) buf, len);
+		}
+	}
+
 	if (msgtype == DHCPACK) {
 		if (b->your_ip == 0)
 			return;
@@ -124,15 +133,6 @@
 		}
 		sta->ipaddr = b->your_ip;
 	}
-
-	if (hapd->conf->disable_dgaf && is_broadcast_ether_addr(buf)) {
-		for (sta = hapd->sta_list; sta; sta = sta->next) {
-			if (!(sta->flags & WLAN_STA_AUTHORIZED))
-				continue;
-			x_snoop_mcast_to_ucast_convert_send(hapd, sta,
-							    (u8 *) buf, len);
-		}
-	}
 }
 
 
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 4ec044e..149f389 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -505,9 +505,9 @@
 }
 
 
-static void hostapd_dpp_set_configurator(struct hostapd_data *hapd,
-					 struct dpp_authentication *auth,
-					 const char *cmd)
+static int hostapd_dpp_set_configurator(struct hostapd_data *hapd,
+					struct dpp_authentication *auth,
+					const char *cmd)
 {
 	const char *pos, *end;
 	struct dpp_configuration *conf_sta = NULL, *conf_ap = NULL;
@@ -521,7 +521,7 @@
 	char *group_id = NULL;
 
 	if (!cmd)
-		return;
+		return 0;
 
 	wpa_printf(MSG_DEBUG, "DPP: Set configurator parameters: %s", cmd);
 	pos = os_strstr(cmd, " ssid=");
@@ -618,10 +618,12 @@
 				conf_ap->akm = DPP_AKM_PSK;
 			if (psk_set) {
 				os_memcpy(conf_ap->psk, psk, PMK_LEN);
-			} else {
+			} else if (pass_len > 0) {
 				conf_ap->passphrase = os_strdup(pass);
 				if (!conf_ap->passphrase)
 					goto fail;
+			} else {
+				goto fail;
 			}
 		} else if (os_strstr(cmd, " conf=ap-dpp")) {
 			conf_ap->akm = DPP_AKM_DPP;
@@ -663,13 +665,15 @@
 	auth->conf_ap = conf_ap;
 	auth->conf = conf;
 	os_free(group_id);
-	return;
+	return 0;
 
 fail:
-	wpa_printf(MSG_DEBUG, "DPP: Failed to set configurator parameters");
+	wpa_msg(hapd->msg_ctx, MSG_INFO,
+		"DPP: Failed to set configurator parameters");
 	dpp_configuration_free(conf_sta);
 	dpp_configuration_free(conf_ap);
 	os_free(group_id);
+	return -1;
 }
 
 
@@ -842,7 +846,11 @@
 	if (!hapd->dpp_auth)
 		goto fail;
 	hostapd_dpp_set_testing_options(hapd, hapd->dpp_auth);
-	hostapd_dpp_set_configurator(hapd, hapd->dpp_auth, cmd);
+	if (hostapd_dpp_set_configurator(hapd, hapd->dpp_auth, cmd) < 0) {
+		dpp_auth_deinit(hapd->dpp_auth);
+		hapd->dpp_auth = NULL;
+		goto fail;
+	}
 
 	hapd->dpp_auth->neg_freq = neg_freq;
 
@@ -967,8 +975,12 @@
 		return;
 	}
 	hostapd_dpp_set_testing_options(hapd, hapd->dpp_auth);
-	hostapd_dpp_set_configurator(hapd, hapd->dpp_auth,
-				     hapd->dpp_configurator_params);
+	if (hostapd_dpp_set_configurator(hapd, hapd->dpp_auth,
+					 hapd->dpp_configurator_params) < 0) {
+		dpp_auth_deinit(hapd->dpp_auth);
+		hapd->dpp_auth = NULL;
+		return;
+	}
 	os_memcpy(hapd->dpp_auth->peer_mac_addr, src, ETH_ALEN);
 
 	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_TX "dst=" MACSTR
@@ -1892,9 +1904,9 @@
 		return -1;
 
 	curve = get_param(cmd, " curve=");
-	hostapd_dpp_set_configurator(hapd, auth, cmd);
-
-	if (dpp_configurator_own_config(auth, curve, 1) == 0) {
+	hostapd_dpp_set_testing_options(hapd, auth);
+	if (hostapd_dpp_set_configurator(hapd, auth, cmd) == 0 &&
+	    dpp_configurator_own_config(auth, curve, 1) == 0) {
 		hostapd_dpp_handle_config_obj(hapd, auth);
 		ret = 0;
 	}
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 1135aea..54be3b5 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -38,6 +38,7 @@
 #include "mbo_ap.h"
 #include "dpp_hostapd.h"
 #include "fils_hlp.h"
+#include "neighbor_db.h"
 
 
 #ifdef CONFIG_FILS
@@ -739,9 +740,12 @@
 void hostapd_event_ch_switch(struct hostapd_data *hapd, int freq, int ht,
 			     int offset, int width, int cf1, int cf2)
 {
+	/* TODO: If OCV is enabled deauth STAs that don't perform a SA Query */
+
 #ifdef NEED_AP_MLME
 	int channel, chwidth, is_dfs;
 	u8 seg0_idx = 0, seg1_idx = 0;
+	size_t i;
 
 	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_INFO,
@@ -824,6 +828,9 @@
 		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_CSA_FINISHED
 			"freq=%d dfs=%d", freq, is_dfs);
 	}
+
+	for (i = 0; i < hapd->iface->num_bss; i++)
+		hostapd_neighbor_set_own_report(hapd->iface->bss[i]);
 #endif /* NEED_AP_MLME */
 }
 
@@ -1072,19 +1079,23 @@
 	struct sta_info *sta;
 	size_t plen __maybe_unused;
 	u16 fc;
+	u8 *action __maybe_unused;
 
-	if (drv_mgmt->frame_len < 24 + 1)
+	if (drv_mgmt->frame_len < IEEE80211_HDRLEN + 2 + 1)
 		return;
 
-	plen = drv_mgmt->frame_len - 24 - 1;
+	plen = drv_mgmt->frame_len - IEEE80211_HDRLEN - 1;
 
 	mgmt = (struct ieee80211_mgmt *) drv_mgmt->frame;
 	fc = le_to_host16(mgmt->frame_control);
 	if (WLAN_FC_GET_STYPE(fc) != WLAN_FC_STYPE_ACTION)
 		return; /* handled by the driver */
 
-	wpa_printf(MSG_DEBUG, "RX_ACTION cat %d action plen %d",
-		   mgmt->u.action.category, (int) plen);
+	action = (u8 *) &mgmt->u.action.u;
+	wpa_printf(MSG_DEBUG, "RX_ACTION category %u action %u sa " MACSTR
+		   " da " MACSTR " plen %d",
+		   mgmt->u.action.category, *action,
+		   MAC2STR(mgmt->sa), MAC2STR(mgmt->da), (int) plen);
 
 	sta = ap_get_sta(hapd, mgmt->sa);
 	if (sta == NULL) {
@@ -1100,10 +1111,7 @@
 #endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
 	if (mgmt->u.action.category == WLAN_ACTION_SA_QUERY && plen >= 4) {
-		ieee802_11_sa_query_action(
-			hapd, mgmt->sa,
-			mgmt->u.action.u.sa_query_resp.action,
-			mgmt->u.action.u.sa_query_resp.trans_id);
+		ieee802_11_sa_query_action(hapd, mgmt, drv_mgmt->frame_len);
 	}
 #endif /* CONFIG_IEEE80211W */
 #ifdef CONFIG_WNM_AP
@@ -1721,6 +1729,11 @@
 				hostapd_reconfig_encryption(hapd);
 			hapd->reenable_beacon = 1;
 			ieee802_11_set_beacon(hapd);
+#ifdef NEED_AP_MLME
+		} else if (hapd->disabled && hapd->iface->cac_started) {
+			wpa_printf(MSG_DEBUG, "DFS: restarting pending CAC");
+			hostapd_handle_dfs(hapd->iface);
+#endif /* NEED_AP_MLME */
 		}
 		break;
 	case EVENT_INTERFACE_DISABLED:
diff --git a/src/ap/eap_user_db.c b/src/ap/eap_user_db.c
index fab307f..a510ee3 100644
--- a/src/ap/eap_user_db.c
+++ b/src/ap/eap_user_db.c
@@ -92,7 +92,7 @@
 		} else if (os_strcmp(col[i], "remediation") == 0 && argv[i]) {
 			user->remediation = strlen(argv[i]) > 0;
 		} else if (os_strcmp(col[i], "t_c_timestamp") == 0 && argv[i]) {
-			user->t_c_timestamp = strtol(argv[i], 0, 10);
+			user->t_c_timestamp = strtol(argv[i], NULL, 10);
 		}
 	}
 
@@ -139,6 +139,7 @@
 	struct hostapd_eap_user *user = NULL;
 	char id_str[256], cmd[300];
 	size_t i;
+	int res;
 
 	if (identity_len >= sizeof(id_str)) {
 		wpa_printf(MSG_DEBUG, "%s: identity len too big: %d >= %d",
@@ -174,6 +175,7 @@
 	if (hapd->tmp_eap_user.identity == NULL)
 		return NULL;
 	os_memcpy(hapd->tmp_eap_user.identity, identity, identity_len);
+	hapd->tmp_eap_user.identity_len = identity_len;
 
 	if (sqlite3_open(hapd->conf->eap_user_sqlite, &db)) {
 		wpa_printf(MSG_INFO, "DB: Failed to open database %s: %s",
@@ -182,9 +184,12 @@
 		return NULL;
 	}
 
-	os_snprintf(cmd, sizeof(cmd),
-		    "SELECT * FROM users WHERE identity='%s' AND phase2=%d;",
-		    id_str, phase2);
+	res = os_snprintf(cmd, sizeof(cmd),
+			  "SELECT * FROM users WHERE identity='%s' AND phase2=%d;",
+			  id_str, phase2);
+	if (os_snprintf_error(sizeof(cmd), res))
+		goto fail;
+
 	wpa_printf(MSG_DEBUG, "DB: %s", cmd);
 	if (sqlite3_exec(db, cmd, get_user_cb, &hapd->tmp_eap_user, NULL) !=
 	    SQLITE_OK) {
@@ -214,6 +219,7 @@
 		}
 	}
 
+fail:
 	sqlite3_close(db);
 
 	return user;
diff --git a/src/ap/fils_hlp.c b/src/ap/fils_hlp.c
index 2a359ab..6da514a 100644
--- a/src/ap/fils_hlp.c
+++ b/src/ap/fils_hlp.c
@@ -580,6 +580,19 @@
 	u8 *tmp, *tmp_pos;
 	int ret = 0;
 
+	if (sta->fils_pending_assoc_req &&
+	    eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta)) {
+		/* Do not process FILS HLP request again if the station
+		 * retransmits (Re)Association Request frame before the previous
+		 * HLP response has either been received or timed out. */
+		wpa_printf(MSG_DEBUG,
+			   "FILS: Do not relay another HLP request from "
+			   MACSTR
+			   " before processing of the already pending one has been completed",
+			   MAC2STR(sta->addr));
+		return 1;
+	}
+
 	/* Old DHCPDISCOVER is not needed anymore, if it was still pending */
 	wpabuf_free(sta->hlp_dhcp_discover);
 	sta->hlp_dhcp_discover = NULL;
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 117ee08..342585f 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -176,8 +176,27 @@
 }
 
 
+static int hostapd_iface_conf_changed(struct hostapd_config *newconf,
+				      struct hostapd_config *oldconf)
+{
+	size_t i;
+
+	if (newconf->num_bss != oldconf->num_bss)
+		return 1;
+
+	for (i = 0; i < newconf->num_bss; i++) {
+		if (os_strcmp(newconf->bss[i]->iface,
+			      oldconf->bss[i]->iface) != 0)
+			return 1;
+	}
+
+	return 0;
+}
+
+
 int hostapd_reload_config(struct hostapd_iface *iface)
 {
+	struct hapd_interfaces *interfaces = iface->interfaces;
 	struct hostapd_data *hapd = iface->bss[0];
 	struct hostapd_config *newconf, *oldconf;
 	size_t j;
@@ -200,6 +219,35 @@
 	hostapd_clear_old(iface);
 
 	oldconf = hapd->iconf;
+	if (hostapd_iface_conf_changed(newconf, oldconf)) {
+		char *fname;
+		int res;
+
+		wpa_printf(MSG_DEBUG,
+			   "Configuration changes include interface/BSS modification - force full disable+enable sequence");
+		fname = os_strdup(iface->config_fname);
+		if (!fname) {
+			hostapd_config_free(newconf);
+			return -1;
+		}
+		hostapd_remove_iface(interfaces, hapd->conf->iface);
+		iface = hostapd_init(interfaces, fname);
+		os_free(fname);
+		hostapd_config_free(newconf);
+		if (!iface) {
+			wpa_printf(MSG_ERROR,
+				   "Failed to initialize interface on config reload");
+			return -1;
+		}
+		iface->interfaces = interfaces;
+		interfaces->iface[interfaces->count] = iface;
+		interfaces->count++;
+		res = hostapd_enable_iface(iface);
+		if (res < 0)
+			wpa_printf(MSG_ERROR,
+				   "Failed to enable interface on config reload");
+		return res;
+	}
 	iface->conf = newconf;
 
 	for (j = 0; j < iface->num_bss; j++) {
@@ -1620,127 +1668,6 @@
 
 #endif /* CONFIG_FST */
 
-
-#ifdef NEED_AP_MLME
-static enum nr_chan_width hostapd_get_nr_chan_width(struct hostapd_data *hapd,
-						    int ht, int vht)
-{
-	if (!ht && !vht)
-		return NR_CHAN_WIDTH_20;
-	if (!hapd->iconf->secondary_channel)
-		return NR_CHAN_WIDTH_20;
-	if (!vht || hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_USE_HT)
-		return NR_CHAN_WIDTH_40;
-	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_80MHZ)
-		return NR_CHAN_WIDTH_80;
-	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_160MHZ)
-		return NR_CHAN_WIDTH_160;
-	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_80P80MHZ)
-		return NR_CHAN_WIDTH_80P80;
-	return NR_CHAN_WIDTH_20;
-}
-#endif /* NEED_AP_MLME */
-
-
-static void hostapd_set_own_neighbor_report(struct hostapd_data *hapd)
-{
-#ifdef NEED_AP_MLME
-	u16 capab = hostapd_own_capab_info(hapd);
-	int ht = hapd->iconf->ieee80211n && !hapd->conf->disable_11n;
-	int vht = hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac;
-	struct wpa_ssid_value ssid;
-	u8 channel, op_class;
-	u8 center_freq1_idx = 0, center_freq2_idx = 0;
-	enum nr_chan_width width;
-	u32 bssid_info;
-	struct wpabuf *nr;
-
-	if (!(hapd->conf->radio_measurements[0] &
-	      WLAN_RRM_CAPS_NEIGHBOR_REPORT))
-		return;
-
-	bssid_info = 3; /* AP is reachable */
-	bssid_info |= NEI_REP_BSSID_INFO_SECURITY; /* "same as the AP" */
-	bssid_info |= NEI_REP_BSSID_INFO_KEY_SCOPE; /* "same as the AP" */
-
-	if (capab & WLAN_CAPABILITY_SPECTRUM_MGMT)
-		bssid_info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT;
-
-	bssid_info |= NEI_REP_BSSID_INFO_RM; /* RRM is supported */
-
-	if (hapd->conf->wmm_enabled) {
-		bssid_info |= NEI_REP_BSSID_INFO_QOS;
-
-		if (hapd->conf->wmm_uapsd &&
-		    (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD))
-			bssid_info |= NEI_REP_BSSID_INFO_APSD;
-	}
-
-	if (ht) {
-		bssid_info |= NEI_REP_BSSID_INFO_HT |
-			NEI_REP_BSSID_INFO_DELAYED_BA;
-
-		/* VHT bit added in IEEE P802.11-REVmc/D4.3 */
-		if (vht)
-			bssid_info |= NEI_REP_BSSID_INFO_VHT;
-	}
-
-	/* TODO: Set NEI_REP_BSSID_INFO_MOBILITY_DOMAIN if MDE is set */
-
-	if (ieee80211_freq_to_channel_ext(hapd->iface->freq,
-					  hapd->iconf->secondary_channel,
-					  hapd->iconf->vht_oper_chwidth,
-					  &op_class, &channel) ==
-	    NUM_HOSTAPD_MODES)
-		return;
-	width = hostapd_get_nr_chan_width(hapd, ht, vht);
-	if (vht) {
-		center_freq1_idx = hapd->iconf->vht_oper_centr_freq_seg0_idx;
-		if (width == NR_CHAN_WIDTH_80P80)
-			center_freq2_idx =
-				hapd->iconf->vht_oper_centr_freq_seg1_idx;
-	} else if (ht) {
-		ieee80211_freq_to_chan(hapd->iface->freq +
-				       10 * hapd->iconf->secondary_channel,
-				       &center_freq1_idx);
-	}
-
-	ssid.ssid_len = hapd->conf->ssid.ssid_len;
-	os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len);
-
-	/*
-	 * Neighbor Report element size = BSSID + BSSID info + op_class + chan +
-	 * phy type + wide bandwidth channel subelement.
-	 */
-	nr = wpabuf_alloc(ETH_ALEN + 4 + 1 + 1 + 1 + 5);
-	if (!nr)
-		return;
-
-	wpabuf_put_data(nr, hapd->own_addr, ETH_ALEN);
-	wpabuf_put_le32(nr, bssid_info);
-	wpabuf_put_u8(nr, op_class);
-	wpabuf_put_u8(nr, channel);
-	wpabuf_put_u8(nr, ieee80211_get_phy_type(hapd->iface->freq, ht, vht));
-
-	/*
-	 * Wide Bandwidth Channel subelement may be needed to allow the
-	 * receiving STA to send packets to the AP. See IEEE P802.11-REVmc/D5.0
-	 * Figure 9-301.
-	 */
-	wpabuf_put_u8(nr, WNM_NEIGHBOR_WIDE_BW_CHAN);
-	wpabuf_put_u8(nr, 3);
-	wpabuf_put_u8(nr, width);
-	wpabuf_put_u8(nr, center_freq1_idx);
-	wpabuf_put_u8(nr, center_freq2_idx);
-
-	hostapd_neighbor_set(hapd, hapd->own_addr, &ssid, nr, hapd->iconf->lci,
-			     hapd->iconf->civic, hapd->iconf->stationary_ap);
-
-	wpabuf_free(nr);
-#endif /* NEED_AP_MLME */
-}
-
-
 #ifdef CONFIG_OWE
 
 static int hostapd_owe_iface_iter(struct hostapd_iface *iface, void *ctx)
@@ -2037,7 +1964,7 @@
 		iface->interfaces->terminate_on_error--;
 
 	for (j = 0; j < iface->num_bss; j++)
-		hostapd_set_own_neighbor_report(iface->bss[j]);
+		hostapd_neighbor_set_own_report(iface->bss[j]);
 
 	return 0;
 
@@ -2682,7 +2609,7 @@
 	if (conf == NULL) {
 		 wpa_printf(MSG_ERROR, "%s: Failed to allocate memory for "
 				"configuration", __func__);
-		return NULL;
+		 return NULL;
 	}
 
 	if (driver) {
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 28b3a1c..85e63d3 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -14,6 +14,13 @@
 #include "ap_config.h"
 #include "drivers/driver.h"
 
+#define OCE_STA_CFON_ENABLED(hapd) \
+	((hapd->conf->oce & OCE_STA_CFON) && \
+	 (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_OCE_STA_CFON))
+#define OCE_AP_ENABLED(hapd) \
+	((hapd->conf->oce & OCE_AP) && \
+	 (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_OCE_AP))
+
 struct wpa_ctrl_dst;
 struct radius_server_data;
 struct upnp_wps_device_sm;
@@ -324,11 +331,6 @@
 
 #ifdef CONFIG_MBO
 	unsigned int mbo_assoc_disallow;
-	/**
-	 * enable_oce - Enable OCE if it is enabled by user and device also
-	 *		supports OCE.
-	 */
-	u8 enable_oce;
 #endif /* CONFIG_MBO */
 
 	struct dl_list nr_db;
diff --git a/src/ap/hs20.c b/src/ap/hs20.c
index 98d016d..532580e 100644
--- a/src/ap/hs20.c
+++ b/src/ap/hs20.c
@@ -25,17 +25,20 @@
 	if (!hapd->conf->hs20)
 		return eid;
 	*eid++ = WLAN_EID_VENDOR_SPECIFIC;
-	*eid++ = 7;
+	*eid++ = hapd->conf->hs20_release < 2 ? 5 : 7;
 	WPA_PUT_BE24(eid, OUI_WFA);
 	eid += 3;
 	*eid++ = HS20_INDICATION_OUI_TYPE;
-	conf = HS20_VERSION; /* Release Number */
-	conf |= HS20_ANQP_DOMAIN_ID_PRESENT;
+	conf = (hapd->conf->hs20_release - 1) << 4; /* Release Number */
+	if (hapd->conf->hs20_release >= 2)
+		conf |= HS20_ANQP_DOMAIN_ID_PRESENT;
 	if (hapd->conf->disable_dgaf)
 		conf |= HS20_DGAF_DISABLED;
 	*eid++ = conf;
-	WPA_PUT_LE16(eid, hapd->conf->anqp_domain_id);
-	eid += 2;
+	if (hapd->conf->hs20_release >= 2) {
+		WPA_PUT_LE16(eid, hapd->conf->anqp_domain_id);
+		eid += 2;
+	}
 
 	return eid;
 }
@@ -84,6 +87,10 @@
 			capab |= WPA_CAPABILITY_MFPR;
 	}
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+	if (hapd->conf->ocv)
+		capab |= WPA_CAPABILITY_OCVC;
+#endif /* CONFIG_OCV */
 	WPA_PUT_LE16(eid, capab);
 	eid += 2;
 
@@ -184,13 +191,14 @@
 {
 	struct wpabuf *buf;
 	int ret;
-	size_t url_len = os_strlen(url);
+	size_t url_len;
 
 	if (!url) {
 		wpa_printf(MSG_INFO, "HS 2.0: No T&C Server URL available");
 		return -1;
 	}
 
+	url_len = os_strlen(url);
 	if (5 + url_len > 255) {
 		wpa_printf(MSG_INFO,
 			   "HS 2.0: Too long T&C Server URL for WNM-Notification: '%s'",
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 5279abc..9d3d990 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -229,9 +229,6 @@
 {
 	int pri_chan, sec_chan;
 
-	if (!iface->conf->secondary_channel)
-		return 1; /* HT40 not used */
-
 	pri_chan = iface->conf->channel;
 	sec_chan = pri_chan + iface->conf->secondary_channel * 4;
 
@@ -697,30 +694,25 @@
 static int hostapd_is_usable_chan(struct hostapd_iface *iface,
 				  int channel, int primary)
 {
-	int i;
 	struct hostapd_channel_data *chan;
 
 	if (!iface->current_mode)
 		return 0;
 
-	for (i = 0; i < iface->current_mode->num_channels; i++) {
-		chan = &iface->current_mode->channels[i];
-		if (chan->chan != channel)
-			continue;
+	chan = hw_get_channel_chan(iface->current_mode, channel, NULL);
+	if (!chan)
+		return 0;
 
-		if (!(chan->flag & HOSTAPD_CHAN_DISABLED))
-			return 1;
+	if ((primary && chan_pri_allowed(chan)) ||
+	    (!primary && !(chan->flag & HOSTAPD_CHAN_DISABLED)))
+		return 1;
 
-		wpa_printf(MSG_DEBUG,
-			   "%schannel [%i] (%i) is disabled for use in AP mode, flags: 0x%x%s%s",
-			   primary ? "" : "Configured HT40 secondary ",
-			   i, chan->chan, chan->flag,
-			   chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
-			   chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
-	}
-
-	wpa_printf(MSG_INFO, "Channel %d (%s) not allowed for AP mode",
-		   channel, primary ? "primary" : "secondary");
+	wpa_printf(MSG_INFO,
+		   "Channel %d (%s) not allowed for AP mode, flags: 0x%x%s%s",
+		   channel, primary ? "primary" : "secondary",
+		   chan->flag,
+		   chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
+		   chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
 	return 0;
 }
 
@@ -728,6 +720,12 @@
 static int hostapd_is_usable_chans(struct hostapd_iface *iface)
 {
 	int secondary_chan;
+	struct hostapd_channel_data *pri_chan;
+
+	pri_chan = hw_get_channel_chan(iface->current_mode,
+				       iface->conf->channel, NULL);
+	if (!pri_chan)
+		return 0;
 
 	if (!hostapd_is_usable_chan(iface, iface->conf->channel, 1))
 		return 0;
@@ -742,13 +740,15 @@
 
 	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
 	secondary_chan = iface->conf->channel + 4;
-	if (hostapd_is_usable_chan(iface, secondary_chan, 0)) {
+	if (hostapd_is_usable_chan(iface, secondary_chan, 0) &&
+	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
 		iface->conf->secondary_channel = 1;
 		return 1;
 	}
 
 	secondary_chan = iface->conf->channel - 4;
-	if (hostapd_is_usable_chan(iface, secondary_chan, 0)) {
+	if (hostapd_is_usable_chan(iface, secondary_chan, 0) &&
+	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
 		iface->conf->secondary_channel = -1;
 		return 1;
 	}
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 90788de..376bbd8 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -21,6 +21,7 @@
 #include "common/ieee802_11_common.h"
 #include "common/wpa_ctrl.h"
 #include "common/sae.h"
+#include "common/ocv.h"
 #include "radius/radius.h"
 #include "radius/radius_client.h"
 #include "p2p/p2p.h"
@@ -62,6 +63,22 @@
 		       int *is_pub);
 #endif /* CONFIG_FILS */
 
+
+u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid)
+{
+	u8 multi_ap_val = 0;
+
+	if (!hapd->conf->multi_ap)
+		return eid;
+	if (hapd->conf->multi_ap & BACKHAUL_BSS)
+		multi_ap_val |= MULTI_AP_BACKHAUL_BSS;
+	if (hapd->conf->multi_ap & FRONTHAUL_BSS)
+		multi_ap_val |= MULTI_AP_FRONTHAUL_BSS;
+
+	return eid + add_multi_ap_ie(eid, 9, multi_ap_val);
+}
+
+
 u8 * hostapd_eid_supp_rates(struct hostapd_data *hapd, u8 *eid)
 {
 	u8 *pos = eid;
@@ -1743,7 +1760,8 @@
 
 
 static void handle_auth(struct hostapd_data *hapd,
-			const struct ieee80211_mgmt *mgmt, size_t len)
+			const struct ieee80211_mgmt *mgmt, size_t len,
+			int rssi)
 {
 	u16 auth_alg, auth_transaction, status_code;
 	u16 resp = WLAN_STATUS_SUCCESS;
@@ -1925,6 +1943,7 @@
 	sta = ap_get_sta(hapd, mgmt->sa);
 	if (sta) {
 		sta->flags &= ~WLAN_STA_PENDING_FILS_ERP;
+		sta->ft_over_ds = 0;
 		if ((fc & WLAN_FC_RETRY) &&
 		    sta->last_seq_ctrl != WLAN_INVALID_MGMT_SEQ &&
 		    sta->last_seq_ctrl == seq_ctrl &&
@@ -1973,6 +1992,9 @@
 	}
 	sta->last_seq_ctrl = seq_ctrl;
 	sta->last_subtype = WLAN_FC_STYPE_AUTH;
+#ifdef CONFIG_MBO
+	sta->auth_rssi = rssi;
+#endif /* CONFIG_MBO */
 
 	res = ieee802_11_set_radius_info(
 		hapd, sta, res, session_timeout, acct_interim_interval,
@@ -2210,6 +2232,57 @@
 	return WLAN_STATUS_SUCCESS;
 }
 
+static u16 check_multi_ap(struct hostapd_data *hapd, struct sta_info *sta,
+			  const u8 *multi_ap_ie, size_t multi_ap_len)
+{
+	u8 multi_ap_value = 0;
+
+	sta->flags &= ~WLAN_STA_MULTI_AP;
+
+	if (!hapd->conf->multi_ap)
+		return WLAN_STATUS_SUCCESS;
+
+	if (multi_ap_ie) {
+		const u8 *multi_ap_subelem;
+
+		multi_ap_subelem = get_ie(multi_ap_ie + 4,
+					  multi_ap_len - 4,
+					  MULTI_AP_SUB_ELEM_TYPE);
+		if (multi_ap_subelem && multi_ap_subelem[1] == 1) {
+			multi_ap_value = multi_ap_subelem[2];
+		} else {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO,
+				       "Multi-AP IE has missing or invalid Multi-AP subelement");
+			return WLAN_STATUS_INVALID_IE;
+		}
+	}
+
+	if (multi_ap_value == MULTI_AP_BACKHAUL_STA)
+		sta->flags |= WLAN_STA_MULTI_AP;
+
+	if ((hapd->conf->multi_ap & BACKHAUL_BSS) &&
+	    multi_ap_value == MULTI_AP_BACKHAUL_STA)
+		return WLAN_STATUS_SUCCESS;
+
+	if (hapd->conf->multi_ap & FRONTHAUL_BSS) {
+		if (multi_ap_value == MULTI_AP_BACKHAUL_STA) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO,
+				       "Backhaul STA tries to associate with fronthaul-only BSS");
+			return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+		}
+		return WLAN_STATUS_SUCCESS;
+	}
+
+	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		       HOSTAPD_LEVEL_INFO,
+		       "Non-Multi-AP STA tries to associate with backhaul-only BSS");
+	return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+}
+
 
 static u16 copy_supp_rates(struct hostapd_data *hapd, struct sta_info *sta,
 			   struct ieee802_11_elems *elems)
@@ -2466,6 +2539,11 @@
 	resp = copy_supp_rates(hapd, sta, &elems);
 	if (resp != WLAN_STATUS_SUCCESS)
 		return resp;
+
+	resp = check_multi_ap(hapd, sta, elems.multi_ap, elems.multi_ap_len);
+	if (resp != WLAN_STATUS_SUCCESS)
+		return resp;
+
 #ifdef CONFIG_IEEE80211N
 	resp = copy_sta_ht_capab(hapd, sta, elems.ht_capabilities);
 	if (resp != WLAN_STATUS_SUCCESS)
@@ -2485,6 +2563,10 @@
 		if (resp != WLAN_STATUS_SUCCESS)
 			return resp;
 
+		resp = copy_sta_vht_oper(hapd, sta, elems.vht_operation);
+		if (resp != WLAN_STATUS_SUCCESS)
+			return resp;
+
 		resp = set_sta_vht_opmode(hapd, sta, elems.vht_opmode_notif);
 		if (resp != WLAN_STATUS_SUCCESS)
 			return resp;
@@ -2713,10 +2795,20 @@
 #ifdef CONFIG_HS20
 	wpabuf_free(sta->hs20_ie);
 	if (elems.hs20 && elems.hs20_len > 4) {
+		int release;
+
 		sta->hs20_ie = wpabuf_alloc_copy(elems.hs20 + 4,
 						 elems.hs20_len - 4);
-	} else
+		release = ((elems.hs20[4] >> 4) & 0x0f) + 1;
+		if (release >= 2 && !wpa_auth_uses_mfp(sta->wpa_sm)) {
+			wpa_printf(MSG_DEBUG,
+				   "HS 2.0: PMF not negotiated by release %d station "
+				   MACSTR, release, MAC2STR(sta->addr));
+			return WLAN_STATUS_ROBUST_MGMT_FRAME_POLICY_VIOLATION;
+		}
+	} else {
 		sta->hs20_ie = NULL;
+	}
 
 	wpabuf_free(sta->roaming_consortium);
 	if (elems.roaming_cons_sel)
@@ -2747,6 +2839,35 @@
 	}
 #endif /* CONFIG_MBO */
 
+#if defined(CONFIG_FILS) && defined(CONFIG_OCV)
+	if (wpa_auth_uses_ocv(sta->wpa_sm) &&
+	    (sta->auth_alg == WLAN_AUTH_FILS_SK ||
+	     sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
+	     sta->auth_alg == WLAN_AUTH_FILS_PK)) {
+		struct wpa_channel_info ci;
+		int tx_chanwidth;
+		int tx_seg1_idx;
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info to validate received OCI in FILS (Re)Association Request frame");
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+
+		if (get_sta_tx_parameters(sta->wpa_sm,
+					  channel_width_to_int(ci.chanwidth),
+					  ci.seg1_idx, &tx_chanwidth,
+					  &tx_seg1_idx) < 0)
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+		if (ocv_verify_tx_params(elems.oci, elems.oci_len, &ci,
+					 tx_chanwidth, tx_seg1_idx) != 0) {
+			wpa_printf(MSG_WARNING, "FILS: %s", ocv_errorstr);
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+	}
+#endif /* CONFIG_FILS && CONFIG_OCV */
+
 	ap_copy_sta_supp_op_classes(sta, elems.supp_op_classes,
 				    elems.supp_op_classes_len);
 
@@ -2791,7 +2912,7 @@
 
 
 static int add_associated_sta(struct hostapd_data *hapd,
-			      struct sta_info *sta)
+			      struct sta_info *sta, int reassoc)
 {
 	struct ieee80211_ht_capabilities ht_cap;
 	struct ieee80211_vht_capabilities vht_cap;
@@ -2807,14 +2928,36 @@
 	 * Skip this if the STA has already completed FT reassociation and the
 	 * TK has been configured since the TX/RX PN must not be reset to 0 for
 	 * the same key.
+	 *
+	 * FT-over-the-DS has a special case where the STA entry (and as such,
+	 * the TK) has not yet been configured to the driver depending on which
+	 * driver interface is used. For that case, allow add-STA operation to
+	 * be used (instead of set-STA). This is needed to allow mac80211-based
+	 * drivers to accept the STA parameter configuration. Since this is
+	 * after a new FT-over-DS exchange, a new TK has been derived, so key
+	 * reinstallation is not a concern for this case.
 	 */
+	wpa_printf(MSG_DEBUG, "Add associated STA " MACSTR
+		   " (added_unassoc=%d auth_alg=%u ft_over_ds=%u reassoc=%d authorized=%d ft_tk=%d fils_tk=%d)",
+		   MAC2STR(sta->addr), sta->added_unassoc, sta->auth_alg,
+		   sta->ft_over_ds, reassoc,
+		   !!(sta->flags & WLAN_STA_AUTHORIZED),
+		   wpa_auth_sta_ft_tk_already_set(sta->wpa_sm),
+		   wpa_auth_sta_fils_tk_already_set(sta->wpa_sm));
+
 	if (!sta->added_unassoc &&
 	    (!(sta->flags & WLAN_STA_AUTHORIZED) ||
+	     (reassoc && sta->ft_over_ds && sta->auth_alg == WLAN_AUTH_FT) ||
 	     (!wpa_auth_sta_ft_tk_already_set(sta->wpa_sm) &&
 	      !wpa_auth_sta_fils_tk_already_set(sta->wpa_sm)))) {
 		hostapd_drv_sta_remove(hapd, sta->addr);
 		wpa_auth_sm_event(sta->wpa_sm, WPA_DRV_STA_REMOVED);
 		set = 0;
+
+		 /* Do not allow the FT-over-DS exception to be used more than
+		  * once per authentication exchange to guarantee a new TK is
+		  * used here */
+		sta->ft_over_ds = 0;
 	}
 
 #ifdef CONFIG_IEEE80211N
@@ -2860,7 +3003,7 @@
 
 static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 			   const u8 *addr, u16 status_code, int reassoc,
-			   const u8 *ies, size_t ies_len)
+			   const u8 *ies, size_t ies_len, int rssi)
 {
 	int send_len;
 	u8 *buf;
@@ -2905,6 +3048,16 @@
 	/* Extended supported rates */
 	p = hostapd_eid_ext_supp_rates(hapd, p);
 
+#ifdef CONFIG_MBO
+	if (status_code == WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS &&
+	    rssi != 0) {
+		int delta = hapd->iconf->rssi_reject_assoc_rssi - rssi;
+
+		p = hostapd_eid_mbo_rssi_assoc_rej(hapd, p, buf + buflen - p,
+						   delta);
+	}
+#endif /* CONFIG_MBO */
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (sta && status_code == WLAN_STATUS_SUCCESS) {
 		/* IEEE 802.11r: Mobility Domain Information, Fast BSS
@@ -2922,7 +3075,8 @@
 #endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_OWE
-	if (sta && (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE))
+	if (sta && status_code == WLAN_STATUS_SUCCESS &&
+	    (hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE))
 		p = wpa_auth_write_assoc_resp_owe(sta->wpa_sm, p,
 						  buf + buflen - p,
 						  ies, ies_len);
@@ -2995,6 +3149,9 @@
 	}
 #endif /* CONFIG_WPS */
 
+	if (sta && (sta->flags & WLAN_STA_MULTI_AP))
+		p = hostapd_eid_multi_ap(hapd, p);
+
 #ifdef CONFIG_P2P
 	if (sta && sta->p2p_ie && hapd->p2p_group) {
 		struct wpabuf *p2p_resp_ie;
@@ -3069,7 +3226,7 @@
 
 #ifdef CONFIG_OWE
 	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) &&
-	    sta && sta->owe_ecdh &&
+	    sta && sta->owe_ecdh && status_code == WLAN_STATUS_SUCCESS &&
 	    wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE) {
 		struct wpabuf *pub;
 
@@ -3172,7 +3329,7 @@
 	reply_res = send_assoc_resp(hapd, sta, sta->addr, WLAN_STATUS_SUCCESS,
 				    sta->fils_pending_assoc_is_reassoc,
 				    sta->fils_pending_assoc_req,
-				    sta->fils_pending_assoc_req_len);
+				    sta->fils_pending_assoc_req_len, 0);
 	os_free(sta->fils_pending_assoc_req);
 	sta->fils_pending_assoc_req = NULL;
 	sta->fils_pending_assoc_req_len = 0;
@@ -3209,7 +3366,7 @@
 
 static void handle_assoc(struct hostapd_data *hapd,
 			 const struct ieee80211_mgmt *mgmt, size_t len,
-			 int reassoc)
+			 int reassoc, int rssi)
 {
 	u16 capab_info, listen_interval, seq_ctrl, fc;
 	u16 resp = WLAN_STATUS_SUCCESS, reply_res;
@@ -3392,6 +3549,14 @@
 		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 		goto fail;
 	}
+
+	if (hapd->iconf->rssi_reject_assoc_rssi && rssi &&
+	    rssi < hapd->iconf->rssi_reject_assoc_rssi &&
+	    (sta->auth_rssi == 0 ||
+	     sta->auth_rssi < hapd->iconf->rssi_reject_assoc_rssi)) {
+		resp = WLAN_STATUS_DENIED_POOR_CHANNEL_CONDITIONS;
+		goto fail;
+	}
 #endif /* CONFIG_MBO */
 
 	/*
@@ -3549,10 +3714,24 @@
 	 *    issues with processing other non-Data Class 3 frames during this
 	 *    window.
 	 */
-	if (resp == WLAN_STATUS_SUCCESS && sta && add_associated_sta(hapd, sta))
+	if (resp == WLAN_STATUS_SUCCESS && sta &&
+	    add_associated_sta(hapd, sta, reassoc))
 		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 
 #ifdef CONFIG_FILS
+	if (sta && delay_assoc && resp == WLAN_STATUS_SUCCESS &&
+	    eloop_is_timeout_registered(fils_hlp_timeout, hapd, sta) &&
+	    sta->fils_pending_assoc_req) {
+		/* Do not reschedule fils_hlp_timeout in case the station
+		 * retransmits (Re)Association Request frame while waiting for
+		 * the previously started FILS HLP wait, so that the timeout can
+		 * be determined from the first pending attempt. */
+		wpa_printf(MSG_DEBUG,
+			   "FILS: Continue waiting for HLP processing before sending (Re)Association Response frame to "
+			   MACSTR, MAC2STR(sta->addr));
+		os_free(tmp);
+		return;
+	}
 	if (sta) {
 		eloop_cancel_timeout(fils_hlp_timeout, hapd, sta);
 		os_free(sta->fils_pending_assoc_req);
@@ -3577,7 +3756,7 @@
 #endif /* CONFIG_FILS */
 
 	reply_res = send_assoc_resp(hapd, sta, mgmt->sa, resp, reassoc, pos,
-				    left);
+				    left, rssi);
 	os_free(tmp);
 
 	/*
@@ -3732,9 +3911,7 @@
 		return 0;
 	}
 
-	ieee802_11_sa_query_action(hapd, mgmt->sa,
-				   mgmt->u.action.u.sa_query_resp.action,
-				   mgmt->u.action.u.sa_query_resp.trans_id);
+	ieee802_11_sa_query_action(hapd, mgmt, len);
 	return 1;
 }
 
@@ -3752,9 +3929,9 @@
 			 unsigned int freq)
 {
 	struct sta_info *sta;
-	sta = ap_get_sta(hapd, mgmt->sa);
+	u8 *action __maybe_unused;
 
-	if (len < IEEE80211_HDRLEN + 1) {
+	if (len < IEEE80211_HDRLEN + 2 + 1) {
 		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "handle_action - too short payload (len=%lu)",
@@ -3762,6 +3939,14 @@
 		return 0;
 	}
 
+	action = (u8 *) &mgmt->u.action.u;
+	wpa_printf(MSG_DEBUG, "RX_ACTION category %u action %u sa " MACSTR
+		   " da " MACSTR " len %d freq %u",
+		   mgmt->u.action.category, *action,
+		   MAC2STR(mgmt->sa), MAC2STR(mgmt->da), (int) len, freq);
+
+	sta = ap_get_sta(hapd, mgmt->sa);
+
 	if (mgmt->u.action.category != WLAN_ACTION_PUBLIC &&
 	    (sta == NULL || !(sta->flags & WLAN_STA_ASSOC))) {
 		wpa_printf(MSG_DEBUG, "IEEE 802.11: Ignored Action "
@@ -4011,17 +4196,17 @@
 	switch (stype) {
 	case WLAN_FC_STYPE_AUTH:
 		wpa_printf(MSG_DEBUG, "mgmt::auth");
-		handle_auth(hapd, mgmt, len);
+		handle_auth(hapd, mgmt, len, ssi_signal);
 		ret = 1;
 		break;
 	case WLAN_FC_STYPE_ASSOC_REQ:
 		wpa_printf(MSG_DEBUG, "mgmt::assoc_req");
-		handle_assoc(hapd, mgmt, len, 0);
+		handle_assoc(hapd, mgmt, len, 0, ssi_signal);
 		ret = 1;
 		break;
 	case WLAN_FC_STYPE_REASSOC_REQ:
 		wpa_printf(MSG_DEBUG, "mgmt::reassoc_req");
-		handle_assoc(hapd, mgmt, len, 1);
+		handle_assoc(hapd, mgmt, len, 1, ssi_signal);
 		ret = 1;
 		break;
 	case WLAN_FC_STYPE_DISASSOC:
@@ -4233,7 +4418,7 @@
 		sta->flags |= WLAN_STA_WDS;
 	}
 
-	if (sta->flags & WLAN_STA_WDS) {
+	if (sta->flags & (WLAN_STA_WDS | WLAN_STA_MULTI_AP)) {
 		int ret;
 		char ifname_wds[IFNAMSIZ + 1];
 
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 2f3b4da..5082226 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -59,6 +59,7 @@
 u8 * hostapd_eid_txpower_envelope(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_he_capab(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_he_operation(struct hostapd_data *hapd, u8 *eid);
+u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid);
 
 int hostapd_ht_operation_update(struct hostapd_iface *iface);
 void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
@@ -80,6 +81,8 @@
 void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta);
 u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *vht_capab);
+u16 copy_sta_vht_oper(struct hostapd_data *hapd, struct sta_info *sta,
+		      const u8 *vht_oper);
 u16 set_sta_vht_opmode(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *vht_opmode);
 void hostapd_tx_status(struct hostapd_data *hapd, const u8 *addr,
@@ -91,8 +94,8 @@
 u8 * hostapd_eid_assoc_comeback_time(struct hostapd_data *hapd,
 				     struct sta_info *sta, u8 *eid);
 void ieee802_11_sa_query_action(struct hostapd_data *hapd,
-				const u8 *sa, const u8 action_type,
-				const u8 *trans_id);
+				const struct ieee80211_mgmt *mgmt,
+				size_t len);
 u8 * hostapd_eid_interworking(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_adv_proto(struct hostapd_data *hapd, u8 *eid);
 u8 * hostapd_eid_roaming_consortium(struct hostapd_data *hapd, u8 *eid);
@@ -120,6 +123,9 @@
 
 u8 hostapd_mbo_ie_len(struct hostapd_data *hapd);
 
+u8 * hostapd_eid_mbo_rssi_assoc_rej(struct hostapd_data *hapd, u8 *eid,
+				    size_t len, int delta);
+
 #else /* CONFIG_MBO */
 
 static inline u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid,
@@ -166,4 +172,7 @@
 			       char **identity, char **radius_cui,
 			       int is_probe_req);
 
+int get_tx_parameters(struct sta_info *sta, int ap_max_chanwidth,
+		      int ap_seg1_idx, int *bandwidth, int *seg1_idx);
+
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index 5cb7fb1..931d4d0 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -289,6 +289,9 @@
 			return HOSTAPD_ACL_ACCEPT;
 		};
 
+		if (hapd->conf->ssid.dynamic_vlan == DYNAMIC_VLAN_DISABLED)
+			vlan_id = NULL;
+
 		/* Check whether ACL cache has an entry for this station */
 		res = hostapd_acl_cache_get(hapd, addr, session_timeout,
 					    acct_interim_interval, vlan_id, psk,
@@ -516,7 +519,6 @@
 	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;
@@ -574,12 +576,10 @@
 			cache->acct_interim_interval = 0;
 		}
 
-		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);
+		if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED)
+			cache->vlan_id.notempty = !!radius_msg_get_vlanid(
+				msg, &cache->vlan_id.untagged,
+				MAX_NUM_TAGGED_VLAN, cache->vlan_id.tagged);
 
 		decode_tunnel_passwords(hapd, shared_secret, shared_secret_len,
 					msg, req, cache);
diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index 1a8d469..0721358 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -86,3 +86,34 @@
 
 	return pos;
 }
+
+
+u8 * hostapd_eid_he_mu_edca_parameter_set(struct hostapd_data *hapd, u8 *eid)
+{
+	struct ieee80211_he_mu_edca_parameter_set *edca;
+	u8 *pos;
+	size_t i;
+
+	pos = (u8 *) &hapd->iface->conf->he_mu_edca;
+	for (i = 0; i < sizeof(*edca); i++) {
+		if (pos[i])
+			break;
+	}
+	if (i == sizeof(*edca))
+		return eid; /* no MU EDCA Parameters configured */
+
+	pos = eid;
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 1 + sizeof(*edca);
+	*pos++ = WLAN_EID_EXT_HE_MU_EDCA_PARAMS;
+
+	edca = (struct ieee80211_he_mu_edca_parameter_set *) pos;
+	os_memcpy(edca, &hapd->iface->conf->he_mu_edca, sizeof(*edca));
+
+	wpa_hexdump(MSG_DEBUG, "HE: MU EDCA Parameter Set element",
+		    pos, sizeof(*edca));
+
+	pos += sizeof(*edca);
+
+	return pos;
+}
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index c481399..d70d6c1 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -10,10 +10,12 @@
 
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
+#include "common/ocv.h"
 #include "hostapd.h"
 #include "sta_info.h"
 #include "ap_config.h"
 #include "ap_drv_ops.h"
+#include "wpa_auth.h"
 #include "ieee802_11.h"
 
 
@@ -49,7 +51,12 @@
 void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
 				  const u8 *addr, const u8 *trans_id)
 {
-	struct ieee80211_mgmt mgmt;
+#ifdef CONFIG_OCV
+	struct sta_info *sta;
+#endif /* CONFIG_OCV */
+	struct ieee80211_mgmt *mgmt;
+	u8 *oci_ie = NULL;
+	u8 oci_ie_len = 0;
 	u8 *end;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Request to "
@@ -57,19 +64,61 @@
 	wpa_hexdump(MSG_DEBUG, "IEEE 802.11: SA Query Transaction ID",
 		    trans_id, WLAN_SA_QUERY_TR_ID_LEN);
 
-	os_memset(&mgmt, 0, sizeof(mgmt));
-	mgmt.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
-					  WLAN_FC_STYPE_ACTION);
-	os_memcpy(mgmt.da, addr, ETH_ALEN);
-	os_memcpy(mgmt.sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt.bssid, hapd->own_addr, ETH_ALEN);
-	mgmt.u.action.category = WLAN_ACTION_SA_QUERY;
-	mgmt.u.action.u.sa_query_req.action = WLAN_SA_QUERY_REQUEST;
-	os_memcpy(mgmt.u.action.u.sa_query_req.trans_id, trans_id,
+#ifdef CONFIG_OCV
+	sta = ap_get_sta(hapd, addr);
+	if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) {
+		struct wpa_channel_info ci;
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info for OCI element in SA Query Request");
+			return;
+		}
+
+		oci_ie_len = OCV_OCI_EXTENDED_LEN;
+		oci_ie = os_zalloc(oci_ie_len);
+		if (!oci_ie) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to allocate buffer for OCI element in SA Query Request");
+			return;
+		}
+
+		if (ocv_insert_extended_oci(&ci, oci_ie) < 0) {
+			os_free(oci_ie);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
+
+	mgmt = os_zalloc(sizeof(*mgmt) + oci_ie_len);
+	if (!mgmt) {
+		wpa_printf(MSG_DEBUG,
+			   "Failed to allocate buffer for SA Query Response frame");
+		os_free(oci_ie);
+		return;
+	}
+
+	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+					   WLAN_FC_STYPE_ACTION);
+	os_memcpy(mgmt->da, addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	mgmt->u.action.category = WLAN_ACTION_SA_QUERY;
+	mgmt->u.action.u.sa_query_req.action = WLAN_SA_QUERY_REQUEST;
+	os_memcpy(mgmt->u.action.u.sa_query_req.trans_id, trans_id,
 		  WLAN_SA_QUERY_TR_ID_LEN);
-	end = mgmt.u.action.u.sa_query_req.trans_id + WLAN_SA_QUERY_TR_ID_LEN;
-	if (hostapd_drv_send_mlme(hapd, &mgmt, end - (u8 *) &mgmt, 0) < 0)
+	end = mgmt->u.action.u.sa_query_req.variable;
+#ifdef CONFIG_OCV
+	if (oci_ie_len > 0) {
+		os_memcpy(end, oci_ie, oci_ie_len);
+		end += oci_ie_len;
+	}
+#endif /* CONFIG_OCV */
+	if (hostapd_drv_send_mlme(hapd, mgmt, end - (u8 *) mgmt, 0) < 0)
 		wpa_printf(MSG_INFO, "ieee802_11_send_sa_query_req: send failed");
+
+	os_free(mgmt);
+	os_free(oci_ie);
 }
 
 
@@ -77,7 +126,9 @@
 					  const u8 *sa, const u8 *trans_id)
 {
 	struct sta_info *sta;
-	struct ieee80211_mgmt resp;
+	struct ieee80211_mgmt *resp;
+	u8 *oci_ie = NULL;
+	u8 oci_ie_len = 0;
 	u8 *end;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Received SA Query Request from "
@@ -92,30 +143,115 @@
 		return;
 	}
 
+#ifdef CONFIG_OCV
+	if (wpa_auth_uses_ocv(sta->wpa_sm)) {
+		struct wpa_channel_info ci;
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info for OCI element in SA Query Response");
+			return;
+		}
+
+		oci_ie_len = OCV_OCI_EXTENDED_LEN;
+		oci_ie = os_zalloc(oci_ie_len);
+		if (!oci_ie) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to allocate buffer for for OCI element in SA Query Response");
+			return;
+		}
+
+		if (ocv_insert_extended_oci(&ci, oci_ie) < 0) {
+			os_free(oci_ie);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
+
+	resp = os_zalloc(sizeof(*resp) + oci_ie_len);
+	if (!resp) {
+		wpa_printf(MSG_DEBUG,
+			   "Failed to allocate buffer for SA Query Response frame");
+		os_free(oci_ie);
+		return;
+	}
+
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Response to "
 		   MACSTR, MAC2STR(sa));
 
-	os_memset(&resp, 0, sizeof(resp));
-	resp.frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
-					  WLAN_FC_STYPE_ACTION);
-	os_memcpy(resp.da, sa, ETH_ALEN);
-	os_memcpy(resp.sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(resp.bssid, hapd->own_addr, ETH_ALEN);
-	resp.u.action.category = WLAN_ACTION_SA_QUERY;
-	resp.u.action.u.sa_query_req.action = WLAN_SA_QUERY_RESPONSE;
-	os_memcpy(resp.u.action.u.sa_query_req.trans_id, trans_id,
+	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+					   WLAN_FC_STYPE_ACTION);
+	os_memcpy(resp->da, sa, ETH_ALEN);
+	os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN);
+	resp->u.action.category = WLAN_ACTION_SA_QUERY;
+	resp->u.action.u.sa_query_req.action = WLAN_SA_QUERY_RESPONSE;
+	os_memcpy(resp->u.action.u.sa_query_req.trans_id, trans_id,
 		  WLAN_SA_QUERY_TR_ID_LEN);
-	end = resp.u.action.u.sa_query_req.trans_id + WLAN_SA_QUERY_TR_ID_LEN;
-	if (hostapd_drv_send_mlme(hapd, &resp, end - (u8 *) &resp, 0) < 0)
+	end = resp->u.action.u.sa_query_req.variable;
+#ifdef CONFIG_OCV
+	if (oci_ie_len > 0) {
+		os_memcpy(end, oci_ie, oci_ie_len);
+		end += oci_ie_len;
+	}
+#endif /* CONFIG_OCV */
+	if (hostapd_drv_send_mlme(hapd, resp, end - (u8 *) resp, 0) < 0)
 		wpa_printf(MSG_INFO, "ieee80211_mgmt_sa_query_request: send failed");
+
+	os_free(resp);
+	os_free(oci_ie);
 }
 
 
-void ieee802_11_sa_query_action(struct hostapd_data *hapd, const u8 *sa,
-				const u8 action_type, const u8 *trans_id)
+void ieee802_11_sa_query_action(struct hostapd_data *hapd,
+				const struct ieee80211_mgmt *mgmt,
+				size_t len)
 {
 	struct sta_info *sta;
 	int i;
+	const u8 *sa = mgmt->sa;
+	const u8 action_type = mgmt->u.action.u.sa_query_resp.action;
+	const u8 *trans_id = mgmt->u.action.u.sa_query_resp.trans_id;
+
+	sta = ap_get_sta(hapd, sa);
+
+#ifdef CONFIG_OCV
+	if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) {
+		struct ieee802_11_elems elems;
+		struct wpa_channel_info ci;
+		int tx_chanwidth;
+		int tx_seg1_idx;
+		size_t ies_len;
+		const u8 *ies;
+
+		ies = mgmt->u.action.u.sa_query_resp.variable;
+		ies_len = len - (ies - (u8 *) mgmt);
+		if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) ==
+		    ParseFailed) {
+			wpa_printf(MSG_DEBUG,
+				   "SA Query: Failed to parse elements");
+			return;
+		}
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info to validate received OCI in SA Query Action frame");
+			return;
+		}
+
+		if (get_sta_tx_parameters(sta->wpa_sm,
+					  channel_width_to_int(ci.chanwidth),
+					  ci.seg1_idx, &tx_chanwidth,
+					  &tx_seg1_idx) < 0)
+			return;
+
+		if (ocv_verify_tx_params(elems.oci, elems.oci_len, &ci,
+					 tx_chanwidth, tx_seg1_idx) != 0) {
+			wpa_printf(MSG_WARNING, "%s", ocv_errorstr);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
 
 	if (action_type == WLAN_SA_QUERY_REQUEST) {
 		ieee802_11_send_sa_query_resp(hapd, sa, trans_id);
@@ -135,7 +271,6 @@
 
 	/* MLME-SAQuery.confirm */
 
-	sta = ap_get_sta(hapd, sa);
 	if (sta == NULL || sta->sa_query_trans_id == NULL) {
 		wpa_printf(MSG_DEBUG, "IEEE 802.11: No matching STA with "
 			   "pending SA Query request found");
@@ -237,6 +372,21 @@
 			*pos |= 0x01;
 #endif /* CONFIG_FILS */
 		break;
+	case 10: /* Bits 80-87 */
+#ifdef CONFIG_SAE
+		if (hapd->conf->wpa &&
+		    wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt)) {
+			int in_use = hostapd_sae_pw_id_in_use(hapd->conf);
+
+			if (in_use)
+				*pos |= 0x02; /* Bit 81 - SAE Password
+					       * Identifiers In Use */
+			if (in_use == 2)
+				*pos |= 0x04; /* Bit 82 - SAE Password
+					       * Identifiers Used Exclusively */
+		}
+#endif /* CONFIG_SAE */
+		break;
 	}
 }
 
@@ -276,6 +426,12 @@
 	     !wpa_key_mgmt_fils(hapd->conf->wpa_key_mgmt)) && len < 10)
 		len = 10;
 #endif /* CONFIG_FILS */
+#ifdef CONFIG_SAE
+	if (len < 11 && hapd->conf->wpa &&
+	    wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) &&
+	    hostapd_sae_pw_id_in_use(hapd->conf))
+		len = 11;
+#endif /* CONFIG_SAE */
 	if (len < hapd->iface->extended_capa_len)
 		len = hapd->iface->extended_capa_len;
 	if (len == 0)
@@ -547,12 +703,29 @@
 
 #ifdef CONFIG_MBO
 
+u8 * hostapd_eid_mbo_rssi_assoc_rej(struct hostapd_data *hapd, u8 *eid,
+				    size_t len, int delta)
+{
+	u8 mbo[4];
+
+	mbo[0] = OCE_ATTR_ID_RSSI_BASED_ASSOC_REJECT;
+	mbo[1] = 2;
+	/* Delta RSSI */
+	mbo[2] = delta;
+	/* Retry delay */
+	mbo[3] = hapd->iconf->rssi_reject_assoc_timeout;
+
+	return eid + mbo_add_ie(eid, len, mbo, 4);
+}
+
+
 u8 * hostapd_eid_mbo(struct hostapd_data *hapd, u8 *eid, size_t len)
 {
 	u8 mbo[9], *mbo_pos = mbo;
 	u8 *pos = eid;
 
-	if (!hapd->conf->mbo_enabled && !hapd->enable_oce)
+	if (!hapd->conf->mbo_enabled &&
+	    !OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd))
 		return eid;
 
 	if (hapd->conf->mbo_enabled) {
@@ -568,12 +741,11 @@
 		*mbo_pos++ = hapd->mbo_assoc_disallow;
 	}
 
-	if (hapd->enable_oce & (OCE_AP | OCE_STA_CFON)) {
+	if (OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd)) {
 		u8 ctrl;
 
 		ctrl = OCE_RELEASE;
-		if ((hapd->enable_oce & (OCE_AP | OCE_STA_CFON)) ==
-		    OCE_STA_CFON)
+		if (OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd))
 			ctrl |= OCE_IS_STA_CFON;
 
 		*mbo_pos++ = OCE_ATTR_ID_CAPA_IND;
@@ -591,7 +763,8 @@
 {
 	u8 len;
 
-	if (!hapd->conf->mbo_enabled && !hapd->enable_oce)
+	if (!hapd->conf->mbo_enabled &&
+	    !OCE_STA_CFON_ENABLED(hapd) && !OCE_AP_ENABLED(hapd))
 		return 0;
 
 	/*
@@ -603,7 +776,7 @@
 		len += 3 + (hapd->mbo_assoc_disallow ? 3 : 0);
 
 	/* OCE capability indication attribute (3) */
-	if (hapd->enable_oce & (OCE_AP | OCE_STA_CFON))
+	if (OCE_STA_CFON_ENABLED(hapd) || OCE_AP_ENABLED(hapd))
 		len += 3;
 
 	return len;
@@ -751,3 +924,71 @@
 
 	return pos;
 }
+
+
+#ifdef CONFIG_OCV
+int get_tx_parameters(struct sta_info *sta, int ap_max_chanwidth,
+		      int ap_seg1_idx, int *bandwidth, int *seg1_idx)
+{
+	int ht_40mhz = 0;
+	int vht_80p80 = 0;
+	int requested_bw;
+
+	if (sta->ht_capabilities)
+		ht_40mhz = !!(sta->ht_capabilities->ht_capabilities_info &
+			      HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET);
+
+	if (sta->vht_operation) {
+		struct ieee80211_vht_operation *oper = sta->vht_operation;
+
+		/*
+		 * If a VHT Operation element was present, use it to determine
+		 * the supported channel bandwidth.
+		 */
+		if (oper->vht_op_info_chwidth == 0) {
+			requested_bw = ht_40mhz ? 40 : 20;
+		} else if (oper->vht_op_info_chan_center_freq_seg1_idx == 0) {
+			requested_bw = 80;
+		} else {
+			int diff;
+
+			requested_bw = 160;
+			diff = abs((int)
+				   oper->vht_op_info_chan_center_freq_seg0_idx -
+				   (int)
+				   oper->vht_op_info_chan_center_freq_seg1_idx);
+			vht_80p80 = oper->vht_op_info_chan_center_freq_seg1_idx
+				!= 0 &&	diff > 16;
+		}
+	} else if (sta->vht_capabilities) {
+		struct ieee80211_vht_capabilities *capab;
+		int vht_chanwidth;
+
+		capab = sta->vht_capabilities;
+
+		/*
+		 * If only the VHT Capabilities element is present (e.g., for
+		 * normal clients), use it to determine the supported channel
+		 * bandwidth.
+		 */
+		vht_chanwidth = capab->vht_capabilities_info &
+			VHT_CAP_SUPP_CHAN_WIDTH_MASK;
+		vht_80p80 = capab->vht_capabilities_info &
+			VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ;
+
+		/* TODO: Also take into account Extended NSS BW Support field */
+		requested_bw = vht_chanwidth ? 160 : 80;
+	} else {
+		requested_bw = ht_40mhz ? 40 : 20;
+	}
+
+	*bandwidth = requested_bw < ap_max_chanwidth ?
+		requested_bw : ap_max_chanwidth;
+
+	*seg1_idx = 0;
+	if (ap_seg1_idx && vht_80p80)
+		*seg1_idx = ap_seg1_idx;
+
+	return 0;
+}
+#endif /* CONFIG_OCV */
diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c
index 8d06620..54ee080 100644
--- a/src/ap/ieee802_11_vht.c
+++ b/src/ap/ieee802_11_vht.c
@@ -357,6 +357,29 @@
 }
 
 
+u16 copy_sta_vht_oper(struct hostapd_data *hapd, struct sta_info *sta,
+		      const u8 *vht_oper)
+{
+	if (!vht_oper) {
+		os_free(sta->vht_operation);
+		sta->vht_operation = NULL;
+		return WLAN_STATUS_SUCCESS;
+	}
+
+	if (!sta->vht_operation) {
+		sta->vht_operation =
+			os_zalloc(sizeof(struct ieee80211_vht_operation));
+		if (!sta->vht_operation)
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	os_memcpy(sta->vht_operation, vht_oper,
+		  sizeof(struct ieee80211_vht_operation));
+
+	return WLAN_STATUS_SUCCESS;
+}
+
+
 u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta,
 			const u8 *ie, size_t len)
 {
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 985f8b7..a56c82e 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / IEEE 802.1X-2004 Authenticator
- * Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2019, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -682,7 +682,8 @@
 
 #ifdef CONFIG_HS20
 	if (hapd->conf->hs20) {
-		u8 ver = 1; /* Release 2 */
+		u8 ver = hapd->conf->hs20_release - 1;
+
 		if (!radius_msg_add_wfa(
 			    msg, RADIUS_VENDOR_ATTR_WFA_HS20_AP_VERSION,
 			    &ver, 1)) {
@@ -1741,6 +1742,45 @@
 }
 
 
+#ifndef CONFIG_NO_VLAN
+static int ieee802_1x_update_vlan(struct radius_msg *msg,
+				  struct hostapd_data *hapd,
+				  struct sta_info *sta)
+{
+	struct vlan_description vlan_desc;
+
+	os_memset(&vlan_desc, 0, sizeof(vlan_desc));
+	vlan_desc.notempty = !!radius_msg_get_vlanid(msg, &vlan_desc.untagged,
+						     MAX_NUM_TAGGED_VLAN,
+						     vlan_desc.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 %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);
+		return -1;
+	}
+
+	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,
+			       HOSTAPD_LEVEL_INFO,
+			       "authentication server did not include required VLAN ID in Access-Accept");
+		return -1;
+	}
+
+	return ap_sta_set_vlan(hapd, sta, &vlan_desc);
+}
+#endif /* CONFIG_NO_VLAN */
+
+
 /**
  * ieee802_1x_receive_auth - Process RADIUS frames from Authentication Server
  * @msg: RADIUS response message
@@ -1763,12 +1803,6 @@
 	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) {
@@ -1833,56 +1867,21 @@
 	switch (hdr->code) {
 	case RADIUS_CODE_ACCESS_ACCEPT:
 #ifndef CONFIG_NO_VLAN
-		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 %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;
-		}
-
-		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,
-				       HOSTAPD_LEVEL_INFO, "authentication "
-				       "server did not include required VLAN "
-				       "ID in Access-Accept");
-			break;
-		}
-#endif /* CONFIG_NO_VLAN */
-
-		if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0)
+		if (hapd->conf->ssid.dynamic_vlan != DYNAMIC_VLAN_DISABLED &&
+		    ieee802_1x_update_vlan(msg, hapd, sta) < 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;
+#endif /* CONFIG_NO_VLAN */
 
 		sta->session_timeout_set = !!session_timeout_set;
 		os_get_reltime(&sta->session_timeout);
@@ -2595,6 +2594,7 @@
 	struct os_reltime diff;
 	const char *name1;
 	const char *name2;
+	char *identity_buf = NULL;
 
 	if (sm == NULL)
 		return 0;
@@ -2710,6 +2710,14 @@
 
 	/* dot1xAuthSessionStatsTable */
 	os_reltime_age(&sta->acct_session_start, &diff);
+	if (sm->eap && !sm->identity) {
+		const u8 *id;
+		size_t id_len;
+
+		id = eap_get_identity(sm->eap, &id_len);
+		if (id)
+			identity_buf = dup_binstr(id, id_len);
+	}
 	ret = os_snprintf(buf + len, buflen - len,
 			  /* TODO: dot1xAuthSessionOctetsRx */
 			  /* TODO: dot1xAuthSessionOctetsTx */
@@ -2725,7 +2733,8 @@
 				   wpa_auth_sta_key_mgmt(sta->wpa_sm))) ?
 			  1 : 2,
 			  (unsigned int) diff.sec,
-			  sm->identity);
+			  sm->identity ? (char *) sm->identity : identity_buf);
+	os_free(identity_buf);
 	if (os_snprintf_error(buflen - len, ret))
 		return len;
 	len += ret;
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index b8fd592..2b6f727 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -11,6 +11,7 @@
 
 #include "utils/common.h"
 #include "hostapd.h"
+#include "ieee802_11.h"
 #include "neighbor_db.h"
 
 
@@ -123,7 +124,7 @@
 }
 
 
-void hostpad_free_neighbor_db(struct hostapd_data *hapd)
+void hostapd_free_neighbor_db(struct hostapd_data *hapd)
 {
 	struct hostapd_neighbor_entry *nr, *prev;
 
@@ -134,3 +135,123 @@
 		os_free(nr);
 	}
 }
+
+
+#ifdef NEED_AP_MLME
+static enum nr_chan_width hostapd_get_nr_chan_width(struct hostapd_data *hapd,
+						    int ht, int vht)
+{
+	if (!ht && !vht)
+		return NR_CHAN_WIDTH_20;
+	if (!hapd->iconf->secondary_channel)
+		return NR_CHAN_WIDTH_20;
+	if (!vht || hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_USE_HT)
+		return NR_CHAN_WIDTH_40;
+	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_80MHZ)
+		return NR_CHAN_WIDTH_80;
+	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_160MHZ)
+		return NR_CHAN_WIDTH_160;
+	if (hapd->iconf->vht_oper_chwidth == VHT_CHANWIDTH_80P80MHZ)
+		return NR_CHAN_WIDTH_80P80;
+	return NR_CHAN_WIDTH_20;
+}
+#endif /* NEED_AP_MLME */
+
+
+void hostapd_neighbor_set_own_report(struct hostapd_data *hapd)
+{
+#ifdef NEED_AP_MLME
+	u16 capab = hostapd_own_capab_info(hapd);
+	int ht = hapd->iconf->ieee80211n && !hapd->conf->disable_11n;
+	int vht = hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac;
+	struct wpa_ssid_value ssid;
+	u8 channel, op_class;
+	u8 center_freq1_idx = 0, center_freq2_idx = 0;
+	enum nr_chan_width width;
+	u32 bssid_info;
+	struct wpabuf *nr;
+
+	if (!(hapd->conf->radio_measurements[0] &
+	      WLAN_RRM_CAPS_NEIGHBOR_REPORT))
+		return;
+
+	bssid_info = 3; /* AP is reachable */
+	bssid_info |= NEI_REP_BSSID_INFO_SECURITY; /* "same as the AP" */
+	bssid_info |= NEI_REP_BSSID_INFO_KEY_SCOPE; /* "same as the AP" */
+
+	if (capab & WLAN_CAPABILITY_SPECTRUM_MGMT)
+		bssid_info |= NEI_REP_BSSID_INFO_SPECTRUM_MGMT;
+
+	bssid_info |= NEI_REP_BSSID_INFO_RM; /* RRM is supported */
+
+	if (hapd->conf->wmm_enabled) {
+		bssid_info |= NEI_REP_BSSID_INFO_QOS;
+
+		if (hapd->conf->wmm_uapsd &&
+		    (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_UAPSD))
+			bssid_info |= NEI_REP_BSSID_INFO_APSD;
+	}
+
+	if (ht) {
+		bssid_info |= NEI_REP_BSSID_INFO_HT |
+			NEI_REP_BSSID_INFO_DELAYED_BA;
+
+		/* VHT bit added in IEEE P802.11-REVmc/D4.3 */
+		if (vht)
+			bssid_info |= NEI_REP_BSSID_INFO_VHT;
+	}
+
+	/* TODO: Set NEI_REP_BSSID_INFO_MOBILITY_DOMAIN if MDE is set */
+
+	if (ieee80211_freq_to_channel_ext(hapd->iface->freq,
+					  hapd->iconf->secondary_channel,
+					  hapd->iconf->vht_oper_chwidth,
+					  &op_class, &channel) ==
+	    NUM_HOSTAPD_MODES)
+		return;
+	width = hostapd_get_nr_chan_width(hapd, ht, vht);
+	if (vht) {
+		center_freq1_idx = hapd->iconf->vht_oper_centr_freq_seg0_idx;
+		if (width == NR_CHAN_WIDTH_80P80)
+			center_freq2_idx =
+				hapd->iconf->vht_oper_centr_freq_seg1_idx;
+	} else if (ht) {
+		ieee80211_freq_to_chan(hapd->iface->freq +
+				       10 * hapd->iconf->secondary_channel,
+				       &center_freq1_idx);
+	}
+
+	ssid.ssid_len = hapd->conf->ssid.ssid_len;
+	os_memcpy(ssid.ssid, hapd->conf->ssid.ssid, ssid.ssid_len);
+
+	/*
+	 * Neighbor Report element size = BSSID + BSSID info + op_class + chan +
+	 * phy type + wide bandwidth channel subelement.
+	 */
+	nr = wpabuf_alloc(ETH_ALEN + 4 + 1 + 1 + 1 + 5);
+	if (!nr)
+		return;
+
+	wpabuf_put_data(nr, hapd->own_addr, ETH_ALEN);
+	wpabuf_put_le32(nr, bssid_info);
+	wpabuf_put_u8(nr, op_class);
+	wpabuf_put_u8(nr, channel);
+	wpabuf_put_u8(nr, ieee80211_get_phy_type(hapd->iface->freq, ht, vht));
+
+	/*
+	 * Wide Bandwidth Channel subelement may be needed to allow the
+	 * receiving STA to send packets to the AP. See IEEE P802.11-REVmc/D5.0
+	 * Figure 9-301.
+	 */
+	wpabuf_put_u8(nr, WNM_NEIGHBOR_WIDE_BW_CHAN);
+	wpabuf_put_u8(nr, 3);
+	wpabuf_put_u8(nr, width);
+	wpabuf_put_u8(nr, center_freq1_idx);
+	wpabuf_put_u8(nr, center_freq2_idx);
+
+	hostapd_neighbor_set(hapd, hapd->own_addr, &ssid, nr, hapd->iconf->lci,
+			     hapd->iconf->civic, hapd->iconf->stationary_ap);
+
+	wpabuf_free(nr);
+#endif /* NEED_AP_MLME */
+}
diff --git a/src/ap/neighbor_db.h b/src/ap/neighbor_db.h
index ba46d88..9c8f4f2 100644
--- a/src/ap/neighbor_db.h
+++ b/src/ap/neighbor_db.h
@@ -17,8 +17,9 @@
 			 const struct wpa_ssid_value *ssid,
 			 const struct wpabuf *nr, const struct wpabuf *lci,
 			 const struct wpabuf *civic, int stationary);
+void hostapd_neighbor_set_own_report(struct hostapd_data *hapd);
 int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid,
 			    const struct wpa_ssid_value *ssid);
-void hostpad_free_neighbor_db(struct hostapd_data *hapd);
+void hostapd_free_neighbor_db(struct hostapd_data *hapd);
 
 #endif /* NEIGHBOR_DB_H */
diff --git a/src/ap/rrm.c b/src/ap/rrm.c
index 56ed29c..f2d5cd1 100644
--- a/src/ap/rrm.c
+++ b/src/ap/rrm.c
@@ -558,7 +558,7 @@
 
 void hostapd_clean_rrm(struct hostapd_data *hapd)
 {
-	hostpad_free_neighbor_db(hapd);
+	hostapd_free_neighbor_db(hapd);
 	eloop_cancel_timeout(hostapd_lci_rep_timeout_handler, hapd, NULL);
 	hapd->lci_req_active = 0;
 	eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 179cf43..8858a34 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -166,7 +166,7 @@
 	/* just in case */
 	ap_sta_set_authorized(hapd, sta, 0);
 
-	if (sta->flags & WLAN_STA_WDS)
+	if (sta->flags & (WLAN_STA_WDS | WLAN_STA_MULTI_AP))
 		hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0);
 
 	if (sta->ipaddr)
@@ -328,6 +328,7 @@
 
 	os_free(sta->ht_capabilities);
 	os_free(sta->vht_capabilities);
+	os_free(sta->vht_operation);
 	hostapd_free_psk_list(sta->psk);
 	os_free(sta->identity);
 	os_free(sta->radius_cui);
@@ -896,9 +897,6 @@
 	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 */
@@ -1165,6 +1163,32 @@
 #endif /* CONFIG_IEEE80211W */
 
 
+const char * ap_sta_wpa_get_keyid(struct hostapd_data *hapd,
+				  struct sta_info *sta)
+{
+	struct hostapd_wpa_psk *psk;
+	struct hostapd_ssid *ssid;
+	const u8 *pmk;
+	int pmk_len;
+
+	ssid = &hapd->conf->ssid;
+
+	pmk = wpa_auth_get_pmk(sta->wpa_sm, &pmk_len);
+	if (!pmk || pmk_len != PMK_LEN)
+		return NULL;
+
+	for (psk = ssid->wpa_psk; psk; psk = psk->next)
+		if (os_memcmp(pmk, psk->psk, PMK_LEN) == 0)
+			break;
+	if (!psk)
+		return NULL;
+	if (!psk || !psk->keyid[0])
+		return NULL;
+
+	return psk->keyid;
+}
+
+
 void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
 			   int authorized)
 {
@@ -1203,7 +1227,11 @@
 					sta->addr, authorized, dev_addr);
 
 	if (authorized) {
+		const char *keyid;
+		char keyid_buf[100];
 		char ip_addr[100];
+
+		keyid_buf[0] = '\0';
 		ip_addr[0] = '\0';
 #ifdef CONFIG_P2P
 		if (wpa_auth_get_ip_addr(sta->wpa_sm, ip_addr_buf) == 0) {
@@ -1214,14 +1242,20 @@
 		}
 #endif /* CONFIG_P2P */
 
-		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_CONNECTED "%s%s",
-			buf, ip_addr);
+		keyid = ap_sta_wpa_get_keyid(hapd, sta);
+		if (keyid) {
+			os_snprintf(keyid_buf, sizeof(keyid_buf),
+				    " keyid=%s", keyid);
+		}
+
+		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_CONNECTED "%s%s%s",
+			buf, ip_addr, keyid_buf);
 
 		if (hapd->msg_ctx_parent &&
 		    hapd->msg_ctx_parent != hapd->msg_ctx)
 			wpa_msg_no_global(hapd->msg_ctx_parent, MSG_INFO,
-					  AP_STA_CONNECTED "%s%s",
-					  buf, ip_addr);
+					  AP_STA_CONNECTED "%s%s%s",
+					  buf, ip_addr, keyid_buf);
 	} else {
 		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_STA_DISCONNECTED "%s", buf);
 
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 9cac6f1..ee3f628 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -36,6 +36,7 @@
 #define WLAN_STA_VHT_OPMODE_ENABLED BIT(20)
 #define WLAN_STA_VENDOR_VHT BIT(21)
 #define WLAN_STA_PENDING_FILS_ERP BIT(22)
+#define WLAN_STA_MULTI_AP BIT(23)
 #define WLAN_STA_PENDING_DISASSOC_CB BIT(29)
 #define WLAN_STA_PENDING_DEAUTH_CB BIT(30)
 #define WLAN_STA_NONERP BIT(31)
@@ -117,6 +118,7 @@
 	unsigned int power_capab:1;
 	unsigned int agreed_to_steer:1;
 	unsigned int hs20_t_c_filtering:1;
+	unsigned int ft_over_ds:1;
 
 	u16 auth_alg;
 
@@ -162,6 +164,7 @@
 
 	struct ieee80211_ht_capabilities *ht_capabilities;
 	struct ieee80211_vht_capabilities *vht_capabilities;
+	struct ieee80211_vht_operation *vht_operation;
 	u8 vht_opmode;
 
 #ifdef CONFIG_IEEE80211W
@@ -215,6 +218,7 @@
 	u8 cell_capa; /* 0 = unknown (not an MBO STA); otherwise,
 		       * enum mbo_cellular_capa values */
 	struct mbo_non_pref_chan_info *non_pref_chan;
+	int auth_rssi; /* Last Authentication frame RSSI */
 #endif /* CONFIG_MBO */
 
 	u8 *supp_op_classes; /* Supported Operating Classes element, if
@@ -320,6 +324,8 @@
 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);
+const char * ap_sta_wpa_get_keyid(struct hostapd_data *hapd,
+				  struct sta_info *sta);
 void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *addr, u16 reason);
 
diff --git a/src/ap/vlan_full.c b/src/ap/vlan_full.c
index aa42335..19aa3c6 100644
--- a/src/ap/vlan_full.c
+++ b/src/ap/vlan_full.c
@@ -16,6 +16,7 @@
 
 #include "utils/common.h"
 #include "drivers/priv_netlink.h"
+#include "drivers/linux_ioctl.h"
 #include "common/linux_bridge.h"
 #include "common/linux_vlan.h"
 #include "utils/eloop.h"
@@ -143,6 +144,9 @@
 		return -1;
 	}
 
+	if (linux_br_del_if(fd, br_name, if_name) == 0)
+		goto done;
+
 	if_index = if_nametoindex(if_name);
 
 	if (if_index == 0) {
@@ -168,6 +172,7 @@
 		return -1;
 	}
 
+done:
 	close(fd);
 	return 0;
 }
@@ -194,6 +199,14 @@
 		return -1;
 	}
 
+	if (linux_br_add_if(fd, br_name, if_name) == 0)
+		goto done;
+	if (errno == EBUSY) {
+		/* The interface is already added. */
+		close(fd);
+		return 1;
+	}
+
 	if_index = if_nametoindex(if_name);
 
 	if (if_index == 0) {
@@ -224,6 +237,7 @@
 		return -1;
 	}
 
+done:
 	close(fd);
 	return 0;
 }
@@ -241,6 +255,9 @@
 		return -1;
 	}
 
+	if (linux_br_del(fd, br_name) == 0)
+		goto done;
+
 	arg[0] = BRCTL_DEL_BRIDGE;
 	arg[1] = (unsigned long) br_name;
 
@@ -252,6 +269,7 @@
 		return -1;
 	}
 
+done:
 	close(fd);
 	return 0;
 }
@@ -277,11 +295,19 @@
 		return -1;
 	}
 
+	if (linux_br_add(fd, br_name) == 0)
+		goto done;
+	if (errno == EEXIST) {
+		/* The bridge is already added. */
+		close(fd);
+		return 1;
+	}
+
 	arg[0] = BRCTL_ADD_BRIDGE;
 	arg[1] = (unsigned long) br_name;
 
 	if (ioctl(fd, SIOCGIFBR, arg) < 0) {
- 		if (errno == EEXIST) {
+		if (errno == EEXIST) {
 			/* The bridge is already added. */
 			close(fd);
 			return 1;
@@ -294,6 +320,7 @@
 		}
 	}
 
+done:
 	/* Decrease forwarding delay to avoid EAPOL timeouts. */
 	os_memset(&ifr, 0, sizeof(ifr));
 	os_strlcpy(ifr.ifr_name, br_name, IFNAMSIZ);
@@ -363,12 +390,18 @@
 {
 	char vlan_ifname[IFNAMSIZ];
 	int clean;
+	int ret;
 
 	if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE)
-		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
-			    tagged_interface, vid);
+		ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
+				  tagged_interface, vid);
 	else
-		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", vid);
+		ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d",
+				  vid);
+	if (ret >= (int) sizeof(vlan_ifname))
+		wpa_printf(MSG_WARNING,
+			   "VLAN: Interface name was truncated to %s",
+			   vlan_ifname);
 
 	clean = 0;
 	ifconfig_up(tagged_interface);
@@ -384,19 +417,28 @@
 }
 
 
-static void vlan_bridge_name(char *br_name, struct hostapd_data *hapd, int vid)
+static void vlan_bridge_name(char *br_name, struct hostapd_data *hapd,
+			     struct hostapd_vlan *vlan, int vid)
 {
 	char *tagged_interface = hapd->conf->ssid.vlan_tagged_interface;
+	int ret;
 
-	if (hapd->conf->vlan_bridge[0]) {
-		os_snprintf(br_name, IFNAMSIZ, "%s%d",
-			    hapd->conf->vlan_bridge, vid);
+	if (vlan->bridge[0]) {
+		os_strlcpy(br_name, vlan->bridge, IFNAMSIZ);
+		ret = 0;
+	} else if (hapd->conf->vlan_bridge[0]) {
+		ret = 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);
+		ret = os_snprintf(br_name, IFNAMSIZ, "br%s.%d",
+				  tagged_interface, vid);
 	} else {
-		os_snprintf(br_name, IFNAMSIZ, "brvlan%d", vid);
+		ret = os_snprintf(br_name, IFNAMSIZ, "brvlan%d", vid);
 	}
+	if (ret >= IFNAMSIZ)
+		wpa_printf(MSG_WARNING,
+			   "VLAN: Interface name was truncated to %s",
+			   br_name);
 }
 
 
@@ -445,7 +487,7 @@
 		    !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_bridge_name(br_name, hapd, vlan, untagged);
 
 		vlan_get_bridge(br_name, hapd, untagged);
 
@@ -458,7 +500,7 @@
 		    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_bridge_name(br_name, hapd, vlan, tagged[i]);
 		vlan_get_bridge(br_name, hapd, tagged[i]);
 		vlan_newlink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE,
 				    ifname, br_name, tagged[i], hapd);
@@ -474,12 +516,19 @@
 {
 	char vlan_ifname[IFNAMSIZ];
 	int clean;
+	int ret;
 
 	if (vlan_naming == DYNAMIC_VLAN_NAMING_WITH_DEVICE)
-		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
-			    tagged_interface, vid);
+		ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "%s.%d",
+				  tagged_interface, vid);
 	else
-		os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d", vid);
+		ret = os_snprintf(vlan_ifname, sizeof(vlan_ifname), "vlan%d",
+				  vid);
+	if (ret >= (int) sizeof(vlan_ifname))
+		wpa_printf(MSG_WARNING,
+			   "VLAN: Interface name was truncated to %s",
+			   vlan_ifname);
+
 
 	clean = dyn_iface_put(hapd, vlan_ifname);
 
@@ -543,7 +592,7 @@
 			    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_bridge_name(br_name, hapd, vlan, tagged[i]);
 			vlan_dellink_tagged(DYNAMIC_VLAN_NAMING_WITH_DEVICE,
 					    ifname, br_name, tagged[i], hapd);
 			vlan_put_bridge(br_name, hapd, tagged[i]);
@@ -555,7 +604,7 @@
 			    (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);
+			vlan_bridge_name(br_name, hapd, vlan, untagged);
 
 			if (vlan->clean & DVLAN_CLEAN_WLAN_PORT)
 				br_delif(br_name, vlan->ifname);
diff --git a/src/ap/vlan_init.c b/src/ap/vlan_init.c
index 01fecee..e293a00 100644
--- a/src/ap/vlan_init.c
+++ b/src/ap/vlan_init.c
@@ -187,6 +187,7 @@
 {
 	struct hostapd_vlan *n;
 	char ifname[IFNAMSIZ + 1], *pos;
+	int ret;
 
 	if (vlan == NULL || vlan->vlan_id != VLAN_ID_WILDCARD)
 		return NULL;
@@ -208,8 +209,13 @@
 		n->vlan_desc = *vlan_desc;
 	n->dynamic_vlan = 1;
 
-	os_snprintf(n->ifname, sizeof(n->ifname), "%s%d%s", ifname, vlan_id,
-		    pos);
+	ret = os_snprintf(n->ifname, sizeof(n->ifname), "%s%d%s",
+			  ifname, vlan_id, pos);
+	if (os_snprintf_error(sizeof(n->ifname), ret)) {
+		os_free(n);
+		return NULL;
+	}
+	os_strlcpy(n->bridge, vlan->bridge, sizeof(n->bridge));
 
 	n->next = hapd->conf->vlan;
 	hapd->conf->vlan = n;
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 61d2f65..27c69d3 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -12,6 +12,7 @@
 #include "utils/eloop.h"
 #include "common/ieee802_11_defs.h"
 #include "common/wpa_ctrl.h"
+#include "common/ocv.h"
 #include "ap/hostapd.h"
 #include "ap/sta_info.h"
 #include "ap/ap_config.h"
@@ -54,8 +55,8 @@
 	size_t gtk_elem_len = 0;
 	size_t igtk_elem_len = 0;
 	struct wnm_sleep_element wnmsleep_ie;
-	u8 *wnmtfs_ie;
-	u8 wnmsleep_ie_len;
+	u8 *wnmtfs_ie, *oci_ie;
+	u8 wnmsleep_ie_len, oci_ie_len;
 	u16 wnmtfs_ie_len;
 	u8 *pos;
 	struct sta_info *sta;
@@ -88,10 +89,42 @@
 		wnmtfs_ie = NULL;
 	}
 
+	oci_ie = NULL;
+	oci_ie_len = 0;
+#ifdef CONFIG_OCV
+	if (action_type == WNM_SLEEP_MODE_EXIT &&
+	    wpa_auth_uses_ocv(sta->wpa_sm)) {
+		struct wpa_channel_info ci;
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info for OCI element in WNM-Sleep Mode frame");
+			os_free(wnmtfs_ie);
+			return -1;
+		}
+
+		oci_ie_len = OCV_OCI_EXTENDED_LEN;
+		oci_ie = os_zalloc(oci_ie_len);
+		if (!oci_ie) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to allocate buffer for OCI element in WNM-Sleep Mode frame");
+			os_free(wnmtfs_ie);
+			return -1;
+		}
+
+		if (ocv_insert_extended_oci(&ci, oci_ie) < 0) {
+			os_free(wnmtfs_ie);
+			os_free(oci_ie);
+			return -1;
+		}
+	}
+#endif /* CONFIG_OCV */
+
 #define MAX_GTK_SUBELEM_LEN 45
 #define MAX_IGTK_SUBELEM_LEN 26
 	mgmt = os_zalloc(sizeof(*mgmt) + wnmsleep_ie_len +
-			 MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN);
+			 MAX_GTK_SUBELEM_LEN + MAX_IGTK_SUBELEM_LEN +
+			 oci_ie_len);
 	if (mgmt == NULL) {
 		wpa_printf(MSG_DEBUG, "MLME: Failed to allocate buffer for "
 			   "WNM-Sleep Response action frame");
@@ -134,11 +167,18 @@
 	os_memcpy(pos, &wnmsleep_ie, wnmsleep_ie_len);
 	/* copy TFS IE here */
 	pos += wnmsleep_ie_len;
-	if (wnmtfs_ie)
+	if (wnmtfs_ie) {
 		os_memcpy(pos, wnmtfs_ie, wnmtfs_ie_len);
+		pos += wnmtfs_ie_len;
+	}
+#ifdef CONFIG_OCV
+	/* copy OCV OCI here */
+	if (oci_ie_len > 0)
+		os_memcpy(pos, oci_ie, oci_ie_len);
+#endif /* CONFIG_OCV */
 
 	len = 1 + sizeof(mgmt->u.action.u.wnm_sleep_resp) + gtk_elem_len +
-		igtk_elem_len + wnmsleep_ie_len + wnmtfs_ie_len;
+		igtk_elem_len + wnmsleep_ie_len + wnmtfs_ie_len + oci_ie_len;
 
 	/* In driver, response frame should be forced to sent when STA is in
 	 * PS mode */
@@ -185,6 +225,7 @@
 #undef MAX_IGTK_SUBELEM_LEN
 fail:
 	os_free(wnmtfs_ie);
+	os_free(oci_ie);
 	os_free(mgmt);
 	return res;
 }
@@ -201,6 +242,11 @@
 	u8 *tfsreq_ie_start = NULL;
 	u8 *tfsreq_ie_end = NULL;
 	u16 tfsreq_ie_len = 0;
+#ifdef CONFIG_OCV
+	struct sta_info *sta;
+	const u8 *oci_ie = NULL;
+	u8 oci_ie_len = 0;
+#endif /* CONFIG_OCV */
 
 	if (!hapd->conf->wnm_sleep_mode) {
 		wpa_printf(MSG_DEBUG, "Ignore WNM-Sleep Mode Request from "
@@ -228,6 +274,12 @@
 			if (!tfsreq_ie_start)
 				tfsreq_ie_start = (u8 *) pos;
 			tfsreq_ie_end = (u8 *) pos;
+#ifdef CONFIG_OCV
+		} else if (*pos == WLAN_EID_EXTENSION && ie_len >= 1 &&
+			   pos[2] == WLAN_EID_EXT_OCV_OCI) {
+			oci_ie = pos + 3;
+			oci_ie_len = ie_len - 1;
+#endif /* CONFIG_OCV */
 		} else
 			wpa_printf(MSG_DEBUG, "WNM: EID %d not recognized",
 				   *pos);
@@ -239,6 +291,27 @@
 		return;
 	}
 
+#ifdef CONFIG_OCV
+	sta = ap_get_sta(hapd, addr);
+	if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_EXIT &&
+	    sta && wpa_auth_uses_ocv(sta->wpa_sm)) {
+		struct wpa_channel_info ci;
+
+		if (hostapd_drv_channel_info(hapd, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info to validate received OCI in WNM-Sleep Mode frame");
+			return;
+		}
+
+		if (ocv_verify_tx_params(oci_ie, oci_ie_len, &ci,
+					 channel_width_to_int(ci.chanwidth),
+					 ci.seg1_idx) != 0) {
+			wpa_msg(hapd, MSG_WARNING, "WNM: %s", ocv_errorstr);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
+
 	if (wnmsleep_ie->action_type == WNM_SLEEP_MODE_ENTER &&
 	    tfsreq_ie_start && tfsreq_ie_end &&
 	    tfsreq_ie_end - tfsreq_ie_start >= 0) {
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 34969e7..8e2b48e 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -13,6 +13,7 @@
 #include "utils/state_machine.h"
 #include "utils/bitfield.h"
 #include "common/ieee802_11_defs.h"
+#include "common/ocv.h"
 #include "crypto/aes.h"
 #include "crypto/aes_wrap.h"
 #include "crypto/aes_siv.h"
@@ -22,6 +23,7 @@
 #include "crypto/sha384.h"
 #include "crypto/random.h"
 #include "eapol_auth/eapol_auth_sm.h"
+#include "drivers/driver.h"
 #include "ap_config.h"
 #include "ieee802_11.h"
 #include "wpa_auth.h"
@@ -238,6 +240,17 @@
 }
 
 
+#ifdef CONFIG_OCV
+static int wpa_channel_info(struct wpa_authenticator *wpa_auth,
+			    struct wpa_channel_info *ci)
+{
+	if (!wpa_auth->cb->channel_info)
+		return -1;
+	return wpa_auth->cb->channel_info(wpa_auth->cb_ctx, ci);
+}
+#endif /* CONFIG_OCV */
+
+
 static void wpa_rekey_gmk(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_authenticator *wpa_auth = eloop_ctx;
@@ -860,6 +873,8 @@
 
 		if (wpa_verify_key_mic(sm->wpa_key_mgmt, pmk_len, &PTK,
 				       data, data_len) == 0) {
+			os_memcpy(sm->PMK, pmk, pmk_len);
+			sm->pmk_len = pmk_len;
 			ok = 1;
 			break;
 		}
@@ -2559,6 +2574,27 @@
 	wpabuf_put(plain, tmp2 - tmp);
 
 	*len = (u8 *) wpabuf_put(plain, 0) - len - 1;
+
+#ifdef CONFIG_OCV
+	if (wpa_auth_uses_ocv(sm)) {
+		struct wpa_channel_info ci;
+		u8 *pos;
+
+		if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "FILS: Failed to get channel info for OCI element");
+			wpabuf_free(plain);
+			return NULL;
+		}
+
+		pos = wpabuf_put(plain, OCV_OCI_EXTENDED_LEN);
+		if (ocv_insert_extended_oci(&ci, pos) < 0) {
+			wpabuf_free(plain);
+			return NULL;
+		}
+	}
+#endif /* CONFIG_OCV */
+
 	return plain;
 }
 
@@ -2624,6 +2660,21 @@
 #endif /* CONFIG_FILS */
 
 
+#ifdef CONFIG_OCV
+int get_sta_tx_parameters(struct wpa_state_machine *sm, int ap_max_chanwidth,
+			  int ap_seg1_idx, int *bandwidth, int *seg1_idx)
+{
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+
+	if (!wpa_auth->cb->get_sta_tx_params)
+		return -1;
+	return wpa_auth->cb->get_sta_tx_params(wpa_auth->cb_ctx, sm->addr,
+					       ap_max_chanwidth, ap_seg1_idx,
+					       bandwidth, seg1_idx);
+}
+#endif /* CONFIG_OCV */
+
+
 SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
 {
 	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
@@ -2675,6 +2726,8 @@
 		    wpa_verify_key_mic(sm->wpa_key_mgmt, pmk_len, &PTK,
 				       sm->last_rx_eapol_key,
 				       sm->last_rx_eapol_key_len) == 0) {
+			os_memcpy(sm->PMK, pmk, pmk_len);
+			sm->pmk_len = pmk_len;
 			ok = 1;
 			break;
 		}
@@ -2746,6 +2799,32 @@
 				   WLAN_REASON_PREV_AUTH_NOT_VALID);
 		return;
 	}
+#ifdef CONFIG_OCV
+	if (wpa_auth_uses_ocv(sm)) {
+		struct wpa_channel_info ci;
+		int tx_chanwidth;
+		int tx_seg1_idx;
+
+		if (wpa_channel_info(wpa_auth, &ci) != 0) {
+			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
+					"Failed to get channel info to validate received OCI in EAPOL-Key 2/4");
+			return;
+		}
+
+		if (get_sta_tx_parameters(sm,
+					  channel_width_to_int(ci.chanwidth),
+					  ci.seg1_idx, &tx_chanwidth,
+					  &tx_seg1_idx) < 0)
+			return;
+
+		if (ocv_verify_tx_params(kde.oci, kde.oci_len, &ci,
+					 tx_chanwidth, tx_seg1_idx) != 0) {
+			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
+					ocv_errorstr);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
 #ifdef CONFIG_IEEE80211R_AP
 	if (ft && ft_check_msg_2_of_4(wpa_auth, sm, &kde) < 0) {
 		wpa_sta_disconnect(wpa_auth, sm->addr,
@@ -2883,6 +2962,36 @@
 #endif /* CONFIG_IEEE80211W */
 
 
+static int ocv_oci_len(struct wpa_state_machine *sm)
+{
+#ifdef CONFIG_OCV
+	if (wpa_auth_uses_ocv(sm))
+		return OCV_OCI_KDE_LEN;
+#endif /* CONFIG_OCV */
+	return 0;
+}
+
+static int ocv_oci_add(struct wpa_state_machine *sm, u8 **argpos)
+{
+#ifdef CONFIG_OCV
+	struct wpa_channel_info ci;
+
+	if (!wpa_auth_uses_ocv(sm))
+		return 0;
+
+	if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
+		wpa_printf(MSG_WARNING,
+			   "Failed to get channel info for OCI element");
+		return -1;
+	}
+
+	return ocv_insert_oci_kde(&ci, argpos);
+#else /* CONFIG_OCV */
+	return 0;
+#endif /* CONFIG_OCV */
+}
+
+
 SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 {
 	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde, *pos, dummy_gtk[32];
@@ -2966,7 +3075,7 @@
 		}
 	}
 
-	kde_len = wpa_ie_len + ieee80211w_kde_len(sm);
+	kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 	if (gtk)
 		kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
 #ifdef CONFIG_IEEE80211R_AP
@@ -3011,6 +3120,10 @@
 				  gtk, gtk_len);
 	}
 	pos = ieee80211w_kde_add(sm, pos);
+	if (ocv_oci_add(sm, &pos) < 0) {
+		os_free(kde);
+		return;
+	}
 
 #ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
@@ -3322,7 +3435,7 @@
 	}
 	if (sm->wpa == WPA_VERSION_WPA2) {
 		kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
-			ieee80211w_kde_len(sm);
+			ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 		kde_buf = os_malloc(kde_len);
 		if (kde_buf == NULL)
 			return;
@@ -3333,6 +3446,10 @@
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gsm->GTK_len);
 		pos = ieee80211w_kde_add(sm, pos);
+		if (ocv_oci_add(sm, &pos) < 0) {
+			os_free(kde_buf);
+			return;
+		}
 		kde_len = pos - kde;
 	} else {
 		kde = gtk;
@@ -3353,8 +3470,67 @@
 
 SM_STATE(WPA_PTK_GROUP, REKEYESTABLISHED)
 {
+#ifdef CONFIG_OCV
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	const u8 *key_data, *mic;
+	struct ieee802_1x_hdr *hdr;
+	struct wpa_eapol_key *key;
+	struct wpa_eapol_ie_parse kde;
+	size_t mic_len;
+	u16 key_data_length;
+#endif /* CONFIG_OCV */
+
 	SM_ENTRY_MA(WPA_PTK_GROUP, REKEYESTABLISHED, wpa_ptk_group);
 	sm->EAPOLKeyReceived = FALSE;
+
+#ifdef CONFIG_OCV
+	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
+
+	/*
+	 * Note: last_rx_eapol_key length fields have already been validated in
+	 * wpa_receive().
+	 */
+	hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key;
+	key = (struct wpa_eapol_key *) (hdr + 1);
+	mic = (u8 *) (key + 1);
+	key_data = mic + mic_len + 2;
+	key_data_length = WPA_GET_BE16(mic + mic_len);
+	if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) -
+	    sizeof(*key) - mic_len - 2)
+		return;
+
+	if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) {
+		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_INFO,
+				 "received EAPOL-Key group msg 2/2 with invalid Key Data contents");
+		return;
+	}
+
+	if (wpa_auth_uses_ocv(sm)) {
+		struct wpa_channel_info ci;
+		int tx_chanwidth;
+		int tx_seg1_idx;
+
+		if (wpa_channel_info(wpa_auth, &ci) != 0) {
+			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
+					"Failed to get channel info to validate received OCI in EAPOL-Key group 1/2");
+			return;
+		}
+
+		if (get_sta_tx_parameters(sm,
+					  channel_width_to_int(ci.chanwidth),
+					  ci.seg1_idx, &tx_chanwidth,
+					  &tx_seg1_idx) < 0)
+			return;
+
+		if (ocv_verify_tx_params(kde.oci, kde.oci_len, &ci,
+					 tx_chanwidth, tx_seg1_idx) != 0) {
+			wpa_auth_logger(wpa_auth, sm->addr, LOGGER_INFO,
+					ocv_errorstr);
+			return;
+		}
+	}
+#endif /* CONFIG_OCV */
+
 	if (sm->GUpdateStationKeys)
 		sm->group->GKeyDoneStations--;
 	sm->GUpdateStationKeys = FALSE;
@@ -3963,6 +4139,15 @@
 }
 
 
+const u8 * wpa_auth_get_pmk(struct wpa_state_machine *sm, int *len)
+{
+	if (!sm)
+		return NULL;
+	*len = sm->pmk_len;
+	return sm->PMK;
+}
+
+
 int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm)
 {
 	if (sm == NULL)
@@ -4666,7 +4851,7 @@
 		}
 	}
 
-	kde_len = wpa_ie_len + ieee80211w_kde_len(sm);
+	kde_len = wpa_ie_len + ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 	if (gtk)
 		kde_len += 2 + RSN_SELECTOR_LEN + 2 + gtk_len;
 #ifdef CONFIG_IEEE80211R_AP
@@ -4715,6 +4900,10 @@
 		os_memset(opos, 0, 6); /* clear PN */
 	}
 #endif /* CONFIG_IEEE80211W */
+	if (ocv_oci_add(sm, &pos) < 0) {
+		os_free(kde);
+		return -1;
+	}
 
 #ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
@@ -4796,7 +4985,7 @@
 	gtk = gsm->GTK[gsm->GN - 1];
 	if (sm->wpa == WPA_VERSION_WPA2) {
 		kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
-			ieee80211w_kde_len(sm);
+			ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 		kde_buf = os_malloc(kde_len);
 		if (kde_buf == NULL)
 			return -1;
@@ -4816,6 +5005,10 @@
 			os_memset(opos, 0, 6); /* clear PN */
 		}
 #endif /* CONFIG_IEEE80211W */
+		if (ocv_oci_add(sm, &pos) < 0) {
+			os_free(kde_buf);
+			return -1;
+		}
 		kde_len = pos - kde;
 	} else {
 		kde = gtk;
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index fad5536..e61648d 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -145,6 +145,7 @@
 struct rsn_pmksa_cache_entry;
 struct eapol_state_machine;
 struct ft_remote_seq;
+struct wpa_channel_info;
 
 
 struct ft_remote_r0kh {
@@ -191,6 +192,9 @@
 	int group_mgmt_cipher;
 	int sae_require_mfp;
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+	int ocv; /* Operating Channel Validation */
+#endif /* CONFIG_OCV */
 #ifdef CONFIG_IEEE80211R_AP
 	u8 ssid[SSID_MAX_LEN];
 	size_t ssid_len;
@@ -265,6 +269,10 @@
 			  size_t data_len);
 	int (*send_oui)(void *ctx, const u8 *dst, u8 oui_suffix, const u8 *data,
 			size_t data_len);
+	int (*channel_info)(void *ctx, struct wpa_channel_info *ci);
+	int (*get_sta_tx_params)(void *ctx, const u8 *addr,
+				 int ap_max_chanwidth, int ap_seg1_idx,
+				 int *bandwidth, int *seg1_idx);
 #ifdef CONFIG_IEEE80211R_AP
 	struct wpa_state_machine * (*add_sta)(void *ctx, const u8 *sta_addr);
 	int (*set_vlan)(void *ctx, const u8 *sta_addr,
@@ -316,6 +324,8 @@
 		      struct wpa_state_machine *sm,
 		      const u8 *osen_ie, size_t osen_ie_len);
 int wpa_auth_uses_mfp(struct wpa_state_machine *sm);
+void wpa_auth_set_ocv(struct wpa_state_machine *sm, int ocv);
+int wpa_auth_uses_ocv(struct wpa_state_machine *sm);
 struct wpa_state_machine *
 wpa_auth_sta_init(struct wpa_authenticator *wpa_auth, const u8 *addr,
 		  const u8 *p2p_dev_addr);
@@ -339,6 +349,7 @@
 void wpa_auth_countermeasures_start(struct wpa_authenticator *wpa_auth);
 int wpa_auth_pairwise_set(struct wpa_state_machine *sm);
 int wpa_auth_get_pairwise(struct wpa_state_machine *sm);
+const u8 * wpa_auth_get_pmk(struct wpa_state_machine *sm, int *len);
 int wpa_auth_sta_key_mgmt(struct wpa_state_machine *sm);
 int wpa_auth_sta_wpa_version(struct wpa_state_machine *sm);
 int wpa_auth_sta_ft_tk_already_set(struct wpa_state_machine *sm);
@@ -449,6 +460,9 @@
 int wpa_fils_validate_key_confirm(struct wpa_state_machine *sm, const u8 *ies,
 				  size_t ies_len);
 
+int get_sta_tx_parameters(struct wpa_state_machine *sm, int ap_max_chanwidth,
+			  int ap_seg1_idx, int *bandwidth, int *seg1_idx);
+
 int wpa_auth_write_fte(struct wpa_authenticator *wpa_auth, int use_sha384,
 		       u8 *buf, size_t len);
 void wpa_auth_get_fils_aead_params(struct wpa_state_machine *sm,
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index e8d46ab..ac736f0 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -13,6 +13,8 @@
 #include "utils/list.h"
 #include "common/ieee802_11_defs.h"
 #include "common/ieee802_11_common.h"
+#include "common/ocv.h"
+#include "drivers/driver.h"
 #include "crypto/aes.h"
 #include "crypto/aes_siv.h"
 #include "crypto/aes_wrap.h"
@@ -727,6 +729,17 @@
 }
 
 
+#ifdef CONFIG_OCV
+static int wpa_channel_info(struct wpa_authenticator *wpa_auth,
+			       struct wpa_channel_info *ci)
+{
+	if (!wpa_auth->cb->channel_info)
+		return -1;
+	return wpa_auth->cb->channel_info(wpa_auth->cb_ctx, ci);
+}
+#endif /* CONFIG_OCV */
+
+
 int wpa_write_mdie(struct wpa_auth_config *conf, u8 *buf, size_t len)
 {
 	u8 *pos = buf;
@@ -1451,7 +1464,7 @@
 					now.sec;
 			else if (session_timeout && r1->session_timeout)
 				*session_timeout = 1;
-			else
+			else if (session_timeout)
 				*session_timeout = 0;
 			return 0;
 		}
@@ -2430,6 +2443,35 @@
 			os_free(igtk);
 		}
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+		if (wpa_auth_uses_ocv(sm)) {
+			struct wpa_channel_info ci;
+			u8 *nbuf, *ocipos;
+
+			if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
+				wpa_printf(MSG_WARNING,
+					   "Failed to get channel info for OCI element");
+				os_free(subelem);
+				return NULL;
+			}
+
+			subelem_len += 2 + OCV_OCI_LEN;
+			nbuf = os_realloc(subelem, subelem_len);
+			if (!nbuf) {
+				os_free(subelem);
+				return NULL;
+			}
+			subelem = nbuf;
+
+			ocipos = subelem + subelem_len - 2 - OCV_OCI_LEN;
+			*ocipos++ = FTIE_SUBELEM_OCI;
+			*ocipos++ = OCV_OCI_LEN;
+			if (ocv_insert_oci(&ci, &ocipos) < 0) {
+				os_free(subelem);
+				return NULL;
+			}
+		}
+#endif /* CONFIG_OCV */
 	} else {
 		r0kh_id = conf->r0_key_holder;
 		r0kh_id_len = conf->r0_key_holder_len;
@@ -2596,6 +2638,8 @@
 		os_memcpy(out_pmk_r1, pmk_r1, PMK_LEN);
 		if (out_pairwise)
 			*out_pairwise = pairwise;
+		os_memcpy(sm->PMK, pmk, PMK_LEN);
+		sm->pmk_len = PMK_LEN;
 		if (out_vlan &&
 		    wpa_ft_get_vlan(sm->wpa_auth, sm->addr, out_vlan) < 0) {
 			wpa_printf(MSG_DEBUG, "FT: vlan not available for STA "
@@ -3178,6 +3222,32 @@
 		return WLAN_STATUS_INVALID_FTIE;
 	}
 
+#ifdef CONFIG_OCV
+	if (wpa_auth_uses_ocv(sm)) {
+		struct wpa_channel_info ci;
+		int tx_chanwidth;
+		int tx_seg1_idx;
+
+		if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
+			wpa_printf(MSG_WARNING,
+				   "Failed to get channel info to validate received OCI in (Re)Assoc Request");
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+
+		if (get_sta_tx_parameters(sm,
+					  channel_width_to_int(ci.chanwidth),
+					  ci.seg1_idx, &tx_chanwidth,
+					  &tx_seg1_idx) < 0)
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+		if (ocv_verify_tx_params(parse.oci, parse.oci_len, &ci,
+					 tx_chanwidth, tx_seg1_idx) != 0) {
+			wpa_printf(MSG_WARNING, "%s", ocv_errorstr);
+			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		}
+	}
+#endif /* CONFIG_OCV */
+
 	return WLAN_STATUS_SUCCESS;
 }
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 8127403..9091f43 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -27,6 +27,7 @@
 #include "tkip_countermeasures.h"
 #include "ap_drv_ops.h"
 #include "ap_config.h"
+#include "ieee802_11.h"
 #include "pmksa_cache_auth.h"
 #include "wpa_auth.h"
 #include "wpa_auth_glue.h"
@@ -55,6 +56,9 @@
 	wconf->wmm_enabled = conf->wmm_enabled;
 	wconf->wmm_uapsd = conf->wmm_uapsd;
 	wconf->disable_pmksa_caching = conf->disable_pmksa_caching;
+#ifdef CONFIG_OCV
+	wconf->ocv = conf->ocv;
+#endif /* CONFIG_OCV */
 	wconf->okc = conf->okc;
 #ifdef CONFIG_IEEE80211W
 	wconf->ieee80211w = conf->ieee80211w;
@@ -776,6 +780,35 @@
 }
 
 
+static int hostapd_channel_info(void *ctx, struct wpa_channel_info *ci)
+{
+	struct hostapd_data *hapd = ctx;
+
+	return hostapd_drv_channel_info(hapd, ci);
+}
+
+
+#ifdef CONFIG_OCV
+static int hostapd_get_sta_tx_params(void *ctx, const u8 *addr,
+				     int ap_max_chanwidth, int ap_seg1_idx,
+				     int *bandwidth, int *seg1_idx)
+{
+	struct hostapd_data *hapd = ctx;
+	struct sta_info *sta;
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta) {
+		hostapd_wpa_auth_logger(hapd, addr, LOGGER_INFO,
+					"Failed to get STA info to validate received OCI");
+		return -1;
+	}
+
+	return get_tx_parameters(sta, ap_max_chanwidth, ap_seg1_idx, bandwidth,
+				 seg1_idx);
+}
+#endif /* CONFIG_OCV */
+
+
 #ifdef CONFIG_IEEE80211R_AP
 
 static int hostapd_wpa_auth_send_ft_action(void *ctx, const u8 *dst,
@@ -814,12 +847,18 @@
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta;
 
+	wpa_printf(MSG_DEBUG, "Add station entry for " MACSTR
+		   " based on WPA authenticator callback",
+		   MAC2STR(sta_addr));
 	if (hostapd_add_sta_node(hapd, sta_addr, WLAN_AUTH_FT) < 0)
 		return NULL;
 
 	sta = ap_sta_add(hapd, sta_addr);
 	if (sta == NULL)
 		return NULL;
+	if (hapd->driver && hapd->driver->add_sta_node)
+		sta->added_unassoc = 1;
+	sta->ft_over_ds = 1;
 	if (sta->wpa_sm) {
 		sta->auth_alg = WLAN_AUTH_FT;
 		return sta->wpa_sm;
@@ -1189,6 +1228,10 @@
 		.for_each_auth = hostapd_wpa_auth_for_each_auth,
 		.send_ether = hostapd_wpa_auth_send_ether,
 		.send_oui = hostapd_wpa_auth_send_oui,
+		.channel_info = hostapd_channel_info,
+#ifdef CONFIG_OCV
+		.get_sta_tx_params = hostapd_get_sta_tx_params,
+#endif /* CONFIG_OCV */
 #ifdef CONFIG_IEEE80211R_AP
 		.send_ft_action = hostapd_wpa_auth_send_ft_action,
 		.add_sta = hostapd_wpa_auth_add_sta,
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index b1cea1b..a349304 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -92,6 +92,9 @@
 #endif /* CONFIG_IEEE80211R_AP */
 	unsigned int is_wnmsleep:1;
 	unsigned int pmkid_set:1;
+#ifdef CONFIG_OCV
+	unsigned int ocv_enabled:1;
+#endif /* CONFIG_OCV */
 
 	u8 req_replay_counter[WPA_REPLAY_COUNTER_LEN];
 	int req_replay_counter_used;
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 253fe6e..3bcbef7 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -293,9 +293,13 @@
 			capab |= WPA_CAPABILITY_MFPR;
 	}
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+	if (conf->ocv)
+		capab |= WPA_CAPABILITY_OCVC;
+#endif /* CONFIG_OCV */
 #ifdef CONFIG_RSN_TESTING
 	if (rsn_testing)
-		capab |= BIT(8) | BIT(14) | BIT(15);
+		capab |= BIT(8) | BIT(15);
 #endif /* CONFIG_RSN_TESTING */
 	WPA_PUT_LE16(pos, capab);
 	pos += 2;
@@ -414,6 +418,10 @@
 			capab |= WPA_CAPABILITY_MFPR;
 	}
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+	if (conf->ocv)
+		capab |= WPA_CAPABILITY_OCVC;
+#endif /* CONFIG_OCV */
 	WPA_PUT_LE16(eid, capab);
 	eid += 2;
 
@@ -553,6 +561,19 @@
 	if (version == WPA_PROTO_RSN) {
 		res = wpa_parse_wpa_ie_rsn(wpa_ie, wpa_ie_len, &data);
 
+		if (wpa_key_mgmt_ft(data.key_mgmt) && !mdie &&
+		    !wpa_key_mgmt_only_ft(data.key_mgmt)) {
+			/* Workaround for some HP and Epson printers that seem
+			 * to incorrectly copy the FT-PSK + WPA-PSK AKMs from AP
+			 * advertised RSNE to Association Request frame. */
+			wpa_printf(MSG_DEBUG,
+				   "RSN: FT set in RSNE AKM but MDE is missing from "
+				   MACSTR
+				   " - ignore FT AKM(s) because there's also a non-FT AKM",
+				   MAC2STR(sm->addr));
+			data.key_mgmt &= ~WPA_KEY_MGMT_FT;
+		}
+
 		selector = RSN_AUTH_KEY_MGMT_UNSPEC_802_1X;
 		if (0) {
 		}
@@ -760,6 +781,17 @@
 	}
 #endif /* CONFIG_SAE */
 
+#ifdef CONFIG_OCV
+	if ((data.capabilities & WPA_CAPABILITY_OCVC) &&
+	    !(data.capabilities & WPA_CAPABILITY_MFPC)) {
+		wpa_printf(MSG_DEBUG,
+			   "Management frame protection required with OCV, but client did not enable it");
+		return WPA_MGMT_FRAME_PROTECTION_VIOLATION;
+	}
+	wpa_auth_set_ocv(sm, wpa_auth->conf.ocv &&
+			 (data.capabilities & WPA_CAPABILITY_OCVC));
+#endif /* CONFIG_OCV */
+
 	if (wpa_auth->conf.ieee80211w == NO_MGMT_FRAME_PROTECTION ||
 	    !(data.capabilities & WPA_CAPABILITY_MFPC))
 		sm->mgmt_frame_prot = 0;
@@ -995,6 +1027,15 @@
 	}
 #endif /* CONFIG_P2P */
 
+#ifdef CONFIG_OCV
+	if (pos[1] > RSN_SELECTOR_LEN + 2 &&
+	    RSN_SELECTOR_GET(pos + 2) == RSN_KEY_DATA_OCI) {
+		ie->oci = pos + 2 + RSN_SELECTOR_LEN;
+		ie->oci_len = pos[1] - RSN_SELECTOR_LEN;
+		return 0;
+	}
+#endif /* CONFIG_OCV */
+
 	return 0;
 }
 
@@ -1062,13 +1103,34 @@
 }
 
 
+#ifdef CONFIG_OCV
+
+void wpa_auth_set_ocv(struct wpa_state_machine *sm, int ocv)
+{
+	if (sm)
+		sm->ocv_enabled = ocv;
+}
+
+
+int wpa_auth_uses_ocv(struct wpa_state_machine *sm)
+{
+	return sm ? sm->ocv_enabled : 0;
+}
+
+#endif /* CONFIG_OCV */
+
+
 #ifdef CONFIG_OWE
 u8 * wpa_auth_write_assoc_resp_owe(struct wpa_state_machine *sm,
 				   u8 *pos, size_t max_len,
 				   const u8 *req_ies, size_t req_ies_len)
 {
 	int res;
-	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	struct wpa_auth_config *conf;
+
+	if (!sm)
+		return pos;
+	conf = &sm->wpa_auth->conf;
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (conf->own_ie_override_len) {
@@ -1082,7 +1144,7 @@
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
-	res = wpa_write_rsn_ie(&sm->wpa_auth->conf, pos, max_len,
+	res = wpa_write_rsn_ie(conf, pos, max_len,
 			       sm->pmksa ? sm->pmksa->pmkid : NULL);
 	if (res < 0)
 		return pos;
diff --git a/src/ap/wpa_auth_ie.h b/src/ap/wpa_auth_ie.h
index 73e4333..a38b206 100644
--- a/src/ap/wpa_auth_ie.h
+++ b/src/ap/wpa_auth_ie.h
@@ -33,6 +33,10 @@
 	const u8 *ip_addr_req;
 	const u8 *ip_addr_alloc;
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_OCV
+	const u8 *oci;
+	size_t oci_len;
+#endif /* CONFIG_OCV */
 
 	const u8 *osen;
 	size_t osen_len;