Revert "Revert "[wpa_supplicant] cumilative patch from commit 4b..."

Revert submission 28102966-revert-26533062-Supplicant_merge_June24-CUATTSRBBR

Fixed the regression issue (ag/28389573)
Bug: 329004037

Reverted changes: /q/submissionid:28102966-revert-26533062-Supplicant_merge_June24-CUATTSRBBR

Test: Turn ON/OFF SoftAp

Change-Id: Ie7ea1ee7f8b1311fce280907d37a2e321542f547
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 445d963..9aa61fa 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -163,6 +163,8 @@
 	/* Default to strict CRL checking. */
 	bss->check_crl_strict = 1;
 
+	bss->multi_ap_profile = MULTI_AP_PROFILE_2;
+
 #ifdef CONFIG_TESTING_OPTIONS
 	bss->sae_commit_status = -1;
 	bss->test_assoc_comeback_type = -1;
@@ -557,6 +559,10 @@
 
 	for (i = 0; i < num_servers; i++) {
 		os_free(servers[i].shared_secret);
+		os_free(servers[i].ca_cert);
+		os_free(servers[i].client_cert);
+		os_free(servers[i].private_key);
+		os_free(servers[i].private_key_passwd);
 	}
 	os_free(servers);
 }
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 69db16d..4f2164d 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -800,6 +800,14 @@
 #define BACKHAUL_BSS 1
 #define FRONTHAUL_BSS 2
 	int multi_ap; /* bitmap of BACKHAUL_BSS, FRONTHAUL_BSS */
+	int multi_ap_profile;
+	/* Multi-AP Profile-1 clients not allowed to connect */
+#define PROFILE1_CLIENT_ASSOC_DISALLOW BIT(0)
+	/* Multi-AP Profile-2 clients not allowed to connect */
+#define PROFILE2_CLIENT_ASSOC_DISALLOW BIT(1)
+	unsigned int multi_ap_client_disallow;
+	/* Primary VLAN ID to use in Multi-AP */
+	int multi_ap_vlanid;
 
 #ifdef CONFIG_AIRTIME_POLICY
 	unsigned int airtime_weight;
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 60d66e4..11fe39c 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -265,8 +265,8 @@
 }
 
 
-static bool hostapd_sta_is_link_sta(struct hostapd_data *hapd,
-				    struct sta_info *sta)
+bool hostapd_sta_is_link_sta(struct hostapd_data *hapd,
+			     struct sta_info *sta)
 {
 #ifdef CONFIG_IEEE80211BE
 	if (ap_sta_is_mld(hapd, sta) &&
@@ -572,12 +572,33 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+int hostapd_if_link_remove(struct hostapd_data *hapd,
+			   enum wpa_driver_if_type type,
+			   const char *ifname, u8 link_id)
+{
+	if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_remove)
+		return -1;
+
+	return hapd->driver->link_remove(hapd->drv_priv, type, ifname,
+					 hapd->mld_link_id);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int hostapd_if_remove(struct hostapd_data *hapd, enum wpa_driver_if_type type,
 		      const char *ifname)
 {
 	if (hapd->driver == NULL || hapd->drv_priv == NULL ||
 	    hapd->driver->if_remove == NULL)
 		return -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		return hostapd_if_link_remove(hapd, type, ifname,
+					      hapd->mld_link_id);
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->if_remove(hapd->drv_priv, type, ifname);
 }
 
@@ -629,7 +650,7 @@
 				    &cmode->he_capab[IEEE80211_MODE_AP] : NULL,
 				    cmode ?
 				    &cmode->eht_capab[IEEE80211_MODE_AP] :
-				    NULL))
+				    NULL, hostapd_get_punct_bitmap(hapd)))
 		return -1;
 
 	if (hapd->driver == NULL)
@@ -758,6 +779,8 @@
 struct wpa_scan_results * hostapd_driver_get_scan_results(
 	struct hostapd_data *hapd)
 {
+	if (hapd->driver && hapd->driver->get_scan_results)
+		return hapd->driver->get_scan_results(hapd->drv_priv, NULL);
 	if (hapd->driver && hapd->driver->get_scan_results2)
 		return hapd->driver->get_scan_results2(hapd->drv_priv);
 	return NULL;
@@ -840,7 +863,7 @@
 
 		link_id = hapd->mld_link_id;
 		if (ap_sta_is_mld(hapd, sta))
-			own_addr = hapd->mld_addr;
+			own_addr = hapd->mld->mld_addr;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -861,7 +884,7 @@
 		struct sta_info *sta = ap_get_sta(hapd, addr);
 
 		if (ap_sta_is_mld(hapd, sta))
-			own_addr = hapd->mld_addr;
+			own_addr = hapd->mld->mld_addr;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -919,7 +942,7 @@
 		sta = ap_get_sta(hapd, dst);
 
 		if (ap_sta_is_mld(hapd, sta)) {
-			own_addr = hapd->mld_addr;
+			own_addr = hapd->mld->mld_addr;
 			bssid = own_addr;
 		}
 #endif /* CONFIG_IEEE80211BE */
@@ -977,7 +1000,8 @@
 				    center_segment1,
 				    cmode->vht_capab,
 				    &cmode->he_capab[IEEE80211_MODE_AP],
-				    &cmode->eht_capab[IEEE80211_MODE_AP])) {
+				    &cmode->eht_capab[IEEE80211_MODE_AP],
+				    hostapd_get_punct_bitmap(hapd))) {
 		wpa_printf(MSG_ERROR, "Can't set freq params");
 		return -1;
 	}
@@ -999,7 +1023,8 @@
 int hostapd_drv_set_qos_map(struct hostapd_data *hapd,
 			    const u8 *qos_map_set, u8 qos_map_set_len)
 {
-	if (!hapd->driver || !hapd->driver->set_qos_map || !hapd->drv_priv)
+	if (!hapd->driver || !hapd->driver->set_qos_map || !hapd->drv_priv ||
+	    !(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_QOS_MAPPING))
 		return 0;
 	return hapd->driver->set_qos_map(hapd->drv_priv, qos_map_set,
 					 qos_map_set_len);
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 331b0ea..d7e79c8 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -26,6 +26,8 @@
 			       struct wpabuf *assocresp);
 int hostapd_reset_ap_wps_ie(struct hostapd_data *hapd);
 int hostapd_set_ap_wps_ie(struct hostapd_data *hapd);
+bool hostapd_sta_is_link_sta(struct hostapd_data *hapd,
+			     struct sta_info *sta);
 int hostapd_set_authorized(struct hostapd_data *hapd,
 			   struct sta_info *sta, int authorized);
 int hostapd_set_sta_flags(struct hostapd_data *hapd, struct sta_info *sta);
@@ -59,6 +61,9 @@
 		   const char *bridge, int use_existing);
 int hostapd_if_remove(struct hostapd_data *hapd, enum wpa_driver_if_type type,
 		      const char *ifname);
+int hostapd_if_link_remove(struct hostapd_data *hapd,
+			   enum wpa_driver_if_type type,
+			   const char *ifname, u8 link_id);
 int hostapd_set_ieee8021x(struct hostapd_data *hapd,
 			  struct wpa_bss_params *params);
 int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd,
@@ -388,9 +393,15 @@
 
 static inline int hostapd_drv_stop_ap(struct hostapd_data *hapd)
 {
+	int link_id = -1;
+
 	if (!hapd->driver || !hapd->driver->stop_ap || !hapd->drv_priv)
 		return 0;
-	return hapd->driver->stop_ap(hapd->drv_priv);
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+	return hapd->driver->stop_ap(hapd->drv_priv, link_id);
 }
 
 static inline int hostapd_drv_channel_info(struct hostapd_data *hapd,
@@ -443,15 +454,28 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #ifdef CONFIG_IEEE80211BE
+
 static inline int hostapd_drv_link_add(struct hostapd_data *hapd,
 				       u8 link_id, const u8 *addr)
 {
 	if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_add)
 		return -1;
 
-	return hapd->driver->link_add(hapd->drv_priv, link_id, addr);
+	return hapd->driver->link_add(hapd->drv_priv, link_id, addr, hapd);
 
 }
+
+static inline int hostapd_drv_link_sta_remove(struct hostapd_data *hapd,
+					      const u8 *addr)
+{
+	if (!hapd->conf->mld_ap || !hapd->driver || !hapd->drv_priv ||
+	    !hapd->driver->link_sta_remove)
+		return -1;
+
+	return hapd->driver->link_sta_remove(hapd->drv_priv, hapd->mld_link_id,
+					     addr);
+}
+
 #endif /* CONFIG_IEEE80211BE */
 
 #endif /* AP_DRV_OPS */
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 1488dcc..6ed4d06 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -107,13 +107,20 @@
 	struct radius_server_conf srv;
 	struct hostapd_bss_config *conf = hapd->conf;
 
-	if (hapd->mld_first_bss) {
+#ifdef CONFIG_IEEE80211BE
+	if (!hostapd_mld_is_first_bss(hapd)) {
+		struct hostapd_data *first;
+
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Using RADIUS server of the first BSS");
 
-		hapd->radius_srv = hapd->mld_first_bss->radius_srv;
+		first = hostapd_mld_get_first_bss(hapd);
+		if (!first)
+			return -1;
+		hapd->radius_srv = first->radius_srv;
 		return 0;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 	os_memset(&srv, 0, sizeof(srv));
 	srv.client_file = conf->radius_server_clients;
@@ -249,18 +256,25 @@
 
 int authsrv_init(struct hostapd_data *hapd)
 {
-	if (hapd->mld_first_bss) {
+#ifdef CONFIG_IEEE80211BE
+	if (!hostapd_mld_is_first_bss(hapd)) {
+		struct hostapd_data *first;
+
 		wpa_printf(MSG_DEBUG, "MLD: Using auth_serv of the first BSS");
 
+		first = hostapd_mld_get_first_bss(hapd);
+		if (!first)
+			return -1;
 #ifdef EAP_TLS_FUNCS
-		hapd->ssl_ctx = hapd->mld_first_bss->ssl_ctx;
+		hapd->ssl_ctx = first->ssl_ctx;
 #endif /* EAP_TLS_FUNCS */
-		hapd->eap_cfg = hapd->mld_first_bss->eap_cfg;
+		hapd->eap_cfg = first->eap_cfg;
 #ifdef EAP_SIM_DB
-		hapd->eap_sim_db_priv = hapd->mld_first_bss->eap_sim_db_priv;
+		hapd->eap_sim_db_priv = first->eap_sim_db_priv;
 #endif /* EAP_SIM_DB */
 		return 0;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 #ifdef EAP_TLS_FUNCS
 	if (hapd->conf->eap_server &&
@@ -376,7 +390,8 @@
 
 void authsrv_deinit(struct hostapd_data *hapd)
 {
-	if (hapd->mld_first_bss) {
+#ifdef CONFIG_IEEE80211BE
+	if (!hostapd_mld_is_first_bss(hapd)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Deinit auth_serv of a non-first BSS");
 
@@ -390,6 +405,7 @@
 #endif /* EAP_TLS_FUNCS */
 		return;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 #ifdef RADIUS_SERVER
 	radius_server_deinit(hapd->radius_srv);
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index e50f0a0..32865f6 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -960,7 +960,7 @@
 	 * We want to include the AP MLD ID in the response if it was
 	 * included in the request.
 	 */
-	probed_mld_id = mld_id != -1 ? mld_id : hapd->conf->mld_id;
+	probed_mld_id = mld_id != -1 ? mld_id : hostapd_get_mld_id(hapd);
 
 	for_each_mld_link(link, i, j, hapd->iface->interfaces,
 			  probed_mld_id) {
@@ -2616,7 +2616,8 @@
 				    hostapd_get_oper_centr_freq_seg1_idx(iconf),
 				    cmode->vht_capab,
 				    &cmode->he_capab[IEEE80211_MODE_AP],
-				    &cmode->eht_capab[IEEE80211_MODE_AP]) == 0)
+				    &cmode->eht_capab[IEEE80211_MODE_AP],
+				    hostapd_get_punct_bitmap(hapd)) == 0)
 		params.freq = &freq;
 
 	for (i = 0; i < hapd->iface->num_hw_features; i++) {
@@ -2675,8 +2676,7 @@
 			continue;
 
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->conf->mld_ap && other->bss[0]->conf->mld_ap &&
-		    hapd->conf->mld_id == other->bss[0]->conf->mld_id)
+		if (hostapd_is_ml_partner(hapd, other->bss[0]))
 			mld_ap = true;
 #endif /* CONFIG_IEEE80211BE */
 
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 5378671..eac0606 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -348,11 +348,15 @@
 
 	if (sta->supp_op_classes &&
 	    buflen - len > (unsigned) (17 + 2 * sta->supp_op_classes[0])) {
-		len += os_snprintf(buf + len, buflen - len, "supp_op_classes=");
+		res = os_snprintf(buf + len, buflen - len, "supp_op_classes=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
 		len += wpa_snprintf_hex(buf + len, buflen - len,
 					sta->supp_op_classes + 1,
 					sta->supp_op_classes[0]);
-		len += os_snprintf(buf + len, buflen - len, "\n");
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
 	}
 
 	if (sta->power_capab) {
@@ -364,6 +368,34 @@
 			len += ret;
 	}
 
+#ifdef CONFIG_IEEE80211AX
+	if ((sta->flags & WLAN_STA_HE) && sta->he_capab) {
+		res = os_snprintf(buf + len, buflen - len, "he_capab=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+		len += wpa_snprintf_hex(buf + len, buflen - len,
+					(const u8 *) sta->he_capab,
+					sta->he_capab_len);
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+	}
+#endif /* CONFIG_IEEE80211AX */
+
+#ifdef CONFIG_IEEE80211BE
+	if ((sta->flags & WLAN_STA_EHT) && sta->eht_capab) {
+		res = os_snprintf(buf + len, buflen - len, "eht_capab=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+		len += wpa_snprintf_hex(buf + len, buflen - len,
+					(const u8 *) sta->eht_capab,
+					sta->eht_capab_len);
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_IEEE80211AC
 	if ((sta->flags & WLAN_STA_VHT) && sta->vht_capabilities) {
 		res = os_snprintf(buf + len, buflen - len,
@@ -372,6 +404,16 @@
 					       vht_capabilities_info));
 		if (!os_snprintf_error(buflen - len, res))
 			len += res;
+
+		res = os_snprintf(buf + len, buflen - len, "vht_capab=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
+		len += wpa_snprintf_hex(buf + len, buflen - len,
+					(const u8 *) sta->vht_capabilities,
+					sizeof(*sta->vht_capabilities));
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
 	}
 #endif /* CONFIG_IEEE80211AC */
 
@@ -386,11 +428,15 @@
 
 	if (sta->ext_capability &&
 	    buflen - len > (unsigned) (11 + 2 * sta->ext_capability[0])) {
-		len += os_snprintf(buf + len, buflen - len, "ext_capab=");
+		res = os_snprintf(buf + len, buflen - len, "ext_capab=");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
 		len += wpa_snprintf_hex(buf + len, buflen - len,
 					sta->ext_capability + 1,
 					sta->ext_capability[0]);
-		len += os_snprintf(buf + len, buflen - len, "\n");
+		res = os_snprintf(buf + len, buflen - len, "\n");
+		if (!os_snprintf_error(buflen - len, res))
+			len += res;
 	}
 
 	if (sta->flags & WLAN_STA_WDS && sta->ifname_wds) {
@@ -419,6 +465,21 @@
 			len += ret;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (sta->mld_info.mld_sta) {
+		for (i = 0; i < MAX_NUM_MLD_LINKS; ++i) {
+			if (!sta->mld_info.links[i].valid)
+				continue;
+			ret = os_snprintf(
+				buf + len, buflen - len,
+				"peer_addr[%d]=" MACSTR "\n",
+				i, MAC2STR(sta->mld_info.links[i].peer_addr));
+			if (!os_snprintf_error(buflen - len, ret))
+				len += ret;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return len;
 }
 
@@ -966,8 +1027,8 @@
 					  "mld_addr[%d]=" MACSTR "\n"
 					  "mld_id[%d]=%d\n"
 					  "mld_link_id[%d]=%d\n",
-					  (int) i, MAC2STR(bss->mld_addr),
-					  (int) i, bss->conf->mld_id,
+					  (int) i, MAC2STR(bss->mld->mld_addr),
+					  (int) i, hostapd_get_mld_id(bss),
 					  (int) i, bss->mld_link_id);
 			if (os_snprintf_error(buflen - len, ret))
 				return len;
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 5e4c810..af9dc16 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -14,6 +14,7 @@
 #include "common/hw_features_common.h"
 #include "common/wpa_ctrl.h"
 #include "hostapd.h"
+#include "beacon.h"
 #include "ap_drv_ops.h"
 #include "drivers/driver.h"
 #include "dfs.h"
@@ -972,6 +973,7 @@
 	struct csa_settings csa_settings;
 	u8 new_vht_oper_chwidth;
 	unsigned int i;
+	unsigned int num_err = 0;
 
 	wpa_printf(MSG_DEBUG, "DFS will switch to a new channel %d", channel);
 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_NEW_CHANNEL
@@ -1009,7 +1011,8 @@
 				      oper_centr_freq_seg1_idx,
 				      cmode->vht_capab,
 				      &cmode->he_capab[ieee80211_mode],
-				      &cmode->eht_capab[ieee80211_mode]);
+				      &cmode->eht_capab[ieee80211_mode],
+				      hostapd_get_punct_bitmap(iface->bss[0]));
 
 	if (err) {
 		wpa_printf(MSG_ERROR,
@@ -1021,10 +1024,10 @@
 	for (i = 0; i < iface->num_bss; i++) {
 		err = hostapd_switch_channel(iface->bss[i], &csa_settings);
 		if (err)
-			break;
+			num_err++;
 	}
 
-	if (err) {
+	if (num_err == iface->num_bss) {
 		wpa_printf(MSG_WARNING,
 			   "DFS failed to schedule CSA (%d) - trying fallback",
 			   err);
@@ -1141,14 +1144,23 @@
 			     int cf1, int cf2)
 {
 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, DFS_EVENT_CAC_COMPLETED
-		"success=%d freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
-		success, freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
+		"success=%d freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d radar_detected=%d",
+		success, freq, ht_enabled, chan_offset, chan_width, cf1, cf2,
+		iface->radar_detected);
 
 	if (success) {
 		/* Complete iface/ap configuration */
 		if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD) {
-			/* Complete AP configuration for the first bring up. */
-			if (iface->state != HAPD_IFACE_ENABLED)
+			/* Complete AP configuration for the first bring up. If
+			 * a radar was detected in this channel, interface setup
+			 * will be handled in
+			 * 1. hostapd_event_ch_switch() if switching to a
+			 *    non-DFS channel
+			 * 2. on next CAC complete event if switching to another
+			 *    DFS channel.
+			 */
+			if (iface->state != HAPD_IFACE_ENABLED &&
+			    !iface->radar_detected)
 				hostapd_setup_interface_complete(iface, 0);
 			else
 				iface->cac_started = 0;
@@ -1193,6 +1205,7 @@
 		hostapd_dfs_update_background_chain(iface);
 	}
 
+	iface->radar_detected = false;
 	return 0;
 }
 
@@ -1436,6 +1449,8 @@
 		"freq=%d ht_enabled=%d chan_offset=%d chan_width=%d cf1=%d cf2=%d",
 		freq, ht_enabled, chan_offset, chan_width, cf1, cf2);
 
+	iface->radar_detected = true;
+
 	/* Proceed only if DFS is not offloaded to the driver */
 	if (iface->drv_flags & WPA_DRIVER_FLAGS_DFS_OFFLOAD)
 		return 0;
@@ -1529,9 +1544,17 @@
 		iface->radar_background.cac_started = 1;
 	} else {
 		/* This is called when the driver indicates that an offloaded
-		 * DFS has started CAC. */
+		 * DFS has started CAC. radar_detected might be set for previous
+		 * DFS channel. Clear it for this new CAC process. */
 		hostapd_set_state(iface, HAPD_IFACE_DFS);
 		iface->cac_started = 1;
+
+		/* Clear radar_detected in case it is for the previous
+		 * frequency. Also remove disabled link's information in RNR
+		 * element from other links. */
+		iface->radar_detected = false;
+		if (iface->interfaces && iface->interfaces->count > 1)
+			ieee802_11_set_beacons(iface);
 	}
 	/* TODO: How to check CAC time for ETSI weather channels? */
 	iface->dfs_cac_ms = 60000;
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 3f89bc2..d1bffa8 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -3941,6 +3941,7 @@
 	eloop_register_timeout(100, 0, hostapd_dpp_push_button_expire,
 			       hapd, NULL);
 
+	wpa_msg(hapd->msg_ctx, MSG_INFO, DPP_EVENT_PB_STATUS "started");
 	return 0;
 }
 
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 533cc54..b0fcd1c 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -517,7 +517,7 @@
 		if (ap_sta_is_mld(hapd, sta)) {
 			wpa_printf(MSG_DEBUG,
 				   "MLD: Set ML info in RSN Authenticator");
-			wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+			wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld->mld_addr,
 					     sta->mld_assoc_link_id,
 					     &sta->mld_info);
 		}
@@ -910,6 +910,54 @@
 }
 
 
+static void hostapd_remove_sta(struct hostapd_data *hapd, struct sta_info *sta)
+{
+	ap_sta_set_authorized(hapd, sta, 0);
+	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC);
+	hostapd_set_sta_flags(hapd, sta);
+	wpa_auth_sm_event(sta->wpa_sm, WPA_DISASSOC);
+	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
+	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
+	ap_free_sta(hapd, sta);
+}
+
+
+#ifdef CONFIG_IEEE80211BE
+static void hostapd_notif_disassoc_mld(struct hostapd_data *assoc_hapd,
+				       struct sta_info *sta,
+				       const u8 *addr)
+{
+	unsigned int link_id, i;
+	struct hostapd_data *tmp_hapd;
+	struct hapd_interfaces *interfaces = assoc_hapd->iface->interfaces;
+
+	/* Remove STA entry in non-assoc links */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sta->mld_info.links[link_id].valid)
+			continue;
+
+		for (i = 0; i < interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+
+			tmp_hapd = interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    assoc_hapd == tmp_hapd ||
+			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			tmp_sta = ap_get_sta(tmp_hapd, addr);
+			if (tmp_sta)
+				ap_free_sta(tmp_hapd, tmp_sta);
+		}
+	}
+
+	/* Remove STA in assoc link */
+	hostapd_remove_sta(assoc_hapd, sta);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr)
 {
 	struct sta_info *sta;
@@ -931,6 +979,50 @@
 		       HOSTAPD_LEVEL_INFO, "disassociated");
 
 	sta = ap_get_sta(hapd, addr);
+#ifdef CONFIG_IEEE80211BE
+	if (hostapd_is_mld_ap(hapd)) {
+		struct hostapd_data *assoc_hapd;
+		unsigned int i;
+
+		if (!sta) {
+			/* Find non-MLO cases from any of the affiliated AP
+			 * links. */
+			for (i = 0; i < hapd->iface->interfaces->count; ++i) {
+				struct hostapd_iface *h =
+					hapd->iface->interfaces->iface[i];
+				struct hostapd_data *h_hapd = h->bss[0];
+				struct hostapd_bss_config *hconf = h_hapd->conf;
+
+				if (!hconf->mld_ap ||
+				    hconf->mld_id != hapd->conf->mld_id)
+					continue;
+
+				sta = ap_get_sta(h_hapd, addr);
+				if (sta) {
+					if (!sta->mld_info.mld_sta) {
+						hapd = h_hapd;
+						goto legacy;
+					}
+					break;
+				}
+			}
+		} else if (!sta->mld_info.mld_sta) {
+			goto legacy;
+		}
+		if (!sta) {
+			wpa_printf(MSG_DEBUG,
+			   "Disassociation notification for unknown STA "
+			   MACSTR, MAC2STR(addr));
+			return;
+		}
+		sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+		if (sta)
+			hostapd_notif_disassoc_mld(assoc_hapd, sta, addr);
+		return;
+	}
+
+legacy:
+#endif /* CONFIG_IEEE80211BE */
 	if (sta == NULL) {
 		wpa_printf(MSG_DEBUG,
 			   "Disassociation notification for unknown STA "
@@ -938,13 +1030,7 @@
 		return;
 	}
 
-	ap_sta_set_authorized(hapd, sta, 0);
-	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC);
-	hostapd_set_sta_flags(hapd, sta);
-	wpa_auth_sm_event(sta->wpa_sm, WPA_DISASSOC);
-	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
-	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
-	ap_free_sta(hapd, sta);
+	hostapd_remove_sta(hapd, sta);
 }
 
 
@@ -1663,6 +1749,34 @@
 }
 
 
+static struct hostapd_data *
+switch_link_scan(struct hostapd_data *hapd, u64 scan_cookie)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && scan_cookie != 0) {
+		unsigned int i;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct hostapd_iface *h;
+			struct hostapd_data *h_hapd;
+
+			h = hapd->iface->interfaces->iface[i];
+			h_hapd = h->bss[0];
+			if (!hostapd_is_ml_partner(hapd, h_hapd))
+				continue;
+
+			if (h_hapd->scan_cookie == scan_cookie) {
+				h_hapd->scan_cookie = 0;
+				return h_hapd;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return hapd;
+}
+
+
 #define HAPD_BROADCAST ((struct hostapd_data *) -1)
 
 static struct hostapd_data * get_hapd_bssid(struct hostapd_iface *iface,
@@ -1731,7 +1845,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->conf->mld_ap &&
-	    ether_addr_equal(hapd->mld_addr, bssid))
+	    ether_addr_equal(hapd->mld->mld_addr, bssid))
 		is_mld = true;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1803,7 +1917,8 @@
 		hapd = tmp_hapd;
 #ifdef CONFIG_IEEE80211BE
 	} else if (hapd->conf->mld_ap &&
-		   ether_addr_equal(hapd->mld_addr, get_hdr_bssid(hdr, len))) {
+		   ether_addr_equal(hapd->mld->mld_addr,
+				    get_hdr_bssid(hdr, len))) {
 		/* AP MLD address match - use hapd pointer as-is */
 #endif /* CONFIG_IEEE80211BE */
 	} else {
@@ -1878,10 +1993,8 @@
 		struct hostapd_iface *h =
 			hapd->iface->interfaces->iface[i];
 		struct hostapd_data *h_hapd = h->bss[0];
-		struct hostapd_bss_config *hconf = h_hapd->conf;
 
-		if (!hconf->mld_ap ||
-		    hconf->mld_id != hapd->conf->mld_id)
+		if (!hostapd_is_ml_partner(h_hapd, hapd))
 			continue;
 
 		h_hapd = hostapd_find_by_sta(h, src, false);
@@ -2292,8 +2405,29 @@
 		michael_mic_failure(hapd, data->michael_mic_failure.src, 1);
 		break;
 	case EVENT_SCAN_RESULTS:
+#ifdef NEED_AP_MLME
+		if (data)
+			hapd = switch_link_scan(hapd,
+						data->scan_info.scan_cookie);
+#endif /* NEED_AP_MLME */
 		if (hapd->iface->scan_cb)
 			hapd->iface->scan_cb(hapd->iface);
+#ifdef CONFIG_IEEE80211BE
+		if (!hapd->iface->scan_cb && hapd->conf->mld_ap) {
+			/* Other links may be waiting for HT scan result */
+			unsigned int i;
+
+			for (i = 0; i < hapd->iface->interfaces->count; i++) {
+				struct hostapd_iface *h =
+					hapd->iface->interfaces->iface[i];
+				struct hostapd_data *h_hapd = h->bss[0];
+
+				if (hostapd_is_ml_partner(hapd, h_hapd) &&
+				    h_hapd->iface->scan_cb)
+					h_hapd->iface->scan_cb(h_hapd->iface);
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		break;
 	case EVENT_WPS_BUTTON_PUSHED:
 		hostapd_wps_button_pushed(hapd, NULL);
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index ddbcabc..56bac45 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -184,6 +184,8 @@
 		hostapd_set_generic_elem(hapd, (u8 *) "", 0);
 	}
 
+	hostapd_neighbor_sync_own_report(hapd);
+
 	ieee802_11_set_beacon(hapd);
 	hostapd_update_wps(hapd);
 
@@ -395,25 +397,6 @@
 #endif /* CONFIG_WEP */
 
 
-static void hostapd_clear_drv_priv(struct hostapd_data *hapd)
-{
-	unsigned int i;
-
-	for (i = 0; i < hapd->iface->interfaces->count; i++) {
-		struct hostapd_iface *iface = hapd->iface->interfaces->iface[i];
-
-		if (hapd->iface == iface || !iface)
-			continue;
-
-		if (iface->bss && iface->bss[0] &&
-		    iface->bss[0]->mld_first_bss == hapd)
-			iface->bss[0]->drv_priv = NULL;
-	}
-
-	hapd->drv_priv = NULL;
-}
-
-
 #ifdef CONFIG_IEEE80211BE
 #ifdef CONFIG_TESTING_OPTIONS
 
@@ -435,6 +418,7 @@
 	ieee802_11_set_beacon(hapd);
 
 	if (!hapd->eht_mld_link_removal_count) {
+		hostapd_free_link_stas(hapd);
 		hostapd_disable_iface(hapd->iface);
 		return;
 	}
@@ -496,7 +480,8 @@
 	vlan_deinit(hapd);
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
-	if (!hapd->mld_first_bss) {
+	if (hostapd_mld_is_first_bss(hapd)) {
+#ifdef CONFIG_IEEE80211BE
 		struct hapd_interfaces *ifaces = hapd->iface->interfaces;
 		size_t i;
 
@@ -515,6 +500,7 @@
 					h->radius_das = NULL;
 			}
 		}
+#endif /* CONFIG_IEEE80211BE */
 		radius_client_deinit(hapd->radius);
 		radius_das_deinit(hapd->radius_das);
 	}
@@ -548,10 +534,20 @@
 			 * driver wrapper may have removed its internal instance
 			 * and hapd->drv_priv is not valid anymore.
 			 */
-			hostapd_clear_drv_priv(hapd);
+			hapd->drv_priv = NULL;
 		}
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	/* If the interface was not added as well as it is not the first BSS,
+	 * at least the link should be removed here since deinit will take care
+	 * of only the first BSS. */
+	if (hapd->conf->mld_ap && !hapd->interface_added &&
+	    hapd->iface->bss[0] != hapd)
+		hostapd_if_link_remove(hapd, WPA_IF_AP_BSS, hapd->conf->iface,
+				       hapd->mld_link_id);
+#endif /* CONFIG_IEEE80211BE */
+
 	wpabuf_free(hapd->time_adv);
 	hapd->time_adv = NULL;
 
@@ -614,6 +610,41 @@
 }
 
 
+/* hostapd_bss_link_deinit - Per-BSS ML cleanup (deinitialization)
+ * @hapd: Pointer to BSS data
+ *
+ * This function is used to unlink the BSS from the AP MLD.
+ * If the BSS being removed is the first link, the next link becomes the first
+ * link.
+ */
+static void hostapd_bss_link_deinit(struct hostapd_data *hapd)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (!hapd->conf || !hapd->conf->mld_ap)
+		return;
+
+	if (!hapd->mld->num_links)
+		return;
+
+	/* If not started, not yet linked to the MLD. However, the first
+	 * BSS is always linked since it is linked during driver_init(), and
+	 * hence, need to remove it from the AP MLD.
+	 */
+	if (!hapd->started && hapd->iface->bss[0] != hapd)
+		return;
+
+	/* The first BSS can also be only linked when at least driver_init() is
+	 * executed. But if previous interface fails, it is not, and hence,
+	 * safe to skip.
+	 */
+	if (hapd->iface->bss[0] == hapd && !hapd->drv_priv)
+		return;
+
+	hostapd_mld_remove_link(hapd);
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
 /**
  * hostapd_cleanup - Per-BSS cleanup (deinitialization)
  * @hapd: Pointer to BSS data
@@ -654,6 +685,7 @@
 void hostapd_cleanup_iface_partial(struct hostapd_iface *iface)
 {
 	wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
+	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
 #ifdef NEED_AP_MLME
 	hostapd_stop_setup_timers(iface);
 #endif /* NEED_AP_MLME */
@@ -683,7 +715,6 @@
 static void hostapd_cleanup_iface(struct hostapd_iface *iface)
 {
 	wpa_printf(MSG_DEBUG, "%s(%p)", __func__, iface);
-	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
 	eloop_cancel_timeout(hostapd_interface_setup_failure_handler, iface,
 			     NULL);
 
@@ -1303,7 +1334,7 @@
 	u8 if_addr[ETH_ALEN];
 	int flush_old_stations = 1;
 
-	if (hapd->mld_first_bss)
+	if (!hostapd_mld_is_first_bss(hapd))
 		wpa_printf(MSG_DEBUG,
 			   "MLD: %s: Setting non-first BSS", __func__);
 
@@ -1465,7 +1496,7 @@
 	}
 #endif /* CONFIG_SQLITE */
 
-	if (!hapd->mld_first_bss) {
+	if (hostapd_mld_is_first_bss(hapd)) {
 		hapd->radius = radius_client_init(hapd, conf->radius);
 		if (!hapd->radius) {
 			wpa_printf(MSG_ERROR,
@@ -1498,10 +1529,17 @@
 			}
 		}
 	} else {
+#ifdef CONFIG_IEEE80211BE
+		struct hostapd_data *f_bss;
+
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Using RADIUS client of the first BSS");
-		hapd->radius = hapd->mld_first_bss->radius;
-		hapd->radius_das = hapd->mld_first_bss->radius_das;
+		f_bss = hostapd_mld_get_first_bss(hapd);
+		if (!f_bss)
+			return -1;
+		hapd->radius = f_bss->radius;
+		hapd->radius_das = f_bss->radius_das;
+#endif /* CONFIG_IEEE80211BE */
 	}
 #endif /* CONFIG_NO_RADIUS */
 
@@ -1546,6 +1584,7 @@
 		wpa_printf(MSG_ERROR, "GAS server initialization failed");
 		return -1;
 	}
+#endif /* CONFIG_INTERWORKING */
 
 	if (conf->qos_map_set_len &&
 	    hostapd_drv_set_qos_map(hapd, conf->qos_map_set,
@@ -1553,7 +1592,6 @@
 		wpa_printf(MSG_ERROR, "Failed to initialize QoS Map");
 		return -1;
 	}
-#endif /* CONFIG_INTERWORKING */
 
 	if (conf->bss_load_update_period && bss_load_update_init(hapd)) {
 		wpa_printf(MSG_ERROR, "BSS Load initialization failed");
@@ -1739,6 +1777,7 @@
 static void hostapd_no_ir_cleanup(struct hostapd_data *bss)
 {
 	hostapd_bss_deinit_no_free(bss);
+	hostapd_bss_link_deinit(bss);
 	hostapd_free_hapd_data(bss);
 	hostapd_cleanup_iface_partial(bss->iface);
 }
@@ -2035,10 +2074,11 @@
 	} else {
 		int ret;
 
-		if (iface->conf->acs) {
+		if (iface->conf->acs && !iface->is_ch_switch_dfs) {
 			iface->freq = 0;
 			iface->conf->channel = 0;
 		}
+		iface->is_ch_switch_dfs = false;
 
 		ret = configured_fixed_chan_to_freq(iface);
 		if (ret < 0)
@@ -2797,6 +2837,8 @@
 		hapd->rad_attr_db = NULL;
 	}
 #endif /* CONFIG_SQLITE */
+
+	hostapd_bss_link_deinit(hapd);
 	hostapd_cleanup(hapd);
 }
 
@@ -2835,6 +2877,40 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+
+static void hostapd_mld_ref_inc(struct hostapd_mld *mld)
+{
+	if (!mld)
+		return;
+
+	if (mld->refcount == HOSTAPD_MLD_MAX_REF_COUNT) {
+		wpa_printf(MSG_ERROR, "AP MLD %s: Ref count overflow",
+			   mld->name);
+		return;
+	}
+
+	mld->refcount++;
+}
+
+
+static void hostapd_mld_ref_dec(struct hostapd_mld *mld)
+{
+	if (!mld)
+		return;
+
+	if (!mld->refcount) {
+		wpa_printf(MSG_ERROR, "AP MLD %s: Ref count underflow",
+			   mld->name);
+		return;
+	}
+
+	mld->refcount--;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
 void hostapd_interface_free(struct hostapd_iface *iface)
 {
 	size_t j;
@@ -2842,6 +2918,10 @@
 	for (j = 0; j < iface->num_bss; j++) {
 		if (!iface->bss)
 			break;
+#ifdef CONFIG_IEEE80211BE
+		if (iface->bss[j])
+			hostapd_mld_ref_dec(iface->bss[j]->mld);
+#endif /* CONFIG_IEEE80211BE */
 		wpa_printf(MSG_DEBUG, "%s: free hapd %p",
 			   __func__, iface->bss[j]);
 		os_free(iface->bss[j]);
@@ -2864,6 +2944,157 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static void hostapd_bss_alloc_link_id(struct hostapd_data *hapd)
+{
+	hapd->mld_link_id = hapd->mld->next_link_id++;
+	wpa_printf(MSG_DEBUG, "AP MLD: %s: Link ID %d assigned.",
+		   hapd->mld->name, hapd->mld_link_id);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
+static void hostapd_bss_setup_multi_link(struct hostapd_data *hapd,
+					 struct hapd_interfaces *interfaces)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_mld *mld, **all_mld;
+	struct hostapd_bss_config *conf;
+	size_t i;
+
+	conf = hapd->conf;
+
+	if (!hapd->iconf || !hapd->iconf->ieee80211be || !conf->mld_ap ||
+	    conf->disable_11be)
+		return;
+
+	for (i = 0; i < interfaces->mld_count; i++) {
+		mld = interfaces->mld[i];
+
+		if (!mld || os_strcmp(conf->iface, mld->name) != 0)
+			continue;
+
+		hapd->mld = mld;
+		hostapd_mld_ref_inc(mld);
+		hostapd_bss_alloc_link_id(hapd);
+		break;
+	}
+
+	if (hapd->mld)
+		return;
+
+	mld = os_zalloc(sizeof(struct hostapd_mld));
+	if (!mld)
+		goto fail;
+
+	os_strlcpy(mld->name, conf->iface, sizeof(conf->iface));
+	dl_list_init(&mld->links);
+
+	wpa_printf(MSG_DEBUG, "AP MLD %s created", mld->name);
+
+	hapd->mld = mld;
+	hostapd_mld_ref_inc(mld);
+	hostapd_bss_alloc_link_id(hapd);
+
+	all_mld = os_realloc_array(interfaces->mld, interfaces->mld_count + 1,
+				   sizeof(struct hostapd_mld *));
+	if (!all_mld)
+		goto fail;
+
+	interfaces->mld = all_mld;
+	interfaces->mld[interfaces->mld_count] = mld;
+	interfaces->mld_count++;
+
+	return;
+fail:
+	if (!mld)
+		return;
+
+	wpa_printf(MSG_DEBUG, "AP MLD %s: free mld %p", mld->name, mld);
+	os_free(mld);
+	hapd->mld = NULL;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+static void hostapd_cleanup_unused_mlds(struct hapd_interfaces *interfaces)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_mld *mld, **all_mld;
+	size_t i, j, num_mlds;
+	bool forced_remove, remove;
+
+	if (!interfaces->mld)
+		return;
+
+	num_mlds = interfaces->mld_count;
+
+	for (i = 0; i < interfaces->mld_count; i++) {
+		mld = interfaces->mld[i];
+		if (!mld)
+			continue;
+
+		remove = false;
+		forced_remove = false;
+
+		if (!mld->refcount)
+			remove = true;
+
+		/* If MLD is still being referenced but the number of interfaces
+		 * is zero, it is safe to force its deletion. Normally, this
+		 * should not happen but even if it does, let us free the
+		 * memory.
+		 */
+		if (!remove && !interfaces->count)
+			forced_remove = true;
+
+		if (!remove && !forced_remove)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "AP MLD %s: Freed%s", mld->name,
+			   forced_remove ? " (forced)" : "");
+		os_free(mld);
+		interfaces->mld[i] = NULL;
+		num_mlds--;
+	}
+
+	if (!num_mlds) {
+		interfaces->mld_count = 0;
+		os_free(interfaces->mld);
+		interfaces->mld = NULL;
+		return;
+	}
+
+	all_mld = os_zalloc(num_mlds * sizeof(struct hostapd_mld *));
+	if (!all_mld) {
+		wpa_printf(MSG_ERROR,
+			   "AP MLD: Failed to re-allocate the MLDs. Expect issues");
+		return;
+	}
+
+	for (i = 0, j = 0; i < interfaces->mld_count; i++) {
+		mld = interfaces->mld[i];
+		if (!mld)
+			continue;
+
+		all_mld[j++] = mld;
+	}
+
+	/* This should not happen */
+	if (j != num_mlds) {
+		wpa_printf(MSG_DEBUG,
+			   "AP MLD: Some error occurred while reallocating MLDs. Expect issues.");
+		os_free(all_mld);
+		return;
+	}
+
+	os_free(interfaces->mld);
+	interfaces->mld = all_mld;
+	interfaces->mld_count = num_mlds;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
 /**
  * hostapd_init - Allocate and initialize per-interface data
  * @config_file: Path to the configuration file
@@ -2907,8 +3138,10 @@
 		if (hapd == NULL)
 			goto fail;
 		hapd->msg_ctx = hapd;
+		hostapd_bss_setup_multi_link(hapd, interfaces);
 	}
 
+	hapd_iface->is_ch_switch_dfs = false;
 	return hapd_iface;
 
 fail:
@@ -3028,6 +3261,8 @@
 		iface->conf->last_bss = bss;
 		iface->bss[iface->num_bss] = hapd;
 		hapd->msg_ctx = hapd;
+		hostapd_bss_setup_multi_link(hapd, interfaces);
+
 
 		bss_idx = iface->num_bss++;
 		conf->num_bss--;
@@ -3061,6 +3296,37 @@
 }
 
 
+static void hostapd_cleanup_driver(const struct wpa_driver_ops *driver,
+				   void *drv_priv, struct hostapd_iface *iface)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (!driver || !driver->hapd_deinit || !drv_priv)
+		return;
+
+	/* In case of non-ML operation, de-init. But if ML operation exist,
+	 * even if that's the last BSS in the interface, the driver (drv) could
+	 * be in use for a different AP MLD. Hence, need to check if drv is
+	 * still being used by some other BSS before de-initiallizing. */
+	if (!iface->bss[0]->conf->mld_ap) {
+		driver->hapd_deinit(drv_priv);
+	} else if (hostapd_mld_is_first_bss(iface->bss[0]) &&
+		   driver->is_drv_shared &&
+		   !driver->is_drv_shared(drv_priv, iface->bss[0])) {
+		driver->hapd_deinit(drv_priv);
+	} else if (hostapd_if_link_remove(iface->bss[0],
+					  WPA_IF_AP_BSS,
+					  iface->bss[0]->conf->iface,
+					  iface->bss[0]->mld_link_id)) {
+		wpa_printf(MSG_WARNING, "Failed to remove BSS interface %s",
+			   iface->bss[0]->conf->iface);
+	}
+#else /* CONFIG_IEEE80211BE */
+	driver->hapd_deinit(drv_priv);
+#endif /* CONFIG_IEEE80211BE */
+	iface->bss[0]->drv_priv = NULL;
+}
+
+
 void hostapd_interface_deinit_free(struct hostapd_iface *iface)
 {
 	const struct wpa_driver_ops *driver;
@@ -3077,11 +3343,7 @@
 	hostapd_interface_deinit(iface);
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
-	if (driver && driver->hapd_deinit && drv_priv) {
-		if (!iface->bss[0]->mld_first_bss)
-			driver->hapd_deinit(drv_priv);
-		hostapd_clear_drv_priv(iface->bss[0]);
-	}
+	hostapd_cleanup_driver(driver, drv_priv, iface);
 	hostapd_interface_free(iface);
 }
 
@@ -3094,15 +3356,16 @@
 
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
+
+	hostapd_cleanup_driver(driver, drv_priv, hapd_iface);
+
 	if (driver && driver->hapd_deinit && drv_priv) {
-		if (!hapd_iface->bss[0]->mld_first_bss)
-			driver->hapd_deinit(drv_priv);
 		for (j = 0; j < hapd_iface->num_bss; j++) {
 			wpa_printf(MSG_DEBUG, "%s:bss[%d]->drv_priv=%p",
 				   __func__, (int) j,
 				   hapd_iface->bss[j]->drv_priv);
 			if (hapd_iface->bss[j]->drv_priv == drv_priv) {
-				hostapd_clear_drv_priv(hapd_iface->bss[j]);
+				hapd_iface->bss[j]->drv_priv = NULL;
 				hapd_iface->extended_capa = NULL;
 				hapd_iface->extended_capa_mask = NULL;
 				hapd_iface->extended_capa_len = 0;
@@ -3112,6 +3375,22 @@
 }
 
 
+static void hostapd_refresh_all_iface_beacons(struct hostapd_iface *hapd_iface)
+{
+	size_t j;
+
+	if (!hapd_iface->interfaces || hapd_iface->interfaces->count <= 1)
+		return;
+
+	for (j = 0; j < hapd_iface->interfaces->count; j++) {
+		if (hapd_iface->interfaces->iface[j] == hapd_iface)
+			continue;
+
+		ieee802_11_update_beacons(hapd_iface->interfaces->iface[j]);
+	}
+}
+
+
 int hostapd_enable_iface(struct hostapd_iface *hapd_iface)
 {
 	size_t j;
@@ -3150,6 +3429,8 @@
 		return -1;
 	}
 
+	hostapd_refresh_all_iface_beacons(hapd_iface);
+
 	return 0;
 }
 
@@ -3207,31 +3488,6 @@
 		return -1;
 	}
 
-#ifdef CONFIG_IEEE80211BE
-	if (hapd_iface->bss[0]->conf->mld_ap &&
-	    !hapd_iface->bss[0]->mld_first_bss) {
-		/* Do not allow mld_first_bss disabling before other BSSs */
-		for (j = 0; j < hapd_iface->interfaces->count; ++j) {
-			struct hostapd_iface *h_iface =
-				hapd_iface->interfaces->iface[j];
-			struct hostapd_data *h_hapd = h_iface->bss[0];
-			struct hostapd_bss_config *h_conf = h_hapd->conf;
-
-			if (!h_conf->mld_ap ||
-			    h_conf->mld_id !=
-			    hapd_iface->bss[0]->conf->mld_id ||
-			    h_iface == hapd_iface)
-				continue;
-
-			if (h_iface->state != HAPD_IFACE_DISABLED) {
-				wpa_printf(MSG_INFO,
-					   "Do not allow disable mld_first_bss first");
-				return -1;
-			}
-		}
-	}
-#endif /* CONFIG_IEEE80211BE */
-
 	wpa_msg(hapd_iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 	driver = hapd_iface->bss[0]->driver;
 	drv_priv = hapd_iface->bss[0]->drv_priv;
@@ -3249,6 +3505,7 @@
 	for (j = 0; j < hapd_iface->num_bss; j++) {
 		struct hostapd_data *hapd = hapd_iface->bss[j];
 		hostapd_bss_deinit_no_free(hapd);
+		hostapd_bss_link_deinit(hapd);
 		hostapd_free_hapd_data(hapd);
 	}
 
@@ -3262,6 +3519,7 @@
 	wpa_printf(MSG_DEBUG, "Interface %s disabled",
 		   hapd_iface->bss[0]->conf->iface);
 	hostapd_set_state(hapd_iface, HAPD_IFACE_DISABLED);
+	hostapd_refresh_all_iface_beacons(hapd_iface);
 	return 0;
 }
 
@@ -3370,6 +3628,7 @@
 			return -1;
 		}
 		hapd->msg_ctx = hapd;
+		hostapd_bss_setup_multi_link(hapd, hapd_iface->interfaces);
 	}
 
 	hapd_iface->conf = conf;
@@ -3443,6 +3702,7 @@
 			if (start_ctrl_iface_bss(hapd) < 0 ||
 			    (hapd_iface->state == HAPD_IFACE_ENABLED &&
 			     hostapd_setup_bss(hapd, -1, true))) {
+				hostapd_bss_link_deinit(hapd);
 				hostapd_cleanup(hapd);
 				hapd_iface->bss[hapd_iface->num_bss - 1] = NULL;
 				hapd_iface->conf->num_bss--;
@@ -3451,6 +3711,9 @@
 					   __func__, hapd, hapd->conf->iface);
 				hostapd_config_free_bss(hapd->conf);
 				hapd->conf = NULL;
+#ifdef CONFIG_IEEE80211BE
+				hostapd_mld_ref_dec(hapd->mld);
+#endif /* CONFIG_IEEE80211BE */
 				os_free(hapd);
 				return -1;
 			}
@@ -3540,7 +3803,11 @@
 				wpa_printf(MSG_DEBUG, "%s: free hapd %p (%s)",
 					   __func__, hapd_iface->bss[i],
 					   hapd->conf->iface);
+				hostapd_bss_link_deinit(hapd);
 				hostapd_cleanup(hapd);
+#ifdef CONFIG_IEEE80211BE
+				hostapd_mld_ref_dec(hapd->mld);
+#endif /* CONFIG_IEEE80211BE */
 				os_free(hapd);
 				hapd_iface->bss[i] = NULL;
 			}
@@ -3550,6 +3817,7 @@
 		if (new_iface) {
 			interfaces->count--;
 			interfaces->iface[interfaces->count] = NULL;
+			hostapd_cleanup_unused_mlds(interfaces);
 		}
 		hostapd_cleanup_iface(hapd_iface);
 	}
@@ -3572,6 +3840,9 @@
 			   __func__, hapd, hapd->conf->iface);
 		hostapd_config_free_bss(hapd->conf);
 		hapd->conf = NULL;
+#ifdef CONFIG_IEEE80211BE
+		hostapd_mld_ref_dec(hapd->mld);
+#endif /* CONFIG_IEEE80211BE */
 		os_free(hapd);
 
 		iface->num_bss--;
@@ -3614,6 +3885,8 @@
 				k++;
 			}
 			interfaces->count--;
+			hostapd_cleanup_unused_mlds(interfaces);
+
 			return 0;
 		}
 
@@ -3913,7 +4186,8 @@
 				    mode ? &mode->he_capab[IEEE80211_MODE_AP] :
 				    NULL,
 				    mode ? &mode->eht_capab[IEEE80211_MODE_AP] :
-				    NULL))
+				    NULL,
+				    hostapd_get_punct_bitmap(hapd)))
 		return -1;
 
 	switch (params->bandwidth) {
@@ -4403,6 +4677,7 @@
 
 
 #ifdef CONFIG_IEEE80211BE
+
 struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
 					       u8 link_id)
 {
@@ -4411,9 +4686,8 @@
 	for (i = 0; i < hapd->iface->interfaces->count; i++) {
 		struct hostapd_iface *h = hapd->iface->interfaces->iface[i];
 		struct hostapd_data *h_hapd = h->bss[0];
-		struct hostapd_bss_config *hconf = h_hapd->conf;
 
-		if (!hconf->mld_ap || hconf->mld_id != hapd->conf->mld_id)
+		if (!hostapd_is_ml_partner(hapd, h_hapd))
 			continue;
 
 		if (h_hapd->mld_link_id == link_id)
@@ -4422,4 +4696,141 @@
 
 	return NULL;
 }
+
+
+bool hostapd_is_ml_partner(struct hostapd_data *hapd1,
+			   struct hostapd_data *hapd2)
+{
+	if (!hapd1->conf->mld_ap || !hapd2->conf->mld_ap)
+		return false;
+
+	return !os_strcmp(hapd1->conf->iface, hapd2->conf->iface);
+}
+
+
+u8 hostapd_get_mld_id(struct hostapd_data *hapd)
+{
+	if (!hapd->conf->mld_ap)
+		return 255;
+
+	/* MLD ID 0 represents self */
+	return 0;
+
+	/* TODO: MLD ID for Multiple BSS cases */
+}
+
+
+int hostapd_mld_add_link(struct hostapd_data *hapd)
+{
+	struct hostapd_mld *mld = hapd->mld;
+
+	if (!hapd->conf->mld_ap)
+		return 0;
+
+	/* Should not happen */
+	if (!mld)
+		return -1;
+
+	dl_list_add_tail(&mld->links, &hapd->link);
+	mld->num_links++;
+
+	wpa_printf(MSG_DEBUG, "AP MLD %s: Link ID %d added. num_links: %d",
+		   mld->name, hapd->mld_link_id, mld->num_links);
+
+	if (mld->fbss)
+		return 0;
+
+	mld->fbss = hapd;
+	wpa_printf(MSG_DEBUG, "AP MLD %s: First link BSS set to %p",
+		   mld->name, mld->fbss);
+	return 0;
+}
+
+
+int hostapd_mld_remove_link(struct hostapd_data *hapd)
+{
+	struct hostapd_mld *mld = hapd->mld;
+	struct hostapd_data *next_fbss;
+
+	if (!hapd->conf->mld_ap)
+		return 0;
+
+	/* Should not happen */
+	if (!mld)
+		return -1;
+
+	dl_list_del(&hapd->link);
+	mld->num_links--;
+
+	wpa_printf(MSG_DEBUG, "AP MLD %s: Link ID %d removed. num_links: %d",
+		   mld->name, hapd->mld_link_id, mld->num_links);
+
+	if (mld->fbss != hapd)
+		return 0;
+
+	/* If the list is empty, all links are removed */
+	if (dl_list_empty(&mld->links)) {
+		mld->fbss = NULL;
+	} else {
+		next_fbss = dl_list_entry(mld->links.next, struct hostapd_data,
+					  link);
+		mld->fbss = next_fbss;
+	}
+
+	wpa_printf(MSG_DEBUG, "AP MLD %s: First link BSS set to %p",
+		   mld->name, mld->fbss);
+	return 0;
+}
+
+
+bool hostapd_mld_is_first_bss(struct hostapd_data *hapd)
+{
+	struct hostapd_mld *mld = hapd->mld;
+
+	if (!hapd->conf->mld_ap)
+		return true;
+
+	/* Should not happen */
+	if (!mld)
+		return false;
+
+	/* If fbss is not set, it is safe to assume the caller is the first BSS.
+	 */
+	if (!mld->fbss)
+		return true;
+
+	return hapd == mld->fbss;
+}
+
+
+struct hostapd_data * hostapd_mld_get_first_bss(struct hostapd_data *hapd)
+{
+	struct hostapd_mld *mld = hapd->mld;
+
+	if (!hapd->conf->mld_ap)
+		return NULL;
+
+	/* Should not happen */
+	if (!mld)
+		return NULL;
+
+	return mld->fbss;
+}
+
 #endif /* CONFIG_IEEE80211BE */
+
+
+u16 hostapd_get_punct_bitmap(struct hostapd_data *hapd)
+{
+	u16 punct_bitmap = 0;
+
+#ifdef CONFIG_IEEE80211BE
+	punct_bitmap = hapd->iconf->punct_bitmap;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (!punct_bitmap)
+		punct_bitmap = hapd->conf->eht_oper_puncturing_override;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+	return punct_bitmap;
+}
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index bcf980f..ff29726 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -44,6 +44,7 @@
 #endif /* CONFIG_CTRL_IFACE_UDP */
 
 struct hostapd_iface;
+struct hostapd_mld;
 
 struct hapd_interfaces {
 	int (*reload_config)(struct hostapd_iface *iface);
@@ -93,6 +94,10 @@
        unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN];
 #endif /* CONFIG_CTRL_IFACE_UDP */
 
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_mld **mld;
+	size_t mld_count;
+#endif /* CONFIG_IEEE80211BE */
 };
 
 enum hostapd_chan_status {
@@ -175,12 +180,6 @@
 	unsigned int reenable_beacon:1;
 
 	u8 own_addr[ETH_ALEN];
-	u8 mld_addr[ETH_ALEN];
-	u8 mld_link_id;
-	/* Used for mld_link_id assignment - valid on the first MLD BSS only */
-	u8 mld_next_link_id;
-
-	struct hostapd_data *mld_first_bss;
 
 	int num_sta; /* number of entries in sta_list */
 	struct sta_info *sta_list; /* STA info list head */
@@ -406,8 +405,10 @@
 	u8 beacon_req_token;
 	u8 lci_req_token;
 	u8 range_req_token;
+	u8 link_measurement_req_token;
 	unsigned int lci_req_active:1;
 	unsigned int range_req_active:1;
+	unsigned int link_mesr_req_active:1;
 
 	int dhcp_sock; /* UDP socket used with the DHCP server */
 
@@ -472,6 +473,9 @@
 
 #ifdef CONFIG_IEEE80211BE
 	u8 eht_mld_bss_param_change;
+	struct hostapd_mld *mld;
+	struct dl_list link;
+	u8 mld_link_id;
 #ifdef CONFIG_TESTING_OPTIONS
 	u8 eht_mld_link_removal_count;
 #endif /* CONFIG_TESTING_OPTIONS */
@@ -480,6 +484,9 @@
 #ifdef CONFIG_NAN_USD
 	struct nan_de *nan_de;
 #endif /* CONFIG_NAN_USD */
+
+	u64 scan_cookie; /* Scan instance identifier for the ongoing HT40 scan
+			  */
 };
 
 
@@ -504,6 +511,29 @@
 	HAPD_IFACE_ENABLED
 };
 
+#ifdef CONFIG_IEEE80211BE
+/**
+ * struct hostapd_mld - hostapd per-mld data structure
+ */
+struct hostapd_mld {
+	char name[IFNAMSIZ + 1];
+	u8 mld_addr[ETH_ALEN];
+	u8 next_link_id;
+	u8 num_links;
+	/* Number of hostapd_data (hapd) referencing this. num_links cannot be
+	 * used since num_links can go to 0 even when a BSS is disabled and
+	 * when it is re-enabled, the MLD should exist and hence it cannot be
+	 * freed when num_links is 0.
+	 */
+	u8 refcount;
+
+	struct hostapd_data *fbss;
+	struct dl_list links; /* List head of all affiliated links */
+};
+
+#define HOSTAPD_MLD_MAX_REF_COUNT      0xFF
+#endif /* CONFIG_IEEE80211BE */
+
 /**
  * struct hostapd_iface - hostapd per-interface data structure
  */
@@ -551,6 +581,7 @@
 
 	u64 drv_flags;
 	u64 drv_flags2;
+	unsigned int drv_rrm_flags;
 
 	/*
 	 * A bitmap of supported protocols for probe response offload. See
@@ -576,6 +607,8 @@
 	int *basic_rates;
 	int freq;
 
+	bool radar_detected;
+
 	/* Background radar configuration */
 	struct {
 		int channel;
@@ -677,6 +710,8 @@
 
 	/* Configured freq of interface is NO_IR */
 	bool is_no_ir;
+
+	bool is_ch_switch_dfs; /* Channel switch from ACS to DFS */
 };
 
 /* hostapd.c */
@@ -783,8 +818,17 @@
 struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
 					       u8 link_id);
 int hostapd_link_remove(struct hostapd_data *hapd, u32 count);
+bool hostapd_is_ml_partner(struct hostapd_data *hapd1,
+			   struct hostapd_data *hapd2);
+u8 hostapd_get_mld_id(struct hostapd_data *hapd);
+int hostapd_mld_add_link(struct hostapd_data *hapd);
+int hostapd_mld_remove_link(struct hostapd_data *hapd);
+struct hostapd_data * hostapd_mld_get_first_bss(struct hostapd_data *hapd);
 
 #ifdef CONFIG_IEEE80211BE
+
+bool hostapd_mld_is_first_bss(struct hostapd_data *hapd);
+
 #define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
 	for (_iface_idx = 0;						\
 	     _iface_idx < (_ifaces)->count;				\
@@ -796,11 +840,21 @@
 			for (_link =					\
 			     (_ifaces)->iface[_iface_idx]->bss[_bss_idx]; \
 			    _link && _link->conf->mld_ap &&		\
-				_link->conf->mld_id == _mld_id;		\
+				hostapd_get_mld_id(_link) == _mld_id;	\
 			    _link = NULL)
+
 #else /* CONFIG_IEEE80211BE */
+
+static inline bool hostapd_mld_is_first_bss(struct hostapd_data *hapd)
+{
+	return true;
+}
+
 #define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
 	if (false)
+
 #endif /* CONFIG_IEEE80211BE */
 
+u16 hostapd_get_punct_bitmap(struct hostapd_data *hapd);
+
 #endif /* HOSTAPD_H */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 596f2f0..c455660 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -107,9 +107,7 @@
 		 */
 		orig_mode_valid = true;
 		mode = iface->current_mode->mode;
-		is_6ghz = mode == HOSTAPD_MODE_IEEE80211A &&
-			iface->current_mode->num_channels > 0 &&
-			is_6ghz_freq(iface->current_mode->channels[0].freq);
+		is_6ghz = iface->current_mode->is_6ghz;
 		iface->current_mode = NULL;
 	}
 	hostapd_free_hw_features(iface->hw_features, iface->num_hw_features);
@@ -508,6 +506,12 @@
 	else
 		ieee80211n_scan_channels_5g(iface, &params);
 
+	params.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (iface->bss[0]->conf->mld_ap)
+		params.link_id = iface->bss[0]->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	ret = hostapd_driver_scan(iface->bss[0], &params);
 	iface->num_ht40_scan_tries++;
 	os_free(params.freqs);
@@ -523,6 +527,7 @@
 
 	if (ret == 0) {
 		iface->scan_cb = ieee80211n_check_scan;
+		iface->bss[0]->scan_cookie = params.scan_cookie;
 		return;
 	}
 
@@ -558,6 +563,11 @@
 	else
 		ieee80211n_scan_channels_5g(iface, &params);
 
+	params.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (iface->bss[0]->conf->mld_ap)
+		params.link_id = iface->bss[0]->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 	ret = hostapd_driver_scan(iface->bss[0], &params);
 	os_free(params.freqs);
 
@@ -579,6 +589,7 @@
 	}
 
 	iface->scan_cb = ieee80211n_check_scan;
+	iface->bss[0]->scan_cookie = params.scan_cookie;
 	return 1;
 }
 
@@ -1070,9 +1081,7 @@
 		return true;
 
 	if (is_6ghz_op_class(iface->conf->op_class) && iface->freq == 0 &&
-	    (mode->mode != HOSTAPD_MODE_IEEE80211A ||
-	     mode->num_channels == 0 ||
-	     !is_6ghz_freq(mode->channels[0].freq)))
+	    !mode->is_6ghz)
 		return true;
 
 	return false;
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 8b8c1f0..85a39d5 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -88,18 +88,31 @@
 			      struct sta_info *sta, int reassoc);
 
 
-u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid)
+static u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid, size_t len)
 {
-	u8 multi_ap_val = 0;
+	struct multi_ap_params multi_ap = { 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);
+	if (hapd->conf->multi_ap & BACKHAUL_BSS)
+		multi_ap.capability |= MULTI_AP_BACKHAUL_BSS;
+	if (hapd->conf->multi_ap & FRONTHAUL_BSS)
+		multi_ap.capability |= MULTI_AP_FRONTHAUL_BSS;
+
+	if (hapd->conf->multi_ap_client_disallow &
+	    PROFILE1_CLIENT_ASSOC_DISALLOW)
+		multi_ap.capability |=
+			MULTI_AP_PROFILE1_BACKHAUL_STA_DISALLOWED;
+	if (hapd->conf->multi_ap_client_disallow &
+	    PROFILE2_CLIENT_ASSOC_DISALLOW)
+		multi_ap.capability |=
+			MULTI_AP_PROFILE2_BACKHAUL_STA_DISALLOWED;
+
+	multi_ap.profile = hapd->conf->multi_ap_profile;
+	multi_ap.vlanid = hapd->conf->multi_ap_vlanid;
+
+	return eid + add_multi_ap_ie(eid, len, &multi_ap);
 }
 
 
@@ -409,7 +422,7 @@
 	 * the addresses.
 	 */
 	if (ap_sta_is_mld(hapd, sta)) {
-		sa = hapd->mld_addr;
+		sa = hapd->mld->mld_addr;
 
 		ml_resp = hostapd_ml_auth_resp(hapd);
 		if (!ml_resp)
@@ -610,7 +623,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (ap_sta_is_mld(hapd, sta))
-		own_addr = hapd->mld_addr;
+		own_addr = hapd->mld->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
 	if (sta->sae->tmp) {
@@ -2390,7 +2403,7 @@
 	wpa_hexdump(MSG_DEBUG, "RSN: Generated FILS ANonce",
 		    fils->anonce, FILS_NONCE_LEN);
 
-	ret = fils_rmsk_to_pmk(pasn->akmp, msk, msk_len, fils->nonce,
+	ret = fils_rmsk_to_pmk(pasn_get_akmp(pasn), msk, msk_len, fils->nonce,
 			       fils->anonce, NULL, 0, pmk, &pmk_len);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "FILS: Failed to derive PMK");
@@ -2400,15 +2413,16 @@
 	ret = pasn_pmk_to_ptk(pmk, pmk_len, sta->addr, hapd->own_addr,
 			      wpabuf_head(pasn->secret),
 			      wpabuf_len(pasn->secret),
-			      &sta->pasn->ptk, sta->pasn->akmp,
-			      sta->pasn->cipher, sta->pasn->kdk_len);
+			      pasn_get_ptk(sta->pasn), pasn_get_akmp(sta->pasn),
+			      pasn_get_cipher(sta->pasn), sta->pasn->kdk_len);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "PASN: FILS: Failed to derive PTK");
 		goto fail;
 	}
 
 	if (pasn->secure_ltf) {
-		ret = wpa_ltf_keyseed(&pasn->ptk, pasn->akmp, pasn->cipher);
+		ret = wpa_ltf_keyseed(pasn_get_ptk(pasn), pasn_get_akmp(pasn),
+				      pasn_get_cipher(pasn));
 		if (ret) {
 			wpa_printf(MSG_DEBUG,
 				   "PASN: FILS: Failed to derive LTF keyseed");
@@ -2554,7 +2568,8 @@
 	 * Calculate pending PMKID here so that we do not need to maintain a
 	 * copy of the EAP-Initiate/Reautt message.
 	 */
-	fils_pmkid_erp(pasn->akmp, wpabuf_head(fils_wd), wpabuf_len(fils_wd),
+	fils_pmkid_erp(pasn_get_akmp(pasn),
+		       wpabuf_head(fils_wd), wpabuf_len(fils_wd),
 		       fils->erp_pmkid);
 
 	wpabuf_free(fils_wd);
@@ -2579,32 +2594,35 @@
 {
 	struct pasn_data *pasn = sta->pasn;
 
-	pasn->cb_ctx = hapd;
-	pasn->send_mgmt = hapd_pasn_send_mlme;
+	pasn_register_callbacks(pasn, hapd, hapd_pasn_send_mlme, NULL);
+	pasn_set_bssid(pasn, hapd->own_addr);
+	pasn_set_own_addr(pasn, hapd->own_addr);
+	pasn_set_peer_addr(pasn, sta->addr);
+	pasn_set_wpa_key_mgmt(pasn, hapd->conf->wpa_key_mgmt);
+	pasn_set_rsn_pairwise(pasn, hapd->conf->rsn_pairwise);
 	pasn->pasn_groups = hapd->conf->pasn_groups;
 	pasn->noauth = hapd->conf->pasn_noauth;
-	pasn->wpa_key_mgmt = hapd->conf->wpa_key_mgmt;
-	pasn->rsn_pairwise = hapd->conf->rsn_pairwise;
-	pasn->derive_kdk = hapd->iface->drv_flags2 &
-		WPA_DRIVER_FLAGS2_SEC_LTF_AP;
+	if (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SEC_LTF_AP)
+		pasn_enable_kdk_derivation(pasn);
+
 #ifdef CONFIG_TESTING_OPTIONS
 	pasn->corrupt_mic = hapd->conf->pasn_corrupt_mic;
 	if (hapd->conf->force_kdk_derivation)
-		pasn->derive_kdk = true;
+		pasn_enable_kdk_derivation(pasn);
 #endif /* CONFIG_TESTING_OPTIONS */
 	pasn->use_anti_clogging = use_anti_clogging(hapd);
-	pasn->password = sae_get_password(hapd, sta, NULL, NULL, &pasn->pt,
-					  NULL);
+	pasn_set_password(pasn, sae_get_password(hapd, sta, NULL, NULL,
+						 &pasn->pt, NULL));
 	pasn->rsn_ie = wpa_auth_get_wpa_ie(hapd->wpa_auth, &pasn->rsn_ie_len);
-	pasn->rsnxe_ie = hostapd_wpa_ie(hapd, WLAN_EID_RSNX);
+	pasn_set_rsnxe_ie(pasn, hostapd_wpa_ie(hapd, WLAN_EID_RSNX));
 	pasn->disable_pmksa_caching = hapd->conf->disable_pmksa_caching;
-	pasn->pmksa = wpa_auth_get_pmksa_cache(hapd->wpa_auth);
+	pasn_set_responder_pmksa(pasn,
+				 wpa_auth_get_pmksa_cache(hapd->wpa_auth));
 
 	pasn->comeback_after = hapd->conf->pasn_comeback_after;
 	pasn->comeback_idx = hapd->comeback_idx;
 	pasn->comeback_key =  hapd->comeback_key;
 	pasn->comeback_pending_idx = hapd->comeback_pending_idx;
-	os_memcpy(pasn->bssid, hapd->own_addr, ETH_ALEN);
 }
 
 
@@ -2652,6 +2670,7 @@
 	struct wpa_pasn_params_data pasn_params;
 	struct wpabuf *wrapped_data = NULL;
 #endif /* CONFIG_FILS */
+	int akmp;
 
 	if (ieee802_11_parse_elems(mgmt->u.auth.variable,
 				   len - offsetof(struct ieee80211_mgmt,
@@ -2675,10 +2694,12 @@
 		return;
 	}
 
-	pasn->akmp = rsn_data.key_mgmt;
-	pasn->cipher = rsn_data.pairwise_cipher;
+	pasn_set_akmp(pasn, rsn_data.key_mgmt);
+	pasn_set_cipher(pasn, rsn_data.pairwise_cipher);
 
-	if (wpa_key_mgmt_ft(pasn->akmp) && rsn_data.num_pmkid) {
+	akmp = pasn_get_akmp(pasn);
+
+	if (wpa_key_mgmt_ft(akmp) && rsn_data.num_pmkid) {
 #ifdef CONFIG_IEEE80211R_AP
 		pasn->pmk_r1_len = 0;
 		wpa_ft_fetch_pmk_r1(hapd->wpa_auth, sta->addr,
@@ -2689,8 +2710,8 @@
 #endif /* CONFIG_IEEE80211R_AP */
 	}
 #ifdef CONFIG_FILS
-	if (pasn->akmp != WPA_KEY_MGMT_FILS_SHA256 &&
-	    pasn->akmp != WPA_KEY_MGMT_FILS_SHA384)
+	if (akmp != WPA_KEY_MGMT_FILS_SHA256 &&
+	    akmp != WPA_KEY_MGMT_FILS_SHA384)
 		return;
 	if (!elems.pasn_params ||
 	    wpa_pasn_parse_parameter_ie(elems.pasn_params - 3,
@@ -2743,7 +2764,7 @@
 			return;
 		}
 
-		sta->pasn = os_zalloc(sizeof(*sta->pasn));
+		sta->pasn = pasn_data_init();
 		if (!sta->pasn) {
 			wpa_printf(MSG_DEBUG,
 				   "PASN: Failed to allocate PASN context");
@@ -2773,13 +2794,14 @@
 		if (handle_auth_pasn_3(sta->pasn, hapd->own_addr,
 				       sta->addr, mgmt, len) == 0) {
 			ptksa_cache_add(hapd->ptksa, hapd->own_addr, sta->addr,
-					sta->pasn->cipher, 43200,
-					&sta->pasn->ptk, NULL, NULL,
-					sta->pasn->akmp);
+					pasn_get_cipher(sta->pasn), 43200,
+					pasn_get_ptk(sta->pasn), NULL, NULL,
+					pasn_get_akmp(sta->pasn));
 
 			pasn_set_keys_from_cache(hapd, hapd->own_addr,
-						 sta->addr, sta->pasn->cipher,
-						 sta->pasn->akmp);
+						 sta->addr,
+						 pasn_get_cipher(sta->pasn),
+						 pasn_get_akmp(sta->pasn));
 		}
 		ap_free_sta(hapd, sta);
 	} else {
@@ -2806,7 +2828,9 @@
 	u16 seq_ctrl;
 	struct radius_sta rad_info;
 	const u8 *dst, *sa, *bssid;
+#ifdef CONFIG_IEEE80211BE
 	bool mld_sta = false;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
 		wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
@@ -2924,15 +2948,17 @@
 		goto fail;
 	}
 
+#ifdef CONFIG_IEEE80211BE
 	if (mld_sta &&
 	    (ether_addr_equal(sa, hapd->own_addr) ||
-	     ether_addr_equal(sa, hapd->mld_addr))) {
+	     ether_addr_equal(sa, hapd->mld->mld_addr))) {
 		wpa_printf(MSG_INFO,
 			   "Station " MACSTR " not allowed to authenticate",
 			   MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto fail;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 	if (hapd->conf->no_auth_if_seen_on) {
 		struct hostapd_data *other;
@@ -3032,15 +3058,6 @@
 				       seq_ctrl);
 			return;
 		}
-#ifdef CONFIG_MESH
-		if ((hapd->conf->mesh & MESH_ENABLED) &&
-		    sta->plink_state == PLINK_BLOCKED) {
-			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
-				   " is blocked - drop Authentication frame",
-				   MAC2STR(sa));
-			return;
-		}
-#endif /* CONFIG_MESH */
 #ifdef CONFIG_PASN
 		if (auth_alg == WLAN_AUTH_PASN &&
 		    (sta->flags & WLAN_STA_ASSOC)) {
@@ -3078,7 +3095,12 @@
 	}
 
 #ifdef CONFIG_IEEE80211BE
-	if (auth_transaction == 1) {
+	/* Set the non-AP MLD information based on the initial Authentication
+	 * frame. Once the STA entry has been added to the driver, the driver
+	 * will translate addresses in the frame and we need to avoid overriding
+	 * peer_addr based on mgmt->sa which would have been translated to the
+	 * MLD MAC address. */
+	if (!sta->added_unassoc && auth_transaction == 1) {
 		ap_sta_free_sta_profile(&sta->mld_info);
 		os_memset(&sta->mld_info, 0, sizeof(sta->mld_info));
 
@@ -3250,7 +3272,7 @@
 	  */
 	if (ap_sta_is_mld(hapd, sta)) {
 		dst = sta->addr;
-		bssid = hapd->mld_addr;
+		bssid = hapd->mld->mld_addr;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -3410,37 +3432,58 @@
 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;
+	struct multi_ap_params multi_ap;
+	u16 status;
 
 	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 {
+	if (!multi_ap_ie) {
+		if (!(hapd->conf->multi_ap & FRONTHAUL_BSS)) {
 			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;
+				       "Non-Multi-AP STA tries to associate with backhaul-only BSS");
+			return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
 		}
+
+		return WLAN_STATUS_SUCCESS;
 	}
 
-	if (multi_ap_value && multi_ap_value != MULTI_AP_BACKHAUL_STA)
+	status = check_multi_ap_ie(multi_ap_ie + 4, multi_ap_len - 4,
+				   &multi_ap);
+	if (status != WLAN_STATUS_SUCCESS)
+		return status;
+
+	if (multi_ap.capability && multi_ap.capability != MULTI_AP_BACKHAUL_STA)
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_INFO,
 			       "Multi-AP IE with unexpected value 0x%02x",
-			       multi_ap_value);
+			       multi_ap.capability);
 
-	if (!(multi_ap_value & MULTI_AP_BACKHAUL_STA)) {
+	if (multi_ap.profile == MULTI_AP_PROFILE_1 &&
+	    (hapd->conf->multi_ap_client_disallow &
+	     PROFILE1_CLIENT_ASSOC_DISALLOW)) {
+		hostapd_logger(hapd, sta->addr,
+			       HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_INFO,
+			       "Multi-AP Profile-1 clients not allowed");
+		return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+	}
+
+	if (multi_ap.profile >= MULTI_AP_PROFILE_2 &&
+	    (hapd->conf->multi_ap_client_disallow &
+	     PROFILE2_CLIENT_ASSOC_DISALLOW)) {
+		hostapd_logger(hapd, sta->addr,
+			       HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_INFO,
+			       "Multi-AP Profile-2 clients not allowed");
+		return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+	}
+
+	if (!(multi_ap.capability & MULTI_AP_BACKHAUL_STA)) {
 		if (hapd->conf->multi_ap & FRONTHAUL_BSS)
 			return WLAN_STATUS_SUCCESS;
 
@@ -3740,7 +3783,7 @@
 	}
 #ifdef CONFIG_IEEE80211BE
 	if (ap_sta_is_mld(hapd, sta))
-		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld->mld_addr,
 				     sta->mld_assoc_link_id, &sta->mld_info);
 #endif /* CONFIG_IEEE80211BE */
 	rsn_ie -= 2;
@@ -4025,7 +4068,7 @@
 				wpa_printf(MSG_DEBUG,
 					   "MLD: Set ML info in RSN Authenticator");
 				wpa_auth_set_ml_info(sta->wpa_sm,
-						     hapd->mld_addr,
+						     hapd->mld->mld_addr,
 						     sta->mld_assoc_link_id,
 						     info);
 			}
@@ -4559,8 +4602,7 @@
 			if (hapd->iface == iface)
 				continue;
 
-			if (iface->bss[0]->conf->mld_ap &&
-			    hapd->conf->mld_id == iface->bss[0]->conf->mld_id &&
+			if (hostapd_is_ml_partner(hapd, iface->bss[0]) &&
 			    i == iface->bss[0]->mld_link_id)
 				break;
 		}
@@ -4787,7 +4829,7 @@
 	 * MLD MAC address.
 	 */
 	if (ap_sta_is_mld(hapd, sta) && allow_mld_addr_trans)
-		sa = hapd->mld_addr;
+		sa = hapd->mld->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
 	os_memcpy(reply->da, addr, ETH_ALEN);
@@ -4991,7 +5033,7 @@
 #endif /* CONFIG_WPS */
 
 	if (sta && (sta->flags & WLAN_STA_MULTI_AP))
-		p = hostapd_eid_multi_ap(hapd, p);
+		p = hostapd_eid_multi_ap(hapd, p, buf + buflen - p);
 
 #ifdef CONFIG_P2P
 	if (sta && sta->p2p_ie && hapd->p2p_group) {
@@ -5775,8 +5817,7 @@
 			tmp_hapd =
 				assoc_hapd->iface->interfaces->iface[i]->bss[0];
 
-			if (!tmp_hapd->conf->mld_ap ||
-			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+			if (!hostapd_is_ml_partner(assoc_hapd, tmp_hapd))
 				continue;
 
 			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
@@ -6206,7 +6247,7 @@
 #endif /* CONFIG_MESH */
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
+	      ether_addr_equal(hapd->mld->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
 	    !ether_addr_equal(mgmt->bssid, hapd->own_addr)) {
 		wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address",
@@ -6229,7 +6270,7 @@
 	     stype != WLAN_FC_STYPE_ACTION) &&
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
+	      ether_addr_equal(hapd->mld->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
 #ifdef CONFIG_NAN_USD
 	    !ether_addr_equal(mgmt->da, nan_network_id) &&
@@ -6443,8 +6484,7 @@
 			struct hostapd_data *tmp_hapd =
 				hapd->iface->interfaces->iface[i]->bss[0];
 
-			if (!tmp_hapd->conf->mld_ap ||
-			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+			if (!hostapd_is_ml_partner(tmp_hapd, hapd))
 				continue;
 
 			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
@@ -7407,12 +7447,12 @@
 		bool ap_mld = false;
 
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
-		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+		if (hostapd_is_ml_partner(hapd, iface->bss[0]))
 			ap_mld = true;
 #endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
+		    iface->state != HAPD_IFACE_ENABLED ||
 		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
@@ -7580,11 +7620,10 @@
 #ifdef CONFIG_IEEE80211BE
 		u8 param_ch = hapd->eht_mld_bss_param_change;
 
-		if (reporting_hapd->conf->mld_ap &&
-		    bss->conf->mld_id == reporting_hapd->conf->mld_id)
+		if (hostapd_is_ml_partner(bss, reporting_hapd))
 			*eid++ = 0;
 		else
-			*eid++ = hapd->conf->mld_id;
+			*eid++ = hostapd_get_mld_id(hapd);
 
 		*eid++ = hapd->mld_link_id | ((param_ch & 0xF) << 4);
 		*eid = (param_ch >> 4) & 0xF;
@@ -7682,12 +7721,12 @@
 		bool ap_mld = false;
 
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
-		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+		if (hostapd_is_ml_partner(hapd, iface->bss[0]))
 			ap_mld = true;
 #endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
+		    iface->state != HAPD_IFACE_ENABLED ||
 		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
@@ -7754,6 +7793,27 @@
 }
 
 
+static size_t hostapd_mbssid_ext_capa(struct hostapd_data *bss,
+				      struct hostapd_data *tx_bss, u8 *buf)
+{
+	u8 ext_capa_tx[20], *ext_capa_tx_end, ext_capa[20], *ext_capa_end;
+	size_t ext_capa_len, ext_capa_tx_len;
+
+	ext_capa_tx_end = hostapd_eid_ext_capab(tx_bss, ext_capa_tx,
+						true);
+	ext_capa_tx_len = ext_capa_tx_end - ext_capa_tx;
+	ext_capa_end = hostapd_eid_ext_capab(bss, ext_capa, true);
+	ext_capa_len = ext_capa_end - ext_capa;
+	if (ext_capa_tx_len != ext_capa_len ||
+	    os_memcmp(ext_capa_tx, ext_capa, ext_capa_len) != 0) {
+		os_memcpy(buf, ext_capa, ext_capa_len);
+		return ext_capa_len;
+	}
+
+	return 0;
+}
+
+
 static size_t hostapd_eid_mbssid_elem_len(struct hostapd_data *hapd,
 					  u32 frame_type, size_t *bss_index,
 					  const u8 *known_bss,
@@ -7761,6 +7821,7 @@
 {
 	struct hostapd_data *tx_bss = hostapd_mbssid_get_tx_bss(hapd);
 	size_t len, i;
+	u8 ext_capa[20];
 
 	/* Element ID: 1 octet
 	 * Length: 1 octet
@@ -7806,6 +7867,10 @@
 			if (rsnx)
 				nontx_profile_len += 2 + rsnx[1];
 		}
+
+		nontx_profile_len += hostapd_mbssid_ext_capa(bss, tx_bss,
+							     ext_capa);
+
 		if (!rsn && hostapd_wpa_ie(tx_bss, WLAN_EID_RSN))
 			ie_count++;
 		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
@@ -7955,6 +8020,9 @@
 				eid += 2 + rsnx[1];
 			}
 		}
+
+		eid += hostapd_mbssid_ext_capa(bss, tx_bss, eid);
+
 		/* List of Element ID values in increasing order */
 		if (!rsn && hostapd_wpa_ie(tx_bss, WLAN_EID_RSN))
 			non_inherit_ie[ie_count++] = WLAN_EID_RSN;
@@ -8062,73 +8130,4 @@
 	return eid;
 }
 
-
-static void punct_update_legacy_bw_80(u8 bitmap, u8 pri_chan, u8 *seg0)
-{
-	u8 first_chan = *seg0 - 6, sec_chan;
-
-	switch (bitmap) {
-	case 0x6:
-		*seg0 = 0;
-		return;
-	case 0x8:
-	case 0x4:
-	case 0x2:
-	case 0x1:
-	case 0xC:
-	case 0x3:
-		if (pri_chan < *seg0)
-			*seg0 -= 4;
-		else
-			*seg0 += 4;
-		break;
-	}
-
-	if (pri_chan < *seg0)
-		sec_chan = pri_chan + 4;
-	else
-		sec_chan = pri_chan - 4;
-
-	if (bitmap & BIT((sec_chan - first_chan) / 4))
-		*seg0 = 0;
-}
-
-
-static void punct_update_legacy_bw_160(u8 bitmap, u8 pri,
-				       enum oper_chan_width *width, u8 *seg0)
-{
-	if (pri < *seg0) {
-		*seg0 -= 8;
-		if (bitmap & 0x0F) {
-			*width = 0;
-			punct_update_legacy_bw_80(bitmap & 0xF, pri, seg0);
-		}
-	} else {
-		*seg0 += 8;
-		if (bitmap & 0xF0) {
-			*width = 0;
-			punct_update_legacy_bw_80((bitmap & 0xF0) >> 4, pri,
-						  seg0);
-		}
-	}
-}
-
-
-void punct_update_legacy_bw(u16 bitmap, u8 pri, enum oper_chan_width *width,
-			    u8 *seg0, u8 *seg1)
-{
-	if (*width == CONF_OPER_CHWIDTH_80MHZ && (bitmap & 0xF)) {
-		*width = CONF_OPER_CHWIDTH_USE_HT;
-		punct_update_legacy_bw_80(bitmap & 0xF, pri, seg0);
-	}
-
-	if (*width == CONF_OPER_CHWIDTH_160MHZ && (bitmap & 0xFF)) {
-		*width = CONF_OPER_CHWIDTH_80MHZ;
-		*seg1 = 0;
-		punct_update_legacy_bw_160(bitmap & 0xFF, pri, width, seg0);
-	}
-
-	/* TODO: 320 MHz */
-}
-
 #endif /* CONFIG_NATIVE_WINDOWS */
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 5fd380a..a35486d 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -248,8 +248,6 @@
 			u8 **elem_offset,
 			const u8 *known_bss, size_t known_bss_len, u8 *rnr_eid,
 			u8 *rnr_count, u8 **rnr_offset, size_t rnr_len);
-void punct_update_legacy_bw(u16 bitmap, u8 pri_chan,
-			    enum oper_chan_width *width, u8 *seg0, u8 *seg1);
 bool hostapd_is_mld_ap(struct hostapd_data *hapd);
 const char * sae_get_password(struct hostapd_data *hapd,
 			      struct sta_info *sta, const char *rx_id,
diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index 840fc20..638546e 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -206,19 +206,11 @@
 	enum oper_chan_width chwidth;
 	size_t elen = 1 + 4;
 	bool eht_oper_info_present;
-	u16 punct_bitmap = conf->punct_bitmap;
+	u16 punct_bitmap = hostapd_get_punct_bitmap(hapd);
 
 	if (!hapd->iface->current_mode)
 		return eid;
 
-#ifdef CONFIG_TESTING_OPTIONS
-	if (!punct_bitmap && hapd->conf->eht_oper_puncturing_override) {
-		wpa_printf(MSG_DEBUG, "EHT: Puncturing mask override=0x%x",
-			   hapd->conf->eht_oper_puncturing_override);
-		punct_bitmap = hapd->conf->eht_oper_puncturing_override;
-	}
-#endif /* CONFIG_TESTING_OPTIONS */
-
 	if (is_6ghz_op_class(conf->op_class))
 		chwidth = op_class_to_ch_width(conf->op_class);
 	else
@@ -458,6 +450,8 @@
 	size_t len, slice_len;
 	u8 link_id;
 	u8 common_info_len;
+	u16 mld_cap;
+	u8 max_simul_links, active_links;
 
 	/*
 	 * As the Multi-Link element can exceed the size of 255 bytes need to
@@ -495,7 +489,7 @@
 	wpabuf_put_u8(buf, common_info_len);
 
 	/* Own MLD MAC Address */
-	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+	wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN);
 
 	/* Own Link ID */
 	wpabuf_put_u8(buf, hapd->mld_link_id);
@@ -507,14 +501,31 @@
 		   hapd->iface->mld_eml_capa);
 	wpabuf_put_le16(buf, hapd->iface->mld_eml_capa);
 
+	mld_cap = hapd->iface->mld_mld_capa;
+	max_simul_links = mld_cap & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK;
+	active_links = hapd->mld->num_links - 1;
+
+	if (active_links > max_simul_links) {
+		wpa_printf(MSG_ERROR,
+			   "MLD: Error in max simultaneous links, advertised: 0x%x current: 0x%x",
+			   max_simul_links, active_links);
+		active_links = max_simul_links;
+	}
+
+	mld_cap &= ~EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK;
+	mld_cap |= active_links & EHT_ML_MLD_CAPA_MAX_NUM_SIM_LINKS_MASK;
+
+	/* TODO: Advertise T2LM based on driver support as well */
+	mld_cap &= ~EHT_ML_MLD_CAPA_TID_TO_LINK_MAP_NEG_SUPP_MSK;
+
 	wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x",
-		   hapd->iface->mld_mld_capa);
-	wpabuf_put_le16(buf, hapd->iface->mld_mld_capa);
+		   mld_cap);
+	wpabuf_put_le16(buf, mld_cap);
 
 	if (include_mld_id) {
 		wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x",
-			   hapd->conf->mld_id);
-		wpabuf_put_u8(buf, hapd->conf->mld_id);
+			   hostapd_get_mld_id(hapd));
+		wpabuf_put_u8(buf, hostapd_get_mld_id(hapd));
 	}
 
 	if (!mld_info)
@@ -578,7 +589,8 @@
 		wpabuf_put_le64(buf, 0);
 
 		/* DTIM Info */
-		wpabuf_put_le16(buf, link_bss->conf->dtim_period);
+		wpabuf_put_u8(buf, 0); /* DTIM Count */
+		wpabuf_put_u8(buf, link_bss->conf->dtim_period);
 
 		/* BSS Parameters Change Count */
 		wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change);
@@ -812,7 +824,7 @@
 	wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK);
 	wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC);
 	wpabuf_put_u8(buf, ETH_ALEN + 1);
-	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+	wpabuf_put_data(buf, hapd->mld->mld_addr, ETH_ALEN);
 
 	return buf;
 }
@@ -1047,8 +1059,7 @@
 			if (hapd == other_hapd)
 				continue;
 
-			if (other_hapd->conf->mld_ap &&
-			    other_hapd->conf->mld_id == hapd->conf->mld_id &&
+			if (hostapd_is_ml_partner(hapd, other_hapd) &&
 			    link_id == other_hapd->mld_link_id)
 				break;
 		}
diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index 4b693a7..a2deda6 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -12,6 +12,7 @@
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
 #include "common/ieee802_11_common.h"
+#include "common/hw_features_common.h"
 #include "hostapd.h"
 #include "ap_config.h"
 #include "beacon.h"
@@ -224,10 +225,11 @@
 		u8 seg0 = hapd->iconf->he_oper_centr_freq_seg0_idx;
 		u8 seg1 = hostapd_get_oper_centr_freq_seg1_idx(hapd->iconf);
 		u8 control;
-
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->iconf->punct_bitmap) {
-			punct_update_legacy_bw(hapd->iconf->punct_bitmap,
+		u16 punct_bitmap = hostapd_get_punct_bitmap(hapd);
+
+		if (punct_bitmap) {
+			punct_update_legacy_bw(punct_bitmap,
 					       hapd->iconf->channel,
 					       &oper_chwidth, &seg0, &seg1);
 		}
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index 0c38483..85790c7 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -121,7 +121,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (ap_sta_is_mld(hapd, sta))
-		own_addr = hapd->mld_addr;
+		own_addr = hapd->mld->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
@@ -219,7 +219,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (ap_sta_is_mld(hapd, sta))
-		own_addr = hapd->mld_addr;
+		own_addr = hapd->mld->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
 	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
@@ -1138,13 +1138,11 @@
 u16 check_ext_capab(struct hostapd_data *hapd, struct sta_info *sta,
 		    const u8 *ext_capab_ie, size_t ext_capab_ie_len)
 {
-#ifdef CONFIG_INTERWORKING
 	/* check for QoS Map support */
 	if (ext_capab_ie_len >= 5) {
 		if (ext_capab_ie[4] & 0x01)
 			sta->qos_map_enabled = 1;
 	}
-#endif /* CONFIG_INTERWORKING */
 
 	if (ext_capab_ie_len > 0) {
 		sta->ecsa_supported = !!(ext_capab_ie[0] & BIT(2));
diff --git a/src/ap/ieee802_11_vht.c b/src/ap/ieee802_11_vht.c
index db615a3..4dc325c 100644
--- a/src/ap/ieee802_11_vht.c
+++ b/src/ap/ieee802_11_vht.c
@@ -12,6 +12,7 @@
 
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
+#include "common/hw_features_common.h"
 #include "hostapd.h"
 #include "ap_config.h"
 #include "sta_info.h"
@@ -79,6 +80,9 @@
 		hostapd_get_oper_chwidth(hapd->iconf);
 	u8 seg0 = hapd->iconf->vht_oper_centr_freq_seg0_idx;
 	u8 seg1 = hapd->iconf->vht_oper_centr_freq_seg1_idx;
+#ifdef CONFIG_IEEE80211BE
+	u16 punct_bitmap = hostapd_get_punct_bitmap(hapd);
+#endif /* CONFIG_IEEE80211BE */
 
 	if (is_6ghz_op_class(hapd->iconf->op_class))
 		return eid;
@@ -90,8 +94,8 @@
 	os_memset(oper, 0, sizeof(*oper));
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->iconf->punct_bitmap) {
-		punct_update_legacy_bw(hapd->iconf->punct_bitmap,
+	if (punct_bitmap) {
+		punct_update_legacy_bw(punct_bitmap,
 				       hapd->iconf->channel,
 				       &oper_chwidth, &seg0, &seg1);
 	}
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index f13c60a..8e98b65 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -172,8 +172,7 @@
 			struct hostapd_data *tmp_hapd =
 				hapd->iface->interfaces->iface[i]->bss[0];
 
-			if (!tmp_hapd->conf->mld_ap ||
-			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+			if (!hostapd_is_ml_partner(hapd, tmp_hapd))
 				continue;
 
 			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
@@ -2540,13 +2539,20 @@
 	struct eapol_auth_config conf;
 	struct eapol_auth_cb cb;
 
-	if (hapd->mld_first_bss) {
+#ifdef CONFIG_IEEE80211BE
+	if (!hostapd_mld_is_first_bss(hapd)) {
+		struct hostapd_data *first;
+
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Using IEEE 802.1X state machine of the first BSS");
 
-		hapd->eapol_auth = hapd->mld_first_bss->eapol_auth;
+		first = hostapd_mld_get_first_bss(hapd);
+		if (!first)
+			return -1;
+		hapd->eapol_auth = first->eapol_auth;
 		return 0;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 	dl_list_init(&hapd->erp_keys);
 
@@ -2632,13 +2638,15 @@
 
 void ieee802_1x_deinit(struct hostapd_data *hapd)
 {
-	if (hapd->mld_first_bss) {
+#ifdef CONFIG_IEEE80211BE
+	if (!hostapd_mld_is_first_bss(hapd)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Deinit IEEE 802.1X state machine of a non-first BSS");
 
 		hapd->eapol_auth = NULL;
 		return;
 	}
+#endif /* CONFIG_IEEE80211BE */
 
 #ifdef CONFIG_WEP
 	eloop_cancel_timeout(ieee802_1x_rekey, hapd, NULL);
diff --git a/src/ap/ndisc_snoop.c b/src/ap/ndisc_snoop.c
index 788c12f..bc1eb62 100644
--- a/src/ap/ndisc_snoop.c
+++ b/src/ap/ndisc_snoop.c
@@ -61,6 +61,7 @@
 	dl_list_for_each_safe(ip6addr, prev, &sta->ip6addr, struct ip6addr,
 			      list) {
 		hostapd_drv_br_delete_ip_neigh(hapd, 6, (u8 *) &ip6addr->addr);
+		dl_list_del(&ip6addr->list);
 		os_free(ip6addr);
 	}
 }
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index 2a25ae2..f7a7d83 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -99,7 +99,10 @@
 	nr->civic = NULL;
 	os_memset(nr->bssid, 0, sizeof(nr->bssid));
 	os_memset(&nr->ssid, 0, sizeof(nr->ssid));
+	os_memset(&nr->lci_date, 0, sizeof(nr->lci_date));
 	nr->stationary = 0;
+	nr->short_ssid = 0;
+	nr->bss_parameters = 0;
 }
 
 
@@ -165,6 +168,14 @@
 }
 
 
+static void hostapd_neighbor_free(struct hostapd_neighbor_entry *nr)
+{
+	hostapd_neighbor_clear_entry(nr);
+	dl_list_del(&nr->list);
+	os_free(nr);
+}
+
+
 int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid,
 			    const struct wpa_ssid_value *ssid)
 {
@@ -174,9 +185,7 @@
 	if (!nr)
 		return -1;
 
-	hostapd_neighbor_clear_entry(nr);
-	dl_list_del(&nr->list);
-	os_free(nr);
+	hostapd_neighbor_free(nr);
 
 	return 0;
 }
@@ -188,9 +197,7 @@
 
 	dl_list_for_each_safe(nr, prev, &hapd->nr_db,
 			      struct hostapd_neighbor_entry, list) {
-		hostapd_neighbor_clear_entry(nr);
-		dl_list_del(&nr->list);
-		os_free(nr);
+		hostapd_neighbor_free(nr);
 	}
 }
 
@@ -325,3 +332,35 @@
 	wpabuf_free(nr);
 #endif /* NEED_AP_MLME */
 }
+
+
+static struct hostapd_neighbor_entry *
+hostapd_neighbor_get_diff_short_ssid(struct hostapd_data *hapd, const u8 *bssid)
+{
+	struct hostapd_neighbor_entry *nr;
+
+	dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
+			 list) {
+		if (ether_addr_equal(bssid, nr->bssid) &&
+		    nr->short_ssid != hapd->conf->ssid.short_ssid)
+			return nr;
+	}
+	return NULL;
+}
+
+
+int hostapd_neighbor_sync_own_report(struct hostapd_data *hapd)
+{
+	struct hostapd_neighbor_entry *nr;
+
+	nr = hostapd_neighbor_get_diff_short_ssid(hapd, hapd->own_addr);
+	if (!nr)
+		return -1;
+
+	/* Clear old entry due to SSID change */
+	hostapd_neighbor_free(nr);
+
+	hostapd_neighbor_set_own_report(hapd);
+
+	return 0;
+}
diff --git a/src/ap/neighbor_db.h b/src/ap/neighbor_db.h
index 992671b..53f7142 100644
--- a/src/ap/neighbor_db.h
+++ b/src/ap/neighbor_db.h
@@ -20,6 +20,7 @@
 			 const struct wpabuf *civic, int stationary,
 			 u8 bss_parameters);
 void hostapd_neighbor_set_own_report(struct hostapd_data *hapd);
+int hostapd_neighbor_sync_own_report(struct hostapd_data *hapd);
 int hostapd_neighbor_remove(struct hostapd_data *hapd, const u8 *bssid,
 			    const struct wpa_ssid_value *ssid);
 void hostapd_free_neighbor_db(struct hostapd_data *hapd);
diff --git a/src/ap/rrm.c b/src/ap/rrm.c
index f2d5cd1..fbcddf3 100644
--- a/src/ap/rrm.c
+++ b/src/ap/rrm.c
@@ -334,6 +334,53 @@
 }
 
 
+static void hostapd_link_mesr_rep_timeout_handler(void *eloop_data,
+						  void *user_ctx)
+{
+	struct hostapd_data *hapd = eloop_data;
+
+	wpa_printf(MSG_DEBUG,
+		   "RRM: Link measurement request (token %u) timed out",
+		   hapd->link_measurement_req_token);
+	hapd->link_mesr_req_active = 0;
+}
+
+
+static void hostapd_handle_link_mesr_report(struct hostapd_data *hapd,
+					    const u8 *buf, size_t len)
+{
+	const struct ieee80211_mgmt *mgmt = (const struct ieee80211_mgmt *) buf;
+	const struct rrm_link_measurement_report *report;
+	const u8 *pos, *end;
+	char report_msg[2 * 8 + 1];
+
+	end = buf + len;
+	pos = mgmt->u.action.u.rrm.variable;
+	report = (const struct rrm_link_measurement_report *) (pos - 1);
+	if (end - (const u8 *) report < (int) sizeof(*report))
+		return;
+
+	if (!hapd->link_mesr_req_active ||
+	    (hapd->link_measurement_req_token != report->dialog_token)) {
+		wpa_printf(MSG_INFO,
+			   "Unexpected Link measurement report, token %u",
+			   report->dialog_token);
+		return;
+	}
+
+	hapd->link_mesr_req_active = 0;
+	eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler, hapd, NULL);
+
+	report_msg[0] = '\0';
+	if (wpa_snprintf_hex(report_msg, sizeof(report_msg),
+			     pos, end - pos) < 0)
+		return;
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, LINK_MSR_RESP_RX MACSTR " %u %s",
+		MAC2STR(mgmt->sa), report->dialog_token, report_msg);
+}
+
+
 void hostapd_handle_radio_measurement(struct hostapd_data *hapd,
 				      const u8 *buf, size_t len)
 {
@@ -356,6 +403,9 @@
 	case WLAN_RRM_NEIGHBOR_REPORT_REQUEST:
 		hostapd_handle_nei_report_req(hapd, buf, len);
 		break;
+	case WLAN_RRM_LINK_MEASUREMENT_REPORT:
+		hostapd_handle_link_mesr_report(hapd, buf, len);
+		break;
 	default:
 		wpa_printf(MSG_DEBUG, "RRM action %u is not supported",
 			   mgmt->u.action.u.rrm.action);
@@ -563,6 +613,7 @@
 	hapd->lci_req_active = 0;
 	eloop_cancel_timeout(hostapd_range_rep_timeout_handler, hapd, NULL);
 	hapd->range_req_active = 0;
+	eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler, hapd, NULL);
 }
 
 
@@ -672,3 +723,73 @@
 		" %u ack=%d", MAC2STR(mgmt->da),
 		mgmt->u.action.u.rrm.dialog_token, ok);
 }
+
+
+int hostapd_send_link_measurement_req(struct hostapd_data *hapd, const u8 *addr)
+{
+	struct wpabuf *buf;
+	struct sta_info *sta;
+	int ret;
+
+	wpa_printf(MSG_DEBUG, "Request Link Measurement: dest addr " MACSTR,
+		   MAC2STR(addr));
+
+	if (!(hapd->iface->drv_rrm_flags &
+	      WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) {
+		wpa_printf(MSG_INFO,
+			   "Request Link Measurement: the driver does not support TX power insertion");
+		return -1;
+	}
+
+	sta = ap_get_sta(hapd, addr);
+	if (!sta || !(sta->flags & WLAN_STA_AUTHORIZED)) {
+		wpa_printf(MSG_INFO,
+			   "Request Link Measurement: specied STA is not connected");
+		return -1;
+	}
+
+	if (!(sta->rrm_enabled_capa[0] & WLAN_RRM_CAPS_LINK_MEASUREMENT)) {
+		wpa_printf(MSG_INFO,
+			   "Request Link Measurement: destination STA does not support link measurement");
+		return -1;
+	}
+
+	if (hapd->link_mesr_req_active) {
+		wpa_printf(MSG_DEBUG,
+			   "Request Link Measurement: request already in process - overriding");
+		hapd->link_mesr_req_active = 0;
+		eloop_cancel_timeout(hostapd_link_mesr_rep_timeout_handler,
+				     hapd, NULL);
+	}
+
+	/* Action + Action type + token + Tx Power used + Max Tx Power = 5 */
+	buf = wpabuf_alloc(5);
+	if (!buf)
+		return -1;
+
+	hapd->link_measurement_req_token++;
+	if (!hapd->link_measurement_req_token)
+		hapd->link_measurement_req_token++;
+
+	wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
+	wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REQUEST);
+	wpabuf_put_u8(buf, hapd->link_measurement_req_token);
+	/* NOTE: The driver is expected to fill the Tx Power Used and Max Tx
+	 * Power */
+	wpabuf_put_u8(buf, 0);
+	wpabuf_put_u8(buf, 0);
+
+	ret = hostapd_drv_send_action(hapd, hapd->iface->freq, 0, addr,
+				      wpabuf_head(buf), wpabuf_len(buf));
+	wpabuf_free(buf);
+	if (ret < 0)
+		return ret;
+
+	hapd->link_mesr_req_active = 1;
+
+	eloop_register_timeout(HOSTAPD_RRM_REQUEST_TIMEOUT, 0,
+			       hostapd_link_mesr_rep_timeout_handler, hapd,
+			       NULL);
+
+	return hapd->link_measurement_req_token;
+}
diff --git a/src/ap/rrm.h b/src/ap/rrm.h
index 02cd522..17751e0 100644
--- a/src/ap/rrm.h
+++ b/src/ap/rrm.h
@@ -29,5 +29,7 @@
 void hostapd_rrm_beacon_req_tx_status(struct hostapd_data *hapd,
 				      const struct ieee80211_mgmt *mgmt,
 				      size_t len, int ok);
+int hostapd_send_link_measurement_req(struct hostapd_data *hapd,
+				      const u8 *addr);
 
 #endif /* RRM_H */
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 2178d65..32944ed 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -180,13 +180,26 @@
 		sta->pasn->fils.erp_resp = NULL;
 #endif /* CONFIG_FILS */
 
-		bin_clear_free(sta->pasn, sizeof(*sta->pasn));
+		pasn_data_deinit(sta->pasn);
 		sta->pasn = NULL;
 	}
 }
 
 #endif /* CONFIG_PASN */
 
+
+static void __ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hostapd_sta_is_link_sta(hapd, sta) &&
+	    !hostapd_drv_link_sta_remove(hapd, sta->addr))
+		return;
+#endif /* CONFIG_IEEE80211BE */
+
+	hostapd_drv_sta_remove(hapd, sta->addr);
+}
+
+
 void ap_free_sta(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	int set_beacon = 0;
@@ -209,7 +222,7 @@
 
 	if (!hapd->iface->driver_ap_teardown &&
 	    !(sta->flags & WLAN_STA_PREAUTH)) {
-		hostapd_drv_sta_remove(hapd, sta->addr);
+		__ap_free_sta(hapd, sta);
 		sta->added_unassoc = 0;
 	}
 
@@ -454,6 +467,27 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+void hostapd_free_link_stas(struct hostapd_data *hapd)
+{
+	struct sta_info *sta, *prev;
+
+	sta = hapd->sta_list;
+	while (sta) {
+		prev = sta;
+		sta = sta->next;
+
+		if (!hostapd_sta_is_link_sta(hapd, prev))
+			continue;
+
+		wpa_printf(MSG_DEBUG, "Removing link station from MLD " MACSTR,
+			   MAC2STR(prev->addr));
+		ap_free_sta(hapd, prev);
+	}
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 /**
  * ap_handle_timer - Per STA timer handler
  * @eloop_ctx: struct hostapd_data *
@@ -970,16 +1004,15 @@
 	interfaces = assoc_hapd->iface->interfaces;
 
 	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!assoc_sta->mld_info.links[link_id].valid)
+			continue;
+
 		for (i = 0; i < interfaces->count; i++) {
 			struct sta_info *tmp_sta;
 
-			if (!assoc_sta->mld_info.links[link_id].valid)
-				continue;
-
 			tmp_hapd = interfaces->iface[i]->bss[0];
 
-			if (!tmp_hapd->conf->mld_ap ||
-			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+			if (!hostapd_is_ml_partner(tmp_hapd, assoc_hapd))
 				continue;
 
 			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
@@ -1433,7 +1466,7 @@
 	u8 addr[ETH_ALEN];
 	u8 ip_addr_buf[4];
 #endif /* CONFIG_P2P */
-	u8 *ip_ptr = NULL;
+	const u8 *ip_ptr = NULL;
 
 #ifdef CONFIG_P2P
 	if (hapd->p2p_group == NULL) {
@@ -1731,7 +1764,7 @@
 	unsigned int i, j;
 
 	for_each_mld_link(tmp_hapd, i, j, hapd->iface->interfaces,
-			  hapd->conf->mld_id) {
+			  hostapd_get_mld_id(hapd)) {
 		struct sta_info *tmp_sta;
 
 		if (hapd == tmp_hapd)
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index b136ff7..153e4a0 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -440,4 +440,6 @@
 
 void ap_sta_free_sta_profile(struct mld_info *info);
 
+void hostapd_free_link_stas(struct hostapd_data *hapd);
+
 #endif /* STA_INFO_H */
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index b77e21b..af8ccca 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -51,7 +51,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->conf->mld_ap && (!sta || ap_sta_is_mld(hapd, sta)))
-		own_addr = hapd->mld_addr;
+		own_addr = hapd->mld->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
 	return own_addr;
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 3002d91..4bf8d79 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -3345,10 +3345,7 @@
 	}
 
 	/* Find matching link ID and the MAC address for each link */
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!(kde->valid_mlo_links & BIT(i)))
-			continue;
-
+	for_each_link(kde->valid_mlo_links, i) {
 		/*
 		 * Each entry should contain the link information and the MAC
 		 * address.
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index b286a77..34de45c 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -1559,8 +1559,7 @@
 			struct hostapd_iface *iface =
 				hapd->iface->interfaces->iface[j];
 
-			if (!iface->bss[0]->conf->mld_ap ||
-			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			if (!hostapd_is_ml_partner(hapd, iface->bss[0]) ||
 			    link_id != iface->bss[0]->mld_link_id ||
 			    !iface->bss[0]->wpa_auth)
 				continue;
@@ -1602,8 +1601,7 @@
 			struct hostapd_iface *iface =
 				hapd->iface->interfaces->iface[j];
 
-			if (!iface->bss[0]->conf->mld_ap ||
-			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			if (!hostapd_is_ml_partner(hapd, iface->bss[0]) ||
 			    link_id != iface->bss[0]->mld_link_id ||
 			    !iface->bss[0]->wpa_auth)
 				continue;
diff --git a/src/build.rules b/src/build.rules
index acda884..c756ccb 100644
--- a/src/build.rules
+++ b/src/build.rules
@@ -80,7 +80,7 @@
 _DIRS := $(BUILDDIR)/$(PROJ)
 .PHONY: _make_dirs
 _make_dirs:
-	@mkdir -p $(_DIRS)
+	@mkdir -p $(sort $(_DIRS))
 
 $(BUILDDIR)/$(PROJ)/src/%.o: $(ROOTDIR)src/%.c $(CONFIG_FILE) | _make_dirs
 	$(Q)$(CC) -c -o $@ $(CFLAGS) $<
diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c
index f45d2e9..2c47bf8 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -381,6 +381,75 @@
 }
 
 
+static void punct_update_legacy_bw_80(u8 bitmap, u8 pri_chan, u8 *seg0)
+{
+	u8 first_chan = *seg0 - 6, sec_chan;
+
+	switch (bitmap) {
+	case 0x6:
+		*seg0 = 0;
+		return;
+	case 0x8:
+	case 0x4:
+	case 0x2:
+	case 0x1:
+	case 0xC:
+	case 0x3:
+		if (pri_chan < *seg0)
+			*seg0 -= 4;
+		else
+			*seg0 += 4;
+		break;
+	}
+
+	if (pri_chan < *seg0)
+		sec_chan = pri_chan + 4;
+	else
+		sec_chan = pri_chan - 4;
+
+	if (bitmap & BIT((sec_chan - first_chan) / 4))
+		*seg0 = 0;
+}
+
+
+static void punct_update_legacy_bw_160(u8 bitmap, u8 pri,
+				       enum oper_chan_width *width, u8 *seg0)
+{
+	if (pri < *seg0) {
+		*seg0 -= 8;
+		if (bitmap & 0x0F) {
+			*width = 0;
+			punct_update_legacy_bw_80(bitmap & 0xF, pri, seg0);
+		}
+	} else {
+		*seg0 += 8;
+		if (bitmap & 0xF0) {
+			*width = 0;
+			punct_update_legacy_bw_80((bitmap & 0xF0) >> 4, pri,
+						  seg0);
+		}
+	}
+}
+
+
+void punct_update_legacy_bw(u16 bitmap, u8 pri, enum oper_chan_width *width,
+			    u8 *seg0, u8 *seg1)
+{
+	if (*width == CONF_OPER_CHWIDTH_80MHZ && (bitmap & 0xF)) {
+		*width = CONF_OPER_CHWIDTH_USE_HT;
+		punct_update_legacy_bw_80(bitmap & 0xF, pri, seg0);
+	}
+
+	if (*width == CONF_OPER_CHWIDTH_160MHZ && (bitmap & 0xFF)) {
+		*width = CONF_OPER_CHWIDTH_80MHZ;
+		*seg1 = 0;
+		punct_update_legacy_bw_160(bitmap & 0xFF, pri, width, seg0);
+	}
+
+	/* TODO: 320 MHz */
+}
+
+
 int hostapd_set_freq_params(struct hostapd_freq_params *data,
 			    enum hostapd_hw_mode mode,
 			    int freq, int channel, int enable_edmg,
@@ -391,8 +460,12 @@
 			    int center_segment0,
 			    int center_segment1, u32 vht_caps,
 			    struct he_capabilities *he_cap,
-			    struct eht_capabilities *eht_cap)
+			    struct eht_capabilities *eht_cap,
+			    u16 punct_bitmap)
 {
+	enum oper_chan_width oper_chwidth_legacy;
+	u8 seg0_legacy, seg1_legacy;
+
 	if (!he_cap || !he_cap->he_supported)
 		he_enabled = 0;
 	if (!eht_cap || !eht_cap->eht_supported)
@@ -578,6 +651,14 @@
 		break;
 	}
 
+	oper_chwidth_legacy = oper_chwidth;
+	seg0_legacy = center_segment0;
+	seg1_legacy = center_segment1;
+	if (punct_bitmap)
+		punct_update_legacy_bw(punct_bitmap, channel,
+				       &oper_chwidth_legacy,
+				       &seg0_legacy, &seg1_legacy);
+
 	if (data->eht_enabled || data->he_enabled ||
 	    data->vht_enabled) switch (oper_chwidth) {
 	case CONF_OPER_CHWIDTH_USE_HT:
@@ -602,7 +683,8 @@
 		/* fall through */
 	case CONF_OPER_CHWIDTH_80MHZ:
 		data->bandwidth = 80;
-		if (!sec_channel_offset) {
+		if (!sec_channel_offset &&
+		    oper_chwidth_legacy != CONF_OPER_CHWIDTH_USE_HT) {
 			wpa_printf(MSG_ERROR,
 				   "80/80+80 MHz: no second channel offset");
 			return -1;
@@ -660,7 +742,8 @@
 				   "160 MHz: center segment 1 should not be set");
 			return -1;
 		}
-		if (!sec_channel_offset) {
+		if (!sec_channel_offset &&
+		    oper_chwidth_legacy != CONF_OPER_CHWIDTH_USE_HT) {
 			wpa_printf(MSG_ERROR,
 				   "160 MHz: second channel offset not set");
 			return -1;
diff --git a/src/common/hw_features_common.h b/src/common/hw_features_common.h
index 82e0282..e791c33 100644
--- a/src/common/hw_features_common.h
+++ b/src/common/hw_features_common.h
@@ -35,6 +35,8 @@
 int check_40mhz_2g4(struct hostapd_hw_modes *mode,
 		    struct wpa_scan_results *scan_res, int pri_chan,
 		    int sec_chan);
+void punct_update_legacy_bw(u16 bitmap, u8 pri_chan,
+			    enum oper_chan_width *width, u8 *seg0, u8 *seg1);
 int hostapd_set_freq_params(struct hostapd_freq_params *data,
 			    enum hostapd_hw_mode mode,
 			    int freq, int channel, int edmg, u8 edmg_channel,
@@ -45,7 +47,8 @@
 			    int center_segment0,
 			    int center_segment1, u32 vht_caps,
 			    struct he_capabilities *he_caps,
-			    struct eht_capabilities *eht_cap);
+			    struct eht_capabilities *eht_cap,
+			    u16 punct_bitmap);
 void set_disable_ht40(struct ieee80211_ht_capabilities *htcaps,
 		      int disabled);
 int ieee80211ac_cap_check(u32 hw, u32 conf);
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index 08ba45b..4de88d1 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -2050,6 +2050,13 @@
 }
 
 
+bool is_80plus_op_class(u8 op_class)
+{
+	/* Operating classes with "80+" behavior indication in Table E-4 */
+	return op_class == 130 || op_class == 135;
+}
+
+
 static int is_11b(u8 rate)
 {
 	return rate == 0x02 || rate == 0x04 || rate == 0x0b || rate == 0x16
@@ -2417,9 +2424,17 @@
 	 * channel center frequency index value, but it happens to be a 20 MHz
 	 * channel and the channel number in the channel set would match the
 	 * value in for the frequency center.
+	 *
+	 * Operating class value pair 128 and 130 is used to describe a 80+80
+	 * MHz channel on the 5 GHz band. 130 is identified with "80+", so this
+	 * is encoded with two octets 130 and 128. Similarly, operating class
+	 * value pair 133 and 135 is used to describe a 80+80 MHz channel on
+	 * the 6 GHz band (135 being the one with "80+" indication). All other
+	 * operating classes listed here are used as 1-octet values.
 	 */
 	{ HOSTAPD_MODE_IEEE80211A, 128, 36, 177, 4, BW80, P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211A, 129, 36, 177, 4, BW160, P2P_SUPP },
+	{ HOSTAPD_MODE_IEEE80211A, 130, 36, 177, 4, BW80P80, P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211A, 131, 1, 233, 4, BW20, P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211A, 132, 1, 233, 8, BW40, P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211A, 133, 1, 233, 16, BW80, P2P_SUPP },
@@ -2427,6 +2442,9 @@
 	{ HOSTAPD_MODE_IEEE80211A, 135, 1, 233, 16, BW80P80, NO_P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211A, 136, 2, 2, 4, BW20, NO_P2P_SUPP },
 
+	/* IEEE P802.11be/D5.0, Table E-4 (Global operating classes) */
+	{ HOSTAPD_MODE_IEEE80211A, 137, 31, 191, 32, BW320, NO_P2P_SUPP },
+
 	/*
 	 * IEEE Std 802.11ad-2012 and P802.ay/D5.0 60 GHz operating classes.
 	 * Class 180 has the legacy channels 1-6. Classes 181-183 include
@@ -2437,11 +2455,6 @@
 	{ HOSTAPD_MODE_IEEE80211AD, 182, 17, 20, 1, BW6480, P2P_SUPP },
 	{ HOSTAPD_MODE_IEEE80211AD, 183, 25, 27, 1, BW8640, P2P_SUPP },
 
-	/* Keep the operating class 130 as the last entry as a workaround for
-	 * the OneHundredAndThirty Delimiter value used in the Supported
-	 * Operating Classes element to indicate the end of the Operating
-	 * Classes field. */
-	{ HOSTAPD_MODE_IEEE80211A, 130, 36, 177, 4, BW80P80, P2P_SUPP },
 	{ -1, 0, 0, 0, 0, BW20, NO_P2P_SUPP }
 };
 
@@ -2569,21 +2582,141 @@
 }
 
 
-size_t add_multi_ap_ie(u8 *buf, size_t len, u8 value)
+u16 check_multi_ap_ie(const u8 *multi_ap_ie, size_t multi_ap_len,
+		      struct multi_ap_params *multi_ap)
+{
+	const struct element *elem;
+	bool ext_present = false;
+	unsigned int vlan_id;
+
+	os_memset(multi_ap, 0, sizeof(*multi_ap));
+
+	/* Default profile is 1, when Multi-AP profile subelement is not
+	 * present in the element. */
+	multi_ap->profile = 1;
+
+	for_each_element(elem, multi_ap_ie, multi_ap_len) {
+		u8 id = elem->id, elen = elem->datalen;
+		const u8 *pos = elem->data;
+
+		switch (id) {
+		case MULTI_AP_SUB_ELEM_TYPE:
+			if (elen >= 1) {
+				multi_ap->capability = *pos;
+				ext_present = true;
+			} else {
+				wpa_printf(MSG_DEBUG,
+					   "Multi-AP invalid Multi-AP subelement");
+				return WLAN_STATUS_INVALID_IE;
+			}
+			break;
+		case MULTI_AP_PROFILE_SUB_ELEM_TYPE:
+			if (elen < 1) {
+				wpa_printf(MSG_DEBUG,
+					   "Multi-AP IE invalid Multi-AP profile subelement");
+				return WLAN_STATUS_INVALID_IE;
+			}
+
+			multi_ap->profile = *pos;
+			if (multi_ap->profile > MULTI_AP_PROFILE_MAX) {
+				wpa_printf(MSG_DEBUG,
+					   "Multi-AP IE with invalid profile 0x%02x",
+					   multi_ap->profile);
+				return WLAN_STATUS_ASSOC_DENIED_UNSPEC;
+			}
+			break;
+		case MULTI_AP_VLAN_SUB_ELEM_TYPE:
+			if (multi_ap->profile < MULTI_AP_PROFILE_2) {
+				wpa_printf(MSG_DEBUG,
+					   "Multi-AP IE invalid profile to read VLAN IE");
+				return WLAN_STATUS_INVALID_IE;
+			}
+			if (elen < 2) {
+				wpa_printf(MSG_DEBUG,
+					   "Multi-AP IE invalid Multi-AP VLAN subelement");
+				return WLAN_STATUS_INVALID_IE;
+			}
+
+			vlan_id = WPA_GET_LE16(pos);
+			if (vlan_id < 1 || vlan_id > 4094) {
+				wpa_printf(MSG_INFO,
+					   "Multi-AP IE invalid Multi-AP VLAN ID %d",
+					   vlan_id);
+				return WLAN_STATUS_INVALID_IE;
+			}
+			multi_ap->vlanid = vlan_id;
+			break;
+		default:
+			wpa_printf(MSG_DEBUG,
+				   "Ignore unknown subelement %u in Multi-AP IE",
+				   id);
+			break;
+		}
+	}
+
+	if (!for_each_element_completed(elem, multi_ap_ie, multi_ap_len)) {
+		wpa_printf(MSG_DEBUG, "Multi AP IE parse failed @%d",
+			   (int) (multi_ap_ie + multi_ap_len -
+				  (const u8 *) elem));
+		wpa_hexdump(MSG_MSGDUMP, "IEs", multi_ap_ie, multi_ap_len);
+	}
+
+	if (!ext_present) {
+		wpa_printf(MSG_DEBUG,
+			   "Multi-AP element without Multi-AP Extension subelement");
+		return WLAN_STATUS_INVALID_IE;
+	}
+
+	return WLAN_STATUS_SUCCESS;
+}
+
+
+size_t add_multi_ap_ie(u8 *buf, size_t len,
+		       const struct multi_ap_params *multi_ap)
 {
 	u8 *pos = buf;
+	u8 *len_ptr;
 
-	if (len < 9)
+	if (len < 6)
 		return 0;
 
 	*pos++ = WLAN_EID_VENDOR_SPECIFIC;
-	*pos++ = 7; /* len */
+	len_ptr = pos; /* Length field to be set at the end */
+	pos++;
 	WPA_PUT_BE24(pos, OUI_WFA);
 	pos += 3;
 	*pos++ = MULTI_AP_OUI_TYPE;
+
+	/* Multi-AP Extension subelement */
+	if (buf + len - pos < 3)
+		return 0;
 	*pos++ = MULTI_AP_SUB_ELEM_TYPE;
 	*pos++ = 1; /* len */
-	*pos++ = value;
+	*pos++ = multi_ap->capability;
+
+	/* Add Multi-AP Profile subelement only for R2 or newer configuration */
+	if (multi_ap->profile >= MULTI_AP_PROFILE_2) {
+		if (buf + len - pos < 3)
+			return 0;
+		*pos++ = MULTI_AP_PROFILE_SUB_ELEM_TYPE;
+		*pos++ = 1;
+		*pos++ = multi_ap->profile;
+	}
+
+	/* Add Multi-AP Default 802.1Q Setting subelement only for backhaul BSS
+	 */
+	if (multi_ap->vlanid &&
+	    multi_ap->profile >= MULTI_AP_PROFILE_2 &&
+	    (multi_ap->capability & MULTI_AP_BACKHAUL_BSS)) {
+		if (buf + len - pos < 4)
+			return 0;
+		*pos++ = MULTI_AP_VLAN_SUB_ELEM_TYPE;
+		*pos++ = 2;
+		WPA_PUT_LE16(pos, multi_ap->vlanid);
+		pos += 2;
+	}
+
+	*len_ptr = pos - len_ptr - 1;
 
 	return pos - buf;
 }
@@ -2748,6 +2881,8 @@
 	case BW80P80:
 	case BW160:
 		return 160;
+	case BW320:
+		return 320;
 	case BW2160:
 		return 2160;
 	default:
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 60260e5..56eb0df 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -30,6 +30,12 @@
 	u8 nof_ies;
 };
 
+struct multi_ap_params {
+	u8 capability;
+	u8 profile;
+	u16 vlanid;
+};
+
 /* Parsed Information Elements */
 struct ieee802_11_elems {
 	const u8 *ssid;
@@ -235,6 +241,7 @@
 int ieee80211_is_dfs(int freq, const struct hostapd_hw_modes *modes,
 		     u16 num_modes);
 int is_dfs_global_op_class(u8 op_class);
+bool is_80plus_op_class(u8 op_class);
 enum phy_type ieee80211_get_phy_type(int freq, int ht, int vht);
 
 int supp_rates_11b_only(struct ieee802_11_elems *elems);
@@ -253,7 +260,7 @@
 	u8 max_chan;
 	u8 inc;
 	enum { BW20, BW40PLUS, BW40MINUS, BW40, BW80, BW2160, BW160, BW80P80,
-	       BW4320, BW6480, BW8640} bw;
+	       BW320, BW4320, BW6480, BW8640} bw;
 	enum { P2P_SUPP, NO_P2P_SUPP } p2p;
 };
 
@@ -266,7 +273,10 @@
 
 size_t mbo_add_ie(u8 *buf, size_t len, const u8 *attr, size_t attr_len);
 
-size_t add_multi_ap_ie(u8 *buf, size_t len, u8 value);
+u16 check_multi_ap_ie(const u8 *multi_ap_ie, size_t multi_ap_len,
+		      struct multi_ap_params *multi_ap);
+size_t add_multi_ap_ie(u8 *buf, size_t len,
+		       const struct multi_ap_params *multi_ap);
 
 struct country_op_class {
 	u8 country_op_class;
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index 619aa19..644bebd 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -1453,11 +1453,20 @@
 #define WFA_CAPA_OUI_TYPE 0x23
 
 #define MULTI_AP_SUB_ELEM_TYPE 0x06
+#define MULTI_AP_PROFILE_SUB_ELEM_TYPE 0x07
+#define MULTI_AP_VLAN_SUB_ELEM_TYPE 0x08
+
+#define MULTI_AP_PROFILE2_BACKHAUL_STA_DISALLOWED BIT(2)
+#define MULTI_AP_PROFILE1_BACKHAUL_STA_DISALLOWED BIT(3)
 #define MULTI_AP_TEAR_DOWN BIT(4)
 #define MULTI_AP_FRONTHAUL_BSS BIT(5)
 #define MULTI_AP_BACKHAUL_BSS BIT(6)
 #define MULTI_AP_BACKHAUL_STA BIT(7)
 
+#define MULTI_AP_PROFILE_1	1
+#define MULTI_AP_PROFILE_2	2
+#define MULTI_AP_PROFILE_MAX	6
+
 #define WMM_OUI_TYPE 2
 #define WMM_OUI_SUBTYPE_INFORMATION_ELEMENT 0
 #define WMM_OUI_SUBTYPE_PARAMETER_ELEMENT 1
diff --git a/src/common/ptksa_cache.c b/src/common/ptksa_cache.c
index c00f288..918a1cc 100644
--- a/src/common/ptksa_cache.c
+++ b/src/common/ptksa_cache.c
@@ -19,6 +19,8 @@
 	unsigned int n_ptksa;
 };
 
+#ifdef CONFIG_PTKSA_CACHE
+
 static void ptksa_cache_set_expiration(struct ptksa_cache *ptksa);
 
 
@@ -342,3 +344,44 @@
 
 	return entry;
 }
+
+#else /* CONFIG_PTKSA_CACHE */
+
+struct ptksa_cache * ptksa_cache_init(void)
+{
+	return (struct ptksa_cache *) 1;
+}
+
+
+void ptksa_cache_deinit(struct ptksa_cache *ptksa)
+{
+}
+
+
+struct ptksa_cache_entry *
+ptksa_cache_get(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
+{
+	return NULL;
+}
+
+
+int ptksa_cache_list(struct ptksa_cache *ptksa, char *buf, size_t len)
+{
+	return -1;
+}
+
+
+struct ptksa_cache_entry *
+ptksa_cache_add(struct ptksa_cache *ptksa, const u8 *own_addr, const u8 *addr,
+		u32 cipher, u32 life_time, const struct wpa_ptk *ptk,
+		void (*cb)(struct ptksa_cache_entry *e), void *ctx, u32 akmp)
+{
+	return NULL;
+}
+
+
+void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
+{
+}
+
+#endif /* CONFIG_PTKSA_CACHE */
diff --git a/src/common/ptksa_cache.h b/src/common/ptksa_cache.h
index 6182215..dd5e7db 100644
--- a/src/common/ptksa_cache.h
+++ b/src/common/ptksa_cache.h
@@ -29,7 +29,6 @@
 	u32 akmp;
 };
 
-#ifdef CONFIG_PTKSA_CACHE
 
 struct ptksa_cache;
 
@@ -48,41 +47,4 @@
 					   void *ctx, u32 akmp);
 void ptksa_cache_flush(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher);
 
-#else /* CONFIG_PTKSA_CACHE */
-
-static inline struct ptksa_cache * ptksa_cache_init(void)
-{
-	return (struct ptksa_cache *) 1;
-}
-
-static inline void ptksa_cache_deinit(struct ptksa_cache *ptksa)
-{
-}
-
-static inline struct ptksa_cache_entry *
-ptksa_cache_get(struct ptksa_cache *ptksa, const u8 *addr, u32 cipher)
-{
-	return NULL;
-}
-
-static inline int ptksa_cache_list(struct ptksa_cache *ptksa,
-				   char *buf, size_t len)
-{
-	return -1;
-}
-
-static inline struct ptksa_cache_entry *
-ptksa_cache_add(struct ptksa_cache *ptksa, const u8 *own_addr, const u8 *addr,
-		u32 cipher, u32 life_time, const struct wpa_ptk *ptk,
-		void (*cb)(struct ptksa_cache_entry *e), void *ctx, u32 akmp)
-{
-	return NULL;
-}
-
-static inline void ptksa_cache_flush(struct ptksa_cache *ptksa,
-				     const u8 *addr, u32 cipher)
-{
-}
-
-#endif /* CONFIG_PTKSA_CACHE */
 #endif /* PTKSA_CACHE_H */
diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h
index a5bbc78..2a4086b 100644
--- a/src/common/qca-vendor.h
+++ b/src/common/qca-vendor.h
@@ -1048,6 +1048,15 @@
  *	to user space to disassociate with a peer based on the peer MAC address
  *	provided. Specify the peer MAC address in
  *	QCA_WLAN_VENDOR_ATTR_MAC_ADDR. For MLO, MLD MAC address is provided.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_ADJUST_TX_POWER: This vendor command is used to
+ *	adjust transmit power. The attributes used with this subcommand are
+ *	defined in enum qca_wlan_vendor_attr_adjust_tx_power.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_SPECTRAL_SCAN_COMPLETE: Event indication from the
+ *	driver to notify user application about the spectral scan completion.
+ *	The attributes used with this subcommand are defined in
+ *	enum qca_wlan_vendor_attr_spectral_scan_complete.
  */
 enum qca_nl80211_vendor_subcmds {
 	QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
@@ -1272,6 +1281,8 @@
 	QCA_NL80211_VENDOR_SUBCMD_FW_PAGE_FAULT_REPORT = 238,
 	QCA_NL80211_VENDOR_SUBCMD_FLOW_POLICY = 239,
 	QCA_NL80211_VENDOR_SUBCMD_DISASSOC_PEER = 240,
+	QCA_NL80211_VENDOR_SUBCMD_ADJUST_TX_POWER = 241,
+	QCA_NL80211_VENDOR_SUBCMD_SPECTRAL_SCAN_COMPLETE = 242,
 };
 
 /* Compatibility defines for previously used subcmd names.
@@ -3361,6 +3372,14 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_QCA_PEER = 106,
 
+	/* 8-bit unsigned value to configure BTM support.
+	 *
+	 * The attribute is applicable only for STA interface. Uses enum
+	 * qca_wlan_btm_support values. This configuration is not allowed in
+	 * connected state.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_BTM_SUPPORT = 107,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_CONFIG_MAX =
@@ -7749,6 +7768,20 @@
 	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_FFT_RECAPTURE = 31,
 	/* Attribute used for padding for 64-bit alignment */
 	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_PAD = 32,
+	/* Spectral data transport mode. u32 attribute. It uses values
+	 * defined in enum qca_wlan_vendor_spectral_data_transport_mode.
+	 * This is an optional attribute. If this attribute is not populated,
+	 * the driver should configure the default transport mode to netlink.
+	 */
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_DATA_TRANSPORT_MODE = 33,
+	/* Spectral scan completion timeout. u32 attribute. This
+	 * attribute is used to configure a timeout value (in us). The
+	 * timeout value would be from the beginning of a spectral
+	 * scan. This is an optional attribute. If this attribute is
+	 * not populated, the driver would internally derive the
+	 * timeout value.
+	 */
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETION_TIMEOUT = 34,
 
 	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_CONFIG_MAX =
@@ -12055,6 +12088,14 @@
  * %QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_POWER_CAP_DBM or based on
  * regulatory/SAE limits if %QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_POWER_CAP_DBM
  * is not provided.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_IFINDEX: u32 attribute, optional.
+ * This specifies the interface index (netdev) for which the corresponding
+ * configurations are applied. If the interface index is not specified, the
+ * configurations are applied based on
+ * %QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_IFACES_BITMASK.
+ * %QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_IFACES_BITMASK along with this
+ * attribute shall have the matching nl80211_iftype.
  */
 enum qca_wlan_vendor_attr_avoid_frequency_ext {
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_INVALID = 0,
@@ -12063,6 +12104,7 @@
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_END = 3,
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_POWER_CAP_DBM = 4,
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_IFACES_BITMASK = 5,
+	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_IFINDEX = 6,
 
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_AVOID_FREQUENCY_MAX =
@@ -14263,7 +14305,11 @@
  * @QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_BITMAP: binary, rate mask bitmap.
  * A bit value of 1 represents rate is enabled and a value of 0
  * represents rate is disabled.
- * For HE targets, 12 bits correspond to one NSS setting.
+ * For EHT targets,
+ * b0-1  => NSS1, MCS 14-15
+ * b2-15 => NSS1, MCS 0-13
+ * b16-29 => NSS2, MCS 0-13
+ * For HE targets, 14 bits correspond to one NSS setting.
  * b0-13  => NSS1, MCS 0-13
  * b14-27 => NSS2, MCS 0-13 and so on for other NSS.
  * For VHT targets, 10 bits correspond to one NSS setting.
@@ -14273,12 +14319,18 @@
  * b0-7  => NSS1, MCS 0-7
  * b8-15 => NSS2, MCS 0-7 and so on for other NSS.
  * For OFDM/CCK targets, 8 bits correspond to one NSS setting.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_LINK_ID: u8, used to specify the
+ * MLO link ID of a link to be configured. Optional attribute.
+ * No need of this attribute in non-MLO cases. If the attribute is
+ * not provided, ratemask will be applied for setup link.
  */
 enum qca_wlan_vendor_attr_ratemask_params {
 	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_INVALID = 0,
 	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_LIST = 1,
 	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_TYPE = 2,
 	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_BITMAP = 3,
+	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_LINK_ID = 4,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_RATEMASK_PARAMS_AFTER_LAST,
@@ -16794,4 +16846,200 @@
 	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_LAST - 1,
 };
 
+/**
+ * enum qca_wlan_btm_support: BTM support configuration
+ *
+ * @QCA_WLAN_BTM_SUPPORT_DEFAULT: Restore default BTM support policy. The driver
+ * follows the BSS Transition bit in the Extended Capabilities element from the
+ * connect request IEs with the default BTM support policy.
+ *
+ * @QCA_WLAN_BTM_SUPPORT_DISABLE: Disable BTM support for the subsequent
+ * (re)association attempts. The driver shall restore the default BTM support
+ * policy during the first disconnection after successful association. When this
+ * configuration is enabled, the driver shall overwrite the BSS Transition bit
+ * as zero in the Extended Capabilities element while sending (Re)Association
+ * Request frames. Also, the driver shall drop the BTM frames from userspace and
+ * the connected AP when this configuration is enabled.
+ */
+enum qca_wlan_btm_support {
+	QCA_WLAN_BTM_SUPPORT_DEFAULT = 0,
+	QCA_WLAN_BTM_SUPPORT_DISABLE = 1,
+};
+
+/**
+ * enum qca_wlan_vendor_data_rate_type - Represents the possible values for
+ * attribute %QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_TYPE.
+ *
+ * @QCA_WLAN_VENDOR_DATA_RATE_TYPE_LEGACY: Data rate type is a legacy rate code
+ * used in OFDM/CCK.
+ *
+ * @QCA_WLAN_VENDOR_DATA_RATE_TYPE_MCS: Data rate type is an MCS index.
+ *
+ */
+enum qca_wlan_vendor_data_rate_type {
+	QCA_WLAN_VENDOR_DATA_RATE_TYPE_LEGACY = 0,
+	QCA_WLAN_VENDOR_DATA_RATE_TYPE_MCS = 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_adjust_tx_power_rate - Definition
+ * of data rate related attributes which is used inside nested attribute
+ * %QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_RATE_CONFIG.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_TYPE: u8 data rate type.
+ * For this attribute, valid values are enumerated in enum
+ * %qca_wlan_vendor_data_rate_type.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_VALUE: u8 value.
+ * This attribute value is interpreted according to the value of attribute
+ * %QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_TYPE. For legacy config
+ * type, this attribute value is defined in the units of 0.5 Mbps.
+ * For non legacy config type, this attribute carries the MCS index number.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_POWER_VALUE: u8 value in dBm.
+ * Usually the target computes a final transmit power that is the maximum
+ * power level that doesn't exceed the limits enforced by various sources
+ * like chip-specific conformance test limits (CTL), Specific Absorption
+ * Rate (SAR), Transmit Power Control (TPC), wiphy-specific limits, STA-specific
+ * limits, channel avoidance limits, Automated Frequency Coordination (AFC),
+ * and others. In some cases it may be desirable to use a power level that is
+ * lower than the maximum power level allowed by all of these limits, so this
+ * attribute provides an additional limit that can be used to reduce the
+ * transmit power level.
+ *
+ */
+enum qca_wlan_vendor_attr_adjust_tx_power_rate {
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_TYPE = 1,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_VALUE = 2,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_RATE_POWER_VALUE = 3,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CONFIG_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CONFIG_MAX =
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CONFIG_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_adjust_tx_power_chain_config - Definition
+ * of chain related attributes which is used inside nested attribute
+ * %QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CHAIN_CONFIG.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_INDEX: u8 value.
+ * Represents a particular chain for which transmit power adjustment needed.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_RATE_CONFIG: A nested
+ * attribute containing data rate related information to adjust transmit
+ * power. The attributes used inside this nested attributes are defined in
+ * enum qca_wlan_vendor_attr_adjust_tx_power_rate.
+ */
+enum qca_wlan_vendor_attr_adjust_tx_power_chain_config {
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_INDEX = 1,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_RATE_CONFIG = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_MAX =
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_CHAIN_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_adjust_tx_power_band_config - Definition
+ * of band related attributes which is used inside nested attribute
+ * %QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CONFIG.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_INDEX: u8 value to
+ * indicate band for which configuration applies. Valid values are enumerated
+ * in enum %nl80211_band.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CHAIN_CONFIG: A nested
+ * attribute containing per chain related information to adjust transmit
+ * power. The attributes used inside this nested attribute are defined in
+ * enum qca_wlan_vendor_attr_adjust_tx_power_chain_config.
+ *
+ */
+enum qca_wlan_vendor_attr_adjust_tx_power_band_config {
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_INDEX = 1,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CHAIN_CONFIG = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_MAX =
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_adjust_tx_power - Definition of attributes
+ * for %QCA_NL80211_VENDOR_SUBCMD_ADJUST_TX_POWER subcommand.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CONFIG: A nested attribute
+ * containing per band related information to adjust transmit power.
+ * The attributes used inside this nested attributes are defined in
+ * enum qca_wlan_vendor_attr_adjust_tx_power_band_config.
+ */
+enum qca_wlan_vendor_attr_adjust_tx_power {
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_BAND_CONFIG = 1,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_MAX =
+	QCA_WLAN_VENDOR_ATTR_ADJUST_TX_POWER_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_spectral_data_transport_mode - Attribute
+ * values for QCA_WLAN_VENDOR_ATTR_SPECTRAL_DATA_TRANSPORT_MODE.
+ *
+ * @QCA_WLAN_VENDOR_SPECTRAL_DATA_TRANSPORT_NETLINK: Use netlink to
+ * send spectral data to userspace applications.
+ * @QCA_WLAN_VENDOR_SPECTRAL_DATA_TRANSPORT_RELAY: Use relay interface
+ * to send spectral data to userspace applications.
+ */
+enum qca_wlan_vendor_spectral_data_transport_mode {
+	QCA_WLAN_VENDOR_SPECTRAL_DATA_TRANSPORT_NETLINK = 0,
+	QCA_WLAN_VENDOR_SPECTRAL_DATA_TRANSPORT_RELAY = 1,
+};
+
+/* enum qca_wlan_vendor_spectral_scan_complete_status - Attribute
+ * values for QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_STATUS to
+ * indicate the completion status for a spectral scan.
+ *
+ * @QCA_WLAN_VENDOR_SPECTRAL_SCAN_COMPLETE_STATUS_SUCCESSFUL:
+ * Indicates a successful completion of the scan.
+ *
+ * @QCA_WLAN_VENDOR_SPECTRAL_SCAN_COMPLETE_STATUS_TIMEOUT: Indicates
+ * a timeout has occured while processing the spectral reports.
+ */
+enum qca_wlan_vendor_spectral_scan_complete_status {
+	QCA_WLAN_VENDOR_SPECTRAL_SCAN_COMPLETE_STATUS_SUCCESSFUL = 0,
+	QCA_WLAN_VENDOR_SPECTRAL_SCAN_COMPLETE_STATUS_TIMEOUT = 1,
+};
+
+/* enum qca_wlan_vendor_attr_spectral_scan_complete - Definition of
+ * attributes for @QCA_NL80211_VENDOR_SUBCMD_SPECTRAL_SCAN_COMPLETE
+ * to indicate scan status and samples received from hardware.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_INVALID: Invalid attribute
+ *
+ * @QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_STATUS: u32 attribute.
+ * Indicates completion status, either the scan is successful or a timeout
+ * is issued by the driver.
+ * See enum qca_wlan_vendor_spectral_scan_complete_status.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_RECEIVED_SAMPLES: u32
+ * attribute. Number of spectral samples received after the scan has started.
+ */
+enum qca_wlan_vendor_attr_spectral_scan_complete {
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_STATUS = 1,
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_RECEIVED_SAMPLES = 2,
+
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_MAX =
+	QCA_WLAN_VENDOR_ATTR_SPECTRAL_SCAN_COMPLETE_AFTER_LAST - 1,
+};
+
 #endif /* QCA_VENDOR_H */
diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c
index c82fd0e..6ea3311 100644
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -1104,7 +1104,7 @@
 			link_id = pos[2] & 0x0f;
 			wpa_printf(MSG_DEBUG, "FT: MLO GTK (Link ID %u)",
 				   link_id);
-			if (link_id >= MAX_NUM_MLO_LINKS)
+			if (link_id >= MAX_NUM_MLD_LINKS)
 				break;
 			parse->valid_mlo_gtks |= BIT(link_id);
 			parse->mlo_gtk[link_id] = pos;
@@ -1119,7 +1119,7 @@
 			link_id = pos[2 + 6] & 0x0f;
 			wpa_printf(MSG_DEBUG, "FT: MLO IGTK (Link ID %u)",
 				   link_id);
-			if (link_id >= MAX_NUM_MLO_LINKS)
+			if (link_id >= MAX_NUM_MLD_LINKS)
 				break;
 			parse->valid_mlo_igtks |= BIT(link_id);
 			parse->mlo_igtk[link_id] = pos;
@@ -1134,7 +1134,7 @@
 			link_id = pos[2 + 6] & 0x0f;
 			wpa_printf(MSG_DEBUG, "FT: MLO BIGTK (Link ID %u)",
 				   link_id);
-			if (link_id >= MAX_NUM_MLO_LINKS)
+			if (link_id >= MAX_NUM_MLD_LINKS)
 				break;
 			parse->valid_mlo_bigtks |= BIT(link_id);
 			parse->mlo_bigtk[link_id] = pos;
@@ -1353,7 +1353,7 @@
 
 		/* TODO: This count should be done based on all _requested_,
 		 * not _accepted_ links. */
-		for (link_id = 0; link_id < MAX_NUM_MLO_LINKS; link_id++) {
+		for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
 			if (parse->mlo_gtk[link_id]) {
 				if (parse->rsn)
 					prot_ie_count--;
@@ -3551,7 +3551,7 @@
 	    selector == RSN_KEY_DATA_MLO_GTK) {
 		link_id = (p[0] & RSN_MLO_GTK_KDE_PREFIX0_LINK_ID_MASK) >>
 			RSN_MLO_GTK_KDE_PREFIX0_LINK_ID_SHIFT;
-		if (link_id >= MAX_NUM_MLO_LINKS)
+		if (link_id >= MAX_NUM_MLD_LINKS)
 			return 2;
 
 		ie->valid_mlo_gtks |= BIT(link_id);
@@ -3569,7 +3569,7 @@
 	    selector == RSN_KEY_DATA_MLO_IGTK) {
 		link_id = (p[8] & RSN_MLO_IGTK_KDE_PREFIX8_LINK_ID_MASK) >>
 			  RSN_MLO_IGTK_KDE_PREFIX8_LINK_ID_SHIFT;
-		if (link_id >= MAX_NUM_MLO_LINKS)
+		if (link_id >= MAX_NUM_MLD_LINKS)
 			return 2;
 
 		ie->valid_mlo_igtks |= BIT(link_id);
@@ -3587,7 +3587,7 @@
 	    selector == RSN_KEY_DATA_MLO_BIGTK) {
 		link_id = (p[8] & RSN_MLO_BIGTK_KDE_PREFIX8_LINK_ID_MASK) >>
 			  RSN_MLO_BIGTK_KDE_PREFIX8_LINK_ID_SHIFT;
-		if (link_id >= MAX_NUM_MLO_LINKS)
+		if (link_id >= MAX_NUM_MLD_LINKS)
 			return 2;
 
 		ie->valid_mlo_bigtks |= BIT(link_id);
@@ -3605,7 +3605,7 @@
 	    selector == RSN_KEY_DATA_MLO_LINK) {
 		link_id = (p[0] & RSN_MLO_LINK_KDE_LI_LINK_ID_MASK) >>
 			  RSN_MLO_LINK_KDE_LI_LINK_ID_SHIFT;
-		if (link_id >= MAX_NUM_MLO_LINKS)
+		if (link_id >= MAX_NUM_MLD_LINKS)
 			return 2;
 
 		ie->valid_mlo_links |= BIT(link_id);
diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index a2c7033..01efeea 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -9,6 +9,8 @@
 #ifndef WPA_COMMON_H
 #define WPA_COMMON_H
 
+#include "common/defs.h"
+
 /* IEEE 802.11i */
 #define PMKID_LEN 16
 #define PMK_LEN 32
@@ -557,8 +559,6 @@
 		       const u8 *ie2, size_t ie2len);
 int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid, bool replace);
 
-#define MAX_NUM_MLO_LINKS 15
-
 struct wpa_ft_ies {
 	const u8 *mdie;
 	size_t mdie_len;
@@ -596,14 +596,14 @@
 	const u8 *rsnxe;
 	size_t rsnxe_len;
 	u16 valid_mlo_gtks; /* bitmap of valid link GTK subelements */
-	const u8 *mlo_gtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_gtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_gtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_gtk_len[MAX_NUM_MLD_LINKS];
 	u16 valid_mlo_igtks; /* bitmap of valid link IGTK subelements */
-	const u8 *mlo_igtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_igtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_igtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_igtk_len[MAX_NUM_MLD_LINKS];
 	u16 valid_mlo_bigtks; /* bitmap of valid link BIGTK subelements */
-	const u8 *mlo_bigtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_bigtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_bigtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_bigtk_len[MAX_NUM_MLD_LINKS];
 
 	struct wpabuf *fte_buf;
 };
@@ -700,17 +700,17 @@
 	const u8 *wmm;
 	size_t wmm_len;
 	u16 valid_mlo_gtks; /* bitmap of valid link GTK KDEs */
-	const u8 *mlo_gtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_gtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_gtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_gtk_len[MAX_NUM_MLD_LINKS];
 	u16 valid_mlo_igtks; /* bitmap of valid link IGTK KDEs */
-	const u8 *mlo_igtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_igtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_igtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_igtk_len[MAX_NUM_MLD_LINKS];
 	u16 valid_mlo_bigtks; /* bitmap of valid link BIGTK KDEs */
-	const u8 *mlo_bigtk[MAX_NUM_MLO_LINKS];
-	size_t mlo_bigtk_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_bigtk[MAX_NUM_MLD_LINKS];
+	size_t mlo_bigtk_len[MAX_NUM_MLD_LINKS];
 	u16 valid_mlo_links; /* bitmap of valid MLO link KDEs */
-	const u8 *mlo_link[MAX_NUM_MLO_LINKS];
-	size_t mlo_link_len[MAX_NUM_MLO_LINKS];
+	const u8 *mlo_link[MAX_NUM_MLD_LINKS];
+	size_t mlo_link_len[MAX_NUM_MLD_LINKS];
 };
 
 int wpa_parse_kde_ies(const u8 *buf, size_t len, struct wpa_eapol_ie_parse *ie);
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index c5bb9ab..f614250 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -413,6 +413,9 @@
 /* parameters: <STA address> <dialog token> <report mode> <beacon report> */
 #define BEACON_RESP_RX "BEACON-RESP-RX "
 
+/* parameters: <STA address> <dialog token> <link measurement report> */
+#define LINK_MSR_RESP_RX "LINK-MSR-RESP-RX "
+
 /* PMKSA cache entry added; parameters: <BSSID> <network_id> */
 #define PMKSA_CACHE_ADDED "PMKSA-CACHE-ADDED "
 /* PMKSA cache entry removed; parameters: <BSSID> <network_id> */
diff --git a/src/crypto/crypto_openssl.c b/src/crypto/crypto_openssl.c
index 427677d..2d8ff60 100644
--- a/src/crypto/crypto_openssl.c
+++ b/src/crypto/crypto_openssl.c
@@ -1835,6 +1835,7 @@
 	ret = 0;
 fail:
 	EVP_MAC_CTX_free(ctx);
+	EVP_MAC_free(emac);
 	return ret;
 #else /* OpenSSL version >= 3.0 */
 	CMAC_CTX *ctx;
@@ -3932,9 +3933,10 @@
 	group = EC_GROUP_new_by_curve_name(nid);
 	prime = BN_new();
 	if (!group || !prime)
-		return -1;
+		goto fail;
 	if (EC_GROUP_get_curve(group, prime, NULL, NULL, NULL) == 1)
 		prime_len = BN_num_bytes(prime);
+fail:
 	EC_GROUP_free(group);
 	BN_free(prime);
 	return prime_len;
@@ -4880,7 +4882,7 @@
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
 	hmac = EVP_MAC_fetch(NULL, "HMAC", NULL);
 	if (!hmac)
-		return -1;
+		goto fail;
 
 	params[0] = OSSL_PARAM_construct_utf8_string(
 		"digest",
@@ -4889,7 +4891,7 @@
 #else /* OpenSSL version >= 3.0 */
 	hctx = HMAC_CTX_new();
 	if (!hctx)
-		return -1;
+		goto fail;
 #endif /* OpenSSL version >= 3.0 */
 
 	while (left > 0) {
@@ -4898,7 +4900,7 @@
 		EVP_MAC_CTX_free(hctx);
 		hctx = EVP_MAC_CTX_new(hmac);
 		if (!hctx)
-			return -1;
+			goto fail;
 
 		if (EVP_MAC_init(hctx, prk, mdlen, params) != 1)
 			goto fail;
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index a55e8e3..069e741 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -43,6 +43,7 @@
 
 #define HOSTAPD_CHAN_VHT_80MHZ_SUBCHANNEL 0x00000800
 #define HOSTAPD_CHAN_VHT_160MHZ_SUBCHANNEL 0x00001000
+#define HOSTAPD_CHAN_EHT_320MHZ_SUBCHANNEL 0x00002000
 
 #define HOSTAPD_CHAN_INDOOR_ONLY 0x00010000
 #define HOSTAPD_CHAN_GO_CONCURRENT 0x00020000
@@ -247,6 +248,11 @@
 	enum hostapd_hw_mode mode;
 
 	/**
+	 * is_6ghz - Whether the mode information is for the 6 GHz band
+	 */
+	bool is_6ghz;
+
+	/**
 	 * num_channels - Number of entries in the channels array
 	 */
 	int num_channels;
@@ -695,6 +701,14 @@
 	 */
 	unsigned int min_probe_req_content:1;
 
+	/**
+	 * link_id - Specify the link that is requesting the scan on an MLD
+	 *
+	 * This is set when operating as an AP MLD and doing an OBSS scan.
+	 * -1 indicates that no particular link ID is set.
+	 */
+	s8 link_id;
+
 	/*
 	 * NOTE: Whenever adding new parameters here, please make sure
 	 * wpa_scan_clone_params() and wpa_scan_free_params() get updated with
@@ -3371,6 +3385,17 @@
 			     size_t ies_len);
 
 	/**
+	 * get_scan_results - Fetch the latest scan results
+	 * @priv: Private driver interface data
+	 * @bssid: Return results only for the specified BSSID, %NULL for all
+	 *
+	 * Returns: Allocated buffer of scan results (caller is responsible for
+	 * freeing the data structure) on success, NULL on failure
+	 */
+	struct wpa_scan_results * (*get_scan_results)(void *priv,
+						      const u8 *bssid);
+
+	/**
 	 * get_scan_results2 - Fetch the latest scan results
 	 * @priv: private driver interface data
 	 *
@@ -4554,13 +4579,14 @@
 	/**
 	 * stop_ap - Removes beacon from AP
 	 * @priv: Private driver interface data
+	 * @link_id: Link ID of the specified link; -1 for non-MLD
 	 * Returns: 0 on success, -1 on failure (or if not supported)
 	 *
 	 * This optional function can be used to disable AP mode related
 	 * configuration. Unlike deinit_ap, it does not change to station
 	 * mode.
 	 */
-	int (*stop_ap)(void *priv);
+	int (*stop_ap)(void *priv, int link_id);
 
 	/**
 	 * get_survey - Retrieve survey data
@@ -5140,9 +5166,44 @@
 	 * @priv: Private driver interface data
 	 * @link_id: The link ID
 	 * @addr: The MAC address to use for the link
+	 * @bss_ctx: BSS context for %WPA_IF_AP_BSS interfaces
 	 * Returns: 0 on success, negative value on failure
 	 */
-	int (*link_add)(void *priv, u8 link_id, const u8 *addr);
+	int (*link_add)(void *priv, u8 link_id, const u8 *addr, void *bss_ctx);
+
+	/**
+	 * link_remove - Remove a link from the AP MLD interface
+	 * @priv: Private driver interface data
+	 * @type: Interface type
+	 * @ifname: Interface name of the virtual interface from where the link
+	 *	is to be removed.
+	 * @link_id: Valid link ID to remove
+	 * Returns: 0 on success, -1 on failure
+	 */
+	int (*link_remove)(void *priv, enum wpa_driver_if_type type,
+			   const char *ifname, u8 link_id);
+
+	/**
+	 * is_drv_shared - Check whether the driver interface is shared
+	 * @priv: Private driver interface data from init()
+	 * @bss_ctx: BSS context for %WPA_IF_AP_BSS interfaces
+	 *
+	 * Checks whether the driver interface is being used by other partner
+	 * BSS(s) or not. This is used to decide whether the driver interface
+	 * needs to be deinitilized when one interface is getting deinitialized.
+	 *
+	 * Returns: true if it is being used or else false.
+	 */
+	bool (*is_drv_shared)(void *priv, void *bss_ctx);
+
+	/**
+	 * link_sta_remove - Remove a link STA from an MLD STA
+	 * @priv: Private driver interface data
+	 * @link_id: The link ID which the link STA is using
+	 * @addr: The MLD MAC address of the MLD STA
+	 * Returns: 0 on success, negative value on failure
+	 */
+	int (*link_sta_remove)(void *priv, u8 link_id, const u8 *addr);
 
 #ifdef CONFIG_TESTING_OPTIONS
 	int (*register_frame)(void *priv, u16 type,
@@ -6352,6 +6413,8 @@
 	 *	(if available).
 	 * @scan_start_tsf_bssid: The BSSID according to which %scan_start_tsf
 	 *	is set.
+	 * @scan_cookie: Unique identification representing the corresponding
+	 *      scan request. 0 if no unique identification is available.
 	 */
 	struct scan_info {
 		int aborted;
@@ -6363,6 +6426,7 @@
 		int nl_scan_event;
 		u64 scan_start_tsf;
 		u8 scan_start_tsf_bssid[ETH_ALEN];
+		u64 scan_cookie;
 	} scan_info;
 
 	/**
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index 9b50b6f..194a5cd 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -18,9 +18,6 @@
 #include <netlink/genl/genl.h>
 #include <netlink/genl/ctrl.h>
 #include <netlink/genl/family.h>
-#ifdef CONFIG_LIBNL3_ROUTE
-#include <netlink/route/neighbour.h>
-#endif /* CONFIG_LIBNL3_ROUTE */
 #include <linux/rtnetlink.h>
 #include <netpacket/packet.h>
 #include <linux/errqueue.h>
@@ -2295,7 +2292,6 @@
 {
 	struct wpa_driver_nl80211_data *drv;
 	struct i802_bss *bss;
-	unsigned int i;
 	char path[128], buf[200], *pos;
 	ssize_t len;
 	int ret;
@@ -2395,16 +2391,12 @@
 	}
 
 	/*
-	 * Set the default link to be the first one, and set its address to that
-	 * of the interface.
+	 * Use link ID 0 for the single "link" of a non-MLD.
 	 */
+	bss->valid_links = 0;
 	bss->flink = &bss->links[0];
-	bss->n_links = 1;
 	os_memcpy(bss->flink->addr, bss->addr, ETH_ALEN);
 
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++)
-		bss->links[i].link_id = NL80211_DRV_LINK_ID_NA;
-
 	return bss;
 
 failed:
@@ -3079,31 +3071,31 @@
 
 
 static int wpa_driver_nl80211_del_beacon(struct i802_bss *bss,
-					 struct i802_link *link)
+					 int link_id)
 {
 	struct nl_msg *msg;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct i802_link *link = nl80211_get_link(bss, link_id);
 
 	if (!link->beacon_set)
 		return 0;
 
 	wpa_printf(MSG_DEBUG, "nl80211: Remove beacon (ifindex=%d)",
-		   drv->ifindex);
+		   bss->ifindex);
 	link->beacon_set = 0;
 	link->freq = 0;
 
 	nl80211_put_wiphy_data_ap(bss);
-	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_DEL_BEACON);
+	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_DEL_BEACON);
 	if (!msg)
 		return -ENOBUFS;
 
-	if (link->link_id != NL80211_DRV_LINK_ID_NA) {
+	if (link_id != NL80211_DRV_LINK_ID_NA) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: MLD: stop beaconing on link=%u",
-			   link->link_id);
+			   link_id);
 
-		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID,
-			       link->link_id)) {
+		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) {
 			nlmsg_free(msg);
 			return -ENOBUFS;
 		}
@@ -3115,10 +3107,10 @@
 
 static void wpa_driver_nl80211_del_beacon_all(struct i802_bss *bss)
 {
-	unsigned int i;
+	int link_id;
 
-	for (i = 0; i < bss->n_links; i++)
-		wpa_driver_nl80211_del_beacon(bss, &bss->links[i]);
+	for_each_link_default(bss->valid_links, link_id, NL80211_DRV_LINK_ID_NA)
+		wpa_driver_nl80211_del_beacon(bss, link_id);
 }
 
 
@@ -4258,14 +4250,11 @@
 
 struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id)
 {
-	unsigned int i;
+	if (link_id < 0 || link_id >= MAX_NUM_MLD_LINKS)
+		return bss->flink;
 
-	for (i = 0; i < bss->n_links; i++) {
-		if (bss->links[i].link_id != link_id)
-			continue;
-
-		return &bss->links[i];
-	}
+	if (BIT(link_id) & bss->valid_links)
+		return &bss->links[link_id];
 
 	return bss->flink;
 }
@@ -4282,9 +4271,9 @@
 static int nl80211_get_link_freq(struct i802_bss *bss, const u8 *addr,
 				 bool bss_freq_debug)
 {
-	size_t i;
+	u8 i;
 
-	for (i = 0; i < bss->n_links; i++) {
+	for_each_link(bss->valid_links, i) {
 		if (ether_addr_equal(bss->links[i].addr, addr)) {
 			wpa_printf(MSG_DEBUG,
 				   "nl80211: Use link freq=%d for address "
@@ -5125,20 +5114,18 @@
 #endif /* CONFIG_MESH */
 
 	if (params->mld_ap) {
-		size_t i;
-
-		for (i = 0; i < bss->n_links; i++) {
-			if (bss->links[i].link_id == params->mld_link_id) {
-				link = &bss->links[i];
-				break;
-			}
-		}
-
-		if (i == bss->n_links) {
-			wpa_printf(MSG_DEBUG, "nl80211: Link ID=%u not found",
-				   params->mld_link_id);
+		if (!nl80211_link_valid(bss->valid_links,
+					params->mld_link_id)) {
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Link ID=%u invalid (valid: 0x%04x)",
+				   params->mld_link_id, bss->valid_links);
 			return -EINVAL;
 		}
+
+		link = nl80211_get_link(bss, params->mld_link_id);
+	} else if (bss->valid_links) {
+		wpa_printf(MSG_DEBUG, "nl80211: MLD configuration expected");
+		return -EINVAL;
 	}
 
 	beacon_set = params->reenable ? 0 : link->beacon_set;
@@ -5179,13 +5166,12 @@
 			   params->mld_link_id);
 
 		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID,
-			       params->mld_link_id) ||
-		    (params->freq &&
-		     nl80211_put_freq_params(msg, params->freq) < 0))
+			       params->mld_link_id))
 			goto fail;
 
-		nl80211_link_set_freq(bss, params->mld_link_id,
-				      params->freq->freq);
+		if (params->freq)
+			nl80211_link_set_freq(bss, params->mld_link_id,
+					      params->freq->freq);
 	}
 
 	if (params->proberesp && params->proberesp_len) {
@@ -5393,6 +5379,9 @@
 		nla_nest_end(msg, ftm);
 	}
 
+	if (params->freq && nl80211_put_freq_params(msg, params->freq) < 0)
+		goto fail;
+
 #ifdef CONFIG_IEEE80211AX
 	if (params->he_spr_ctrl) {
 		struct nlattr *spr;
@@ -5427,9 +5416,6 @@
 		nla_nest_end(msg, spr);
 	}
 
-	if (params->freq && nl80211_put_freq_params(msg, params->freq) < 0)
-		goto fail;
-
 	if (params->freq && params->freq->he_enabled &&
 	    nl80211_attr_supported(drv, NL80211_ATTR_HE_BSS_COLOR)) {
 		struct nlattr *bss_color;
@@ -5548,27 +5534,6 @@
 }
 
 
-static bool nl80211_link_valid(struct i802_bss *bss, s8 link_id)
-{
-	unsigned int i;
-
-	if (link_id < 0)
-		return false;
-
-	for (i = 0; i < bss->n_links; i++) {
-		wpa_printf(MSG_DEBUG, "nl80211: %s - i=%u, link_id=%u",
-			   __func__, i, bss->links[i].link_id);
-		if (bss->links[i].link_id == NL80211_DRV_LINK_ID_NA)
-			continue;
-
-		if (bss->links[i].link_id == link_id)
-			return true;
-	}
-
-	return false;
-}
-
-
 static int nl80211_set_channel(struct i802_bss *bss,
 			       struct hostapd_freq_params *freq, int set_chan)
 {
@@ -5589,7 +5554,7 @@
 		return -1;
 	}
 
-	if (nl80211_link_valid(bss, freq->link_id)) {
+	if (nl80211_link_valid(bss->valid_links, freq->link_id)) {
 		wpa_printf(MSG_DEBUG, "nl80211: Set link_id=%u for freq",
 			   freq->link_id);
 
@@ -5955,26 +5920,25 @@
 
 static void rtnl_neigh_delete_fdb_entry(struct i802_bss *bss, const u8 *addr)
 {
-#ifdef CONFIG_LIBNL3_ROUTE
 	struct wpa_driver_nl80211_data *drv = bss->drv;
-	struct rtnl_neigh *rn;
-	struct nl_addr *nl_addr;
+	struct ndmsg nhdr = {
+		.ndm_state = NUD_PERMANENT,
+		.ndm_ifindex = bss->ifindex,
+		.ndm_family = AF_BRIDGE,
+	};
+	struct nl_msg *msg;
 	int err;
 
-	rn = rtnl_neigh_alloc();
-	if (!rn)
+	msg = nlmsg_alloc_simple(RTM_DELNEIGH, NLM_F_CREATE);
+	if (!msg)
 		return;
 
-	rtnl_neigh_set_family(rn, AF_BRIDGE);
-	rtnl_neigh_set_ifindex(rn, bss->ifindex);
-	nl_addr = nl_addr_build(AF_BRIDGE, (void *) addr, ETH_ALEN);
-	if (!nl_addr) {
-		rtnl_neigh_put(rn);
-		return;
-	}
-	rtnl_neigh_set_lladdr(rn, nl_addr);
+	if (nlmsg_append(msg, &nhdr, sizeof(nhdr), NLMSG_ALIGNTO) < 0 ||
+	    nla_put(msg, NDA_LLADDR, ETH_ALEN, (void *) addr) ||
+	    nl_send_auto_complete(drv->rtnl_sk, msg) < 0)
+		goto errout;
 
-	err = rtnl_neigh_delete(drv->rtnl_sk, rn, 0);
+	err = nl_wait_for_ack(drv->rtnl_sk);
 	if (err < 0) {
 		wpa_printf(MSG_DEBUG, "nl80211: bridge FDB entry delete for "
 			   MACSTR " ifindex=%d failed: %s", MAC2STR(addr),
@@ -5984,9 +5948,8 @@
 			   MACSTR, MAC2STR(addr));
 	}
 
-	nl_addr_put(nl_addr);
-	rtnl_neigh_put(rn);
-#endif /* CONFIG_LIBNL3_ROUTE */
+errout:
+	nlmsg_free(msg);
 }
 
 
@@ -6783,7 +6746,6 @@
 	if (params->mld_params.mld_addr && params->mld_params.valid_links > 0) {
 		struct wpa_driver_mld_params *mld_params = &params->mld_params;
 		struct nlattr *links, *attr;
-		int i;
 		u8 link_id;
 
 		wpa_printf(MSG_DEBUG, "  * MLD: MLD addr=" MACSTR,
@@ -6799,31 +6761,8 @@
 		if (!links)
 			return -1;
 
-		attr = nla_nest_start(msg, 0);
-		if (!attr)
-			return -1;
-
-		/* First add the association link ID */
-		link_id = mld_params->assoc_link_id;
-		if (nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id) ||
-		    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN,
-			    mld_params->mld_links[link_id].bssid) ||
-		    nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
-				mld_params->mld_links[link_id].freq))
-			return -1;
-
-		os_memcpy(drv->sta_mlo_info.links[link_id].bssid,
-			  mld_params->mld_links[link_id].bssid, ETH_ALEN);
-
-		nla_nest_end(msg, attr);
-
-		for (i = 1, link_id = 0; link_id < MAX_NUM_MLD_LINKS;
-		     link_id++) {
-			if (!(mld_params->valid_links & BIT(link_id)) ||
-			    link_id == mld_params->assoc_link_id)
-				continue;
-
-			attr = nla_nest_start(msg, i);
+		for_each_link(mld_params->valid_links, link_id) {
+			attr = nla_nest_start(msg, 0);
 			if (!attr)
 				return -1;
 
@@ -6833,11 +6772,11 @@
 				    mld_params->mld_links[link_id].bssid) ||
 			    nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
 					mld_params->mld_links[link_id].freq) ||
-			    (mld_params->mld_links[i].disabled &&
+			    (mld_params->mld_links[link_id].disabled &&
 			     nla_put_flag(msg,
 					  NL80211_ATTR_MLO_LINK_DISABLED)) ||
 			    (mld_params->mld_links[link_id].ies &&
-			     mld_params->mld_links[i].ies_len &&
+			     mld_params->mld_links[link_id].ies_len &&
 			     nla_put(msg, NL80211_ATTR_IE,
 				     mld_params->mld_links[link_id].ies_len,
 				     mld_params->mld_links[link_id].ies)))
@@ -6847,7 +6786,6 @@
 				  mld_params->mld_links[link_id].bssid,
 				  ETH_ALEN);
 			nla_nest_end(msg, attr);
-			i++;
 		}
 
 		nla_nest_end(msg, links);
@@ -7424,10 +7362,7 @@
 
 		/* Error and force TEST_FAIL checking for each link */
 		ret = -EINVAL;
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-			if (!(params->mld_params.valid_links & BIT(i)))
-				continue;
-
+		for_each_link(params->mld_params.valid_links, i) {
 			if (TEST_FAIL_TAG("link"))
 				err_info.link_id = i;
 		}
@@ -8762,7 +8697,6 @@
 	    (params->num_bridge == 0 || !params->bridge[0]))
 		add_ifidx(drv, br_ifindex, drv->ifindex);
 
-#ifdef CONFIG_LIBNL3_ROUTE
 	if (bss->added_if_into_bridge || bss->already_in_bridge) {
 		int err;
 
@@ -8779,7 +8713,6 @@
 			goto failed;
 		}
 	}
-#endif /* CONFIG_LIBNL3_ROUTE */
 
 	if (drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) {
 		wpa_printf(MSG_DEBUG,
@@ -9004,7 +8937,6 @@
 
 	if (type == WPA_IF_AP_BSS && setup_ap) {
 		struct i802_bss *new_bss = os_zalloc(sizeof(*new_bss));
-		unsigned int i;
 
 		if (new_bss == NULL) {
 			if (added)
@@ -9012,10 +8944,6 @@
 			return -1;
 		}
 
-		/* Initialize here before any failure path */
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++)
-			new_bss->links[i].link_id = NL80211_DRV_LINK_ID_NA;
-
 		if (bridge &&
 		    i802_check_bridge(drv, new_bss, bridge, ifname) < 0) {
 			wpa_printf(MSG_ERROR, "nl80211: Failed to add the new "
@@ -9040,7 +8968,7 @@
 		new_bss->drv = drv;
 		new_bss->next = drv->first_bss->next;
 		new_bss->flink = &new_bss->links[0];
-		new_bss->n_links = 1;
+		new_bss->valid_links = 0;
 		os_memcpy(new_bss->flink->addr, new_bss->addr, ETH_ALEN);
 
 		new_bss->flink->freq = drv->first_bss->flink->freq;
@@ -9120,6 +9048,7 @@
 				tbss->next = bss->next;
 				/* Unsubscribe management frames */
 				nl80211_teardown_ap(bss);
+				nl80211_remove_links(bss);
 				nl80211_destroy_bss(bss);
 				if (!bss->added_if)
 					i802_set_iface_flags(bss, 0);
@@ -9134,6 +9063,7 @@
 	} else {
 		wpa_printf(MSG_DEBUG, "nl80211: First BSS - reassign context");
 		nl80211_teardown_ap(bss);
+		nl80211_remove_links(bss);
 		if (!bss->added_if && !drv->first_bss->next)
 			wpa_driver_nl80211_del_beacon_all(bss);
 		nl80211_destroy_bss(bss);
@@ -9142,6 +9072,7 @@
 		if (drv->first_bss->next) {
 			drv->first_bss = drv->first_bss->next;
 			drv->ctx = drv->first_bss->ctx;
+			drv->ifindex = drv->first_bss->ifindex;
 			os_free(bss);
 		} else {
 			wpa_printf(MSG_DEBUG, "nl80211: No second BSS to reassign context to");
@@ -9382,10 +9313,7 @@
 		return 0;
 
 	/* First try to pick a link that uses the same band */
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!(mlo->valid_links & BIT(i)))
-			continue;
-
+	for_each_link(mlo->valid_links, i) {
 		if (any_valid_link_id == -1)
 			any_valid_link_id = i;
 
@@ -9580,59 +9508,81 @@
 }
 
 
-static void nl80211_remove_links(struct i802_bss *bss)
+static int nl80211_remove_link(struct i802_bss *bss, int link_id)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct i802_link *link;
 	struct nl_msg *msg;
+	size_t i;
+	int ret;
+
+	wpa_printf(MSG_DEBUG, "nl80211: Remove link (ifindex=%d link_id=%u)",
+		   bss->ifindex, link_id);
+
+	if (!(bss->valid_links & BIT(link_id))) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: MLD: remove link: Link not found");
+		return -1;
+	}
+
+	link = &bss->links[link_id];
+
+	wpa_driver_nl80211_del_beacon(bss, link_id);
+
+	/* First remove the link locally */
+	bss->valid_links &= ~BIT(link_id);
+	os_memset(link->addr, 0, ETH_ALEN);
+
+	/* Choose new deflink if we are removing that link */
+	if (bss->flink == link) {
+		for_each_link_default(bss->valid_links, i, 0) {
+			bss->flink = &bss->links[i];
+			break;
+		}
+	}
+
+	/* If this was the last link, reset default link */
+	if (!bss->valid_links) {
+		/* TODO: Does keeping freq/bandwidth make sense? */
+		if (bss->flink != link)
+			os_memcpy(bss->flink, link, sizeof(*link));
+
+		os_memcpy(bss->flink->addr, bss->addr, ETH_ALEN);
+	}
+
+	/* Remove the link from the kernel */
+	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_REMOVE_LINK);
+	if (!msg ||
+	    nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) {
+		nlmsg_free(msg);
+		wpa_printf(MSG_ERROR,
+			   "nl80211: remove link (%d) failed", link_id);
+		return -1;
+	}
+
+	ret = send_and_recv_cmd(drv, msg);
+	if (ret)
+		wpa_printf(MSG_ERROR,
+			   "nl80211: remove link (%d) failed. ret=%d (%s)",
+			   link_id, ret, strerror(-ret));
+
+	return ret;
+}
+
+
+static void nl80211_remove_links(struct i802_bss *bss)
+{
 	int ret;
 	u8 link_id;
 
-	if (bss->n_links == 0)
-		return;
-
-	while (bss->links[0].link_id != NL80211_DRV_LINK_ID_NA) {
-		struct i802_link *link = &bss->links[0];
-
-		wpa_printf(MSG_DEBUG, "nl80211: MLD: remove link_id=%u",
-			   link->link_id);
-
-		wpa_driver_nl80211_del_beacon(bss, link);
-
-		link_id = link->link_id;
-
-		/* First remove the link locally */
-		if (bss->n_links == 1) {
-			bss->flink->link_id = NL80211_DRV_LINK_ID_NA;
-			os_memcpy(bss->flink->addr, bss->addr, ETH_ALEN);
-		} else {
-			struct i802_link *other = &bss->links[bss->n_links - 1];
-
-			os_memcpy(link, other, sizeof(*link));
-			other->link_id = NL80211_DRV_LINK_ID_NA;
-			os_memset(other->addr, 0, ETH_ALEN);
-
-			bss->n_links--;
-		}
-
-		/* Remove the link from the kernel */
-		msg = nl80211_drv_msg(drv, 0, NL80211_CMD_REMOVE_LINK);
-		if (!msg ||
-		    nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) {
-			nlmsg_free(msg);
-			wpa_printf(MSG_ERROR,
-				   "nl80211: remove link (%d) failed",
-				   link_id);
-			return;
-		}
-
-		ret = send_and_recv_cmd(drv, msg);
-		if (ret) {
-			wpa_printf(MSG_ERROR,
-				   "nl80211: remove link (%d) failed. ret=%d (%s)",
-				   link_id, ret, strerror(-ret));
-			return;
-		}
+	for_each_link(bss->valid_links, link_id) {
+		ret = nl80211_remove_link(bss, link_id);
+		if (ret)
+			break;
 	}
+
+	if (bss->flink)
+		os_memcpy(bss->flink->addr, bss->addr, ETH_ALEN);
 }
 
 
@@ -9645,7 +9595,7 @@
 		return -1;
 
 	/* Stop beaconing */
-	wpa_driver_nl80211_del_beacon(bss, bss->flink);
+	wpa_driver_nl80211_del_beacon(bss, NL80211_DRV_LINK_ID_NA);
 
 	nl80211_remove_links(bss);
 
@@ -9660,7 +9610,7 @@
 }
 
 
-static int wpa_driver_nl80211_stop_ap(void *priv)
+static int wpa_driver_nl80211_stop_ap(void *priv, int link_id)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
@@ -9668,9 +9618,17 @@
 	if (!is_ap_interface(drv->nlmode))
 		return -1;
 
-	wpa_driver_nl80211_del_beacon_all(bss);
+	if (link_id == -1) {
+		wpa_driver_nl80211_del_beacon_all(bss);
+		return 0;
+	}
 
-	return 0;
+	if (nl80211_link_valid(bss->valid_links, link_id)) {
+		wpa_driver_nl80211_del_beacon(bss, link_id);
+		return 0;
+	}
+
+	return -1;
 }
 
 
@@ -9823,10 +9781,7 @@
 	if (!sinfo[NL80211_SURVEY_INFO_NOISE])
 		return NL_SKIP;
 
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!(mlo_sig->valid_links & BIT(i)))
-			continue;
-
+	for_each_link(mlo_sig->valid_links, i) {
 		if (nla_get_u32(sinfo[NL80211_SURVEY_INFO_FREQUENCY]) !=
 		    mlo_sig->links[i].frequency)
 			continue;
@@ -9919,10 +9874,7 @@
 	os_memset(mlo_si, 0, sizeof(*mlo_si));
 	mlo_si->valid_links = drv->sta_mlo_info.valid_links;
 
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!(mlo_si->valid_links & BIT(i)))
-			continue;
-
+	for_each_link(mlo_si->valid_links, i) {
 		res = nl80211_get_link_signal(drv,
 					      drv->sta_mlo_info.links[i].bssid,
 					      &mlo_si->links[i].data);
@@ -10846,6 +10798,73 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+
+static int driver_nl80211_link_remove(void *priv, enum wpa_driver_if_type type,
+				      const char *ifname, u8 link_id)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+
+	if (type != WPA_IF_AP_BSS ||
+	    !nl80211_link_valid(bss->valid_links, link_id))
+		return -1;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Teardown AP(%s) link %d (type=%d ifname=%s links=0x%x)",
+		   bss->ifname, link_id, type, ifname, bss->valid_links);
+
+	nl80211_remove_link(bss, link_id);
+
+	bss->ctx = bss->flink->ctx;
+
+	if (drv->first_bss == bss && !bss->valid_links)
+		drv->ctx = bss->ctx;
+
+	if (!bss->valid_links) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: No more links remaining, so remove interface");
+		return wpa_driver_nl80211_if_remove(bss, type, ifname);
+	}
+
+	return 0;
+}
+
+
+static bool nl80211_is_drv_shared(void *priv, void *bss_ctx)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	unsigned int num_bss = 0;
+
+	/* If any other BSS exist, someone else is using this since at this
+	 * time, we would have removed all BSSs created by this driver and only
+	 * this BSS should be remaining if the driver is not shared by anyone.
+	 */
+	for (bss = drv->first_bss; bss; bss = bss->next) {
+		num_bss++;
+		if (num_bss > 1)
+			return true;
+	}
+
+	/* This is the only BSS present */
+	bss = priv;
+
+	/* If only one/no link is there no one is sharing */
+	if (bss->valid_links <= 1)
+		return false;
+
+	/* More than one link means someone is still using. To check if
+	 * only 1 bit is set, power of 2 condition can be checked. */
+	if (!(bss->valid_links & (bss->valid_links - 1)))
+		return false;
+
+	return true;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
 static int driver_nl80211_send_mlme(void *priv, const u8 *data,
 				    size_t data_len, int noack,
 				    unsigned int freq,
@@ -11114,10 +11133,7 @@
 			return pos - buf;
 		pos += res;
 
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-			if (!(mlo->valid_links & BIT(i)))
-				continue;
-
+		for_each_link(mlo->valid_links, i) {
 			res = os_snprintf(pos, end - pos,
 					  "link_addr[%u]=" MACSTR "\n"
 					  "link_bssid[%u]=" MACSTR "\n"
@@ -12248,13 +12264,14 @@
 				      const u8 *ipaddr, int prefixlen,
 				      const u8 *addr)
 {
-#ifdef CONFIG_LIBNL3_ROUTE
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
-	struct rtnl_neigh *rn;
-	struct nl_addr *nl_ipaddr = NULL;
-	struct nl_addr *nl_lladdr = NULL;
-	int family, addrsize;
+	struct ndmsg nhdr = {
+		.ndm_state = NUD_PERMANENT,
+		.ndm_ifindex = bss->br_ifindex,
+	};
+	struct nl_msg *msg;
+	int addrsize;
 	int res;
 
 	if (!ipaddr || prefixlen == 0 || !addr)
@@ -12273,85 +12290,62 @@
 	}
 
 	if (version == 4) {
-		family = AF_INET;
+		nhdr.ndm_family = AF_INET;
 		addrsize = 4;
 	} else if (version == 6) {
-		family = AF_INET6;
+		nhdr.ndm_family = AF_INET6;
 		addrsize = 16;
 	} else {
 		return -EINVAL;
 	}
 
-	rn = rtnl_neigh_alloc();
-	if (rn == NULL)
+	msg = nlmsg_alloc_simple(RTM_NEWNEIGH, NLM_F_CREATE);
+	if (!msg)
 		return -ENOMEM;
 
-	/* set the destination ip address for neigh */
-	nl_ipaddr = nl_addr_build(family, (void *) ipaddr, addrsize);
-	if (nl_ipaddr == NULL) {
-		wpa_printf(MSG_DEBUG, "nl80211: nl_ipaddr build failed");
-		res = -ENOMEM;
+	res = -ENOMEM;
+	if (nlmsg_append(msg, &nhdr, sizeof(nhdr), NLMSG_ALIGNTO) < 0 ||
+	    nla_put(msg, NDA_DST, addrsize, (void *) ipaddr) ||
+	    nla_put(msg, NDA_LLADDR, ETH_ALEN, (void *) addr))
 		goto errout;
-	}
-	nl_addr_set_prefixlen(nl_ipaddr, prefixlen);
-	res = rtnl_neigh_set_dst(rn, nl_ipaddr);
-	if (res) {
-		wpa_printf(MSG_DEBUG,
-			   "nl80211: neigh set destination addr failed");
+
+	res = nl_send_auto_complete(drv->rtnl_sk, msg);
+	if (res < 0)
 		goto errout;
-	}
 
-	/* set the corresponding lladdr for neigh */
-	nl_lladdr = nl_addr_build(AF_BRIDGE, (u8 *) addr, ETH_ALEN);
-	if (nl_lladdr == NULL) {
-		wpa_printf(MSG_DEBUG, "nl80211: neigh set lladdr failed");
-		res = -ENOMEM;
-		goto errout;
-	}
-	rtnl_neigh_set_lladdr(rn, nl_lladdr);
-
-	rtnl_neigh_set_ifindex(rn, bss->br_ifindex);
-	rtnl_neigh_set_state(rn, NUD_PERMANENT);
-
-	res = rtnl_neigh_add(drv->rtnl_sk, rn, NLM_F_CREATE);
+	res = nl_wait_for_ack(drv->rtnl_sk);
 	if (res) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Adding bridge ip neigh failed: %s",
 			   nl_geterror(res));
 	}
 errout:
-	if (nl_lladdr)
-		nl_addr_put(nl_lladdr);
-	if (nl_ipaddr)
-		nl_addr_put(nl_ipaddr);
-	if (rn)
-		rtnl_neigh_put(rn);
+	nlmsg_free(msg);
 	return res;
-#else /* CONFIG_LIBNL3_ROUTE */
-	return -1;
-#endif /* CONFIG_LIBNL3_ROUTE */
 }
 
 
 static int wpa_driver_br_delete_ip_neigh(void *priv, u8 version,
 					 const u8 *ipaddr)
 {
-#ifdef CONFIG_LIBNL3_ROUTE
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
-	struct rtnl_neigh *rn;
-	struct nl_addr *nl_ipaddr;
-	int family, addrsize;
+	struct ndmsg nhdr = {
+		.ndm_state = NUD_PERMANENT,
+		.ndm_ifindex = bss->br_ifindex,
+	};
+	struct nl_msg *msg;
+	int addrsize;
 	int res;
 
 	if (!ipaddr)
 		return -EINVAL;
 
 	if (version == 4) {
-		family = AF_INET;
+		nhdr.ndm_family = AF_INET;
 		addrsize = 4;
 	} else if (version == 6) {
-		family = AF_INET6;
+		nhdr.ndm_family = AF_INET6;
 		addrsize = 16;
 	} else {
 		return -EINVAL;
@@ -12369,41 +12363,28 @@
 		return -1;
 	}
 
-	rn = rtnl_neigh_alloc();
-	if (rn == NULL)
+	msg = nlmsg_alloc_simple(RTM_DELNEIGH, NLM_F_CREATE);
+	if (!msg)
 		return -ENOMEM;
 
-	/* set the destination ip address for neigh */
-	nl_ipaddr = nl_addr_build(family, (void *) ipaddr, addrsize);
-	if (nl_ipaddr == NULL) {
-		wpa_printf(MSG_DEBUG, "nl80211: nl_ipaddr build failed");
-		res = -ENOMEM;
+	res = -ENOMEM;
+	if (nlmsg_append(msg, &nhdr, sizeof(nhdr), NLMSG_ALIGNTO) < 0 ||
+	    nla_put(msg, NDA_DST, addrsize, (void *) ipaddr))
 		goto errout;
-	}
-	res = rtnl_neigh_set_dst(rn, nl_ipaddr);
-	if (res) {
-		wpa_printf(MSG_DEBUG,
-			   "nl80211: neigh set destination addr failed");
+
+	res = nl_send_auto_complete(drv->rtnl_sk, msg);
+	if (res < 0)
 		goto errout;
-	}
 
-	rtnl_neigh_set_ifindex(rn, bss->br_ifindex);
-
-	res = rtnl_neigh_delete(drv->rtnl_sk, rn, 0);
+	res = nl_wait_for_ack(drv->rtnl_sk);
 	if (res) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Deleting bridge ip neigh failed: %s",
 			   nl_geterror(res));
 	}
 errout:
-	if (nl_ipaddr)
-		nl_addr_put(nl_ipaddr);
-	if (rn)
-		rtnl_neigh_put(rn);
+	nlmsg_free(msg);
 	return res;
-#else /* CONFIG_LIBNL3_ROUTE */
-	return -1;
-#endif /* CONFIG_LIBNL3_ROUTE */
 }
 
 
@@ -13986,12 +13967,12 @@
 }
 #endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */
 
-static int nl80211_link_add(void *priv, u8 link_id, const u8 *addr)
+static int nl80211_link_add(void *priv, u8 link_id, const u8 *addr,
+			    void *bss_ctx)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct nl_msg *msg;
-	unsigned int idx, i;
 	int ret;
 
 	wpa_printf(MSG_DEBUG, "nl80211: MLD: add link_id=%u, addr=" MACSTR,
@@ -14004,32 +13985,24 @@
 		return -EINVAL;
 	}
 
-	if (bss->n_links >= MAX_NUM_MLD_LINKS) {
-		wpa_printf(MSG_DEBUG, "nl80211: MLD: already have n_links=%zu",
-			   bss->n_links);
+	if (link_id >= MAX_NUM_MLD_LINKS) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: invalid link_id=%u", link_id);
 		return -EINVAL;
 	}
 
-	for (i = 0; i < bss->n_links; i++) {
-		if (bss->links[i].link_id == link_id &&
-		    bss->links[i].beacon_set) {
-			wpa_printf(MSG_DEBUG,
-				   "nl80211: MLD: link already set");
-			return -EINVAL;
-		}
+	if (bss->valid_links & BIT(link_id)) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: MLD: Link %u already set", link_id);
+		return -EINVAL;
 	}
 
-	/* try using the first link entry, assuming it is not beaconing yet */
-	if (bss->n_links == 1 &&
-	    bss->flink->link_id == NL80211_DRV_LINK_ID_NA) {
+	if (!bss->valid_links) {
+		/* Becoming MLD, verify we were not beaconing */
 		if (bss->flink->beacon_set) {
 			wpa_printf(MSG_DEBUG, "nl80211: BSS already beaconing");
 			return -EINVAL;
 		}
-
-		idx = 0;
-	} else {
-		idx = bss->n_links;
 	}
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_ADD_LINK);
@@ -14047,16 +14020,51 @@
 		return ret;
 	}
 
-	bss->links[idx].link_id = link_id;
-	os_memcpy(bss->links[idx].addr, addr, ETH_ALEN);
+	os_memcpy(bss->links[link_id].addr, addr, ETH_ALEN);
 
-	bss->n_links = idx + 1;
+	/* The new link is the first one, make it the default */
+	if (!bss->valid_links)
+		bss->flink = &bss->links[link_id];
 
-	wpa_printf(MSG_DEBUG, "nl80211: MLD: n_links=%zu", bss->n_links);
+	bss->valid_links |= BIT(link_id);
+	bss->links[link_id].ctx = bss_ctx;
+
+	wpa_printf(MSG_DEBUG, "nl80211: MLD: valid_links=0x%04x",
+		   bss->valid_links);
 	return 0;
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static int wpa_driver_nl80211_link_sta_remove(void *priv, u8 link_id,
+					      const u8 *addr)
+{
+	struct i802_bss *bss = priv;
+	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl_msg *msg;
+	int ret;
+
+	if (!(bss->valid_links & BIT(link_id)))
+		return -ENOLINK;
+
+	if (!(msg = nl80211_bss_msg(bss, 0, NL80211_CMD_REMOVE_LINK_STA)) ||
+	    nla_put(msg, NL80211_ATTR_MLD_ADDR, ETH_ALEN, addr) ||
+	    nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) {
+		nlmsg_free(msg);
+		return -ENOBUFS;
+	}
+
+	ret = send_and_recv_cmd(drv, msg);
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: link_sta_remove -> REMOVE_LINK_STA on link_id %u from MLD STA "
+		   MACSTR ", from %s --> %d (%s)",
+		   link_id, MAC2STR(addr), bss->ifname, ret, strerror(-ret));
+
+	return ret;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 #ifdef CONFIG_TESTING_OPTIONS
 
 static int testing_nl80211_register_frame(void *priv, u16 type,
@@ -14111,7 +14119,7 @@
 	.scan2 = driver_nl80211_scan2,
 	.sched_scan = wpa_driver_nl80211_sched_scan,
 	.stop_sched_scan = wpa_driver_nl80211_stop_sched_scan,
-	.get_scan_results2 = wpa_driver_nl80211_get_scan_results,
+	.get_scan_results = wpa_driver_nl80211_get_scan_results,
 	.abort_scan = wpa_driver_nl80211_abort_scan,
 	.deauthenticate = driver_nl80211_deauthenticate,
 	.authenticate = driver_nl80211_authenticate,
@@ -14251,6 +14259,11 @@
 #endif /* CONFIG_DPP */
 	.get_sta_mlo_info = nl80211_get_sta_mlo_info,
 	.link_add = nl80211_link_add,
+#ifdef CONFIG_IEEE80211BE
+	.link_remove = driver_nl80211_link_remove,
+	.is_drv_shared = nl80211_is_drv_shared,
+	.link_sta_remove = wpa_driver_nl80211_link_sta_remove,
+#endif /* CONFIG_IEEE80211BE */
 #ifdef CONFIG_TESTING_OPTIONS
 	.register_frame = testing_nl80211_register_frame,
 	.radio_disable = testing_nl80211_radio_disable,
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index 048c9a3..d2c1ffa 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -55,7 +55,6 @@
 struct i802_link {
 	unsigned int beacon_set:1;
 
-	s8 link_id;
 	int freq;
 	int bandwidth;
 	u8 addr[ETH_ALEN];
@@ -66,7 +65,7 @@
 	struct wpa_driver_nl80211_data *drv;
 	struct i802_bss *next;
 
-	size_t n_links;
+	u16 valid_links;
 	struct i802_link links[MAX_NUM_MLD_LINKS];
 	struct i802_link *flink;
 
@@ -292,6 +291,21 @@
 		  void *ack_data,
 		  struct nl80211_err_info *err_info);
 
+// This function is not used in supplicant anymore. But keeping this wrapper
+// functions for libraries outside wpa_supplicant to build (For eg: lib_driver_cmd_XX)
+static inline int
+send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
+		   struct nl_msg *msg,
+		   int (*valid_handler)(struct nl_msg *, void *),
+		   void *valid_data,
+		   int (*ack_handler_custom)(struct nl_msg *, void *),
+		   void *ack_data)
+{
+	return send_and_recv(drv->global, drv->global->nl, msg,
+			     valid_handler, valid_data,
+			     ack_handler_custom, ack_data, NULL);
+}
+
 static inline int
 send_and_recv_cmd(struct wpa_driver_nl80211_data *drv,
 		  struct nl_msg *msg)
@@ -357,6 +371,18 @@
 void nl80211_restore_ap_mode(struct i802_bss *bss);
 struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id);
 
+static inline bool nl80211_link_valid(u16 links, s8 link_id)
+{
+	if (link_id < 0 || link_id >= MAX_NUM_MLD_LINKS)
+		return false;
+
+	if (links & BIT(link_id))
+		return true;
+
+	return false;
+}
+
+
 static inline bool
 nl80211_attr_supported(struct wpa_driver_nl80211_data *drv, unsigned int attr)
 {
@@ -394,7 +420,8 @@
 int wpa_driver_nl80211_sched_scan(void *priv,
 				  struct wpa_driver_scan_params *params);
 int wpa_driver_nl80211_stop_sched_scan(void *priv);
-struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv);
+struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv,
+							      const u8 *bssid);
 void nl80211_dump_scan(struct wpa_driver_nl80211_data *drv);
 int wpa_driver_nl80211_abort_scan(void *priv, u64 scan_cookie);
 int wpa_driver_nl80211_vendor_scan(struct i802_bss *bss,
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 0b6f511..d8375cc 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -888,6 +888,10 @@
 				nla_get_u16(tb1[NL80211_ATTR_MLD_CAPA_AND_OPS]);
 		}
 
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: EML Capability: 0x%x MLD Capability: 0x%x",
+			   capa->eml_capa, capa->mld_capa_and_ops);
+
 		drv->num_iface_capa++;
 		if (drv->num_iface_capa == NL80211_IFTYPE_MAX)
 			break;
@@ -2165,6 +2169,9 @@
 	for (m = 0; m < *num_modes; m++) {
 		if (!modes[m].num_channels)
 			continue;
+
+		modes[m].is_6ghz = false;
+
 		if (modes[m].channels[0].freq < 2000) {
 			modes[m].num_channels = 0;
 			continue;
@@ -2176,10 +2183,14 @@
 					break;
 				}
 			}
-		} else if (modes[m].channels[0].freq > 50000)
+		} else if (modes[m].channels[0].freq > 50000) {
 			modes[m].mode = HOSTAPD_MODE_IEEE80211AD;
-		else
+		} else if (is_6ghz_freq(modes[m].channels[0].freq)) {
 			modes[m].mode = HOSTAPD_MODE_IEEE80211A;
+			modes[m].is_6ghz = true;
+		} else {
+			modes[m].mode = HOSTAPD_MODE_IEEE80211A;
+		}
 	}
 
 	/* Remove unsupported bands */
@@ -2407,6 +2418,57 @@
 }
 
 
+static void nl80211_set_6ghz_mode(struct hostapd_hw_modes *mode, int start,
+				  int end, int max_bw)
+{
+	int c;
+
+	for (c = 0; c < mode->num_channels; c++) {
+		struct hostapd_channel_data *chan = &mode->channels[c];
+
+		if (chan->freq - 10 < start || chan->freq + 10 > end)
+			continue;
+
+		if (max_bw >= 80)
+			chan->flag |= HOSTAPD_CHAN_VHT_80MHZ_SUBCHANNEL;
+
+		if (max_bw >= 160)
+			chan->flag |= HOSTAPD_CHAN_VHT_160MHZ_SUBCHANNEL;
+
+		if (max_bw >= 320)
+			chan->flag |= HOSTAPD_CHAN_EHT_320MHZ_SUBCHANNEL;
+	}
+}
+
+
+static void nl80211_reg_rule_6ghz(struct nlattr *tb[],
+				 struct phy_info_arg *results)
+{
+	u32 start, end, max_bw;
+	u16 m;
+
+	if (!tb[NL80211_ATTR_FREQ_RANGE_START] ||
+	    !tb[NL80211_ATTR_FREQ_RANGE_END] ||
+	    !tb[NL80211_ATTR_FREQ_RANGE_MAX_BW])
+		return;
+
+	start = nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_START]) / 1000;
+	end = nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_END]) / 1000;
+	max_bw = nla_get_u32(tb[NL80211_ATTR_FREQ_RANGE_MAX_BW]) / 1000;
+
+	if (max_bw < 80)
+		return;
+
+	for (m = 0; m < *results->num_modes; m++) {
+		if (results->modes[m].num_channels == 0 ||
+		    !is_6ghz_freq(results->modes[m].channels[0].freq))
+			continue;
+
+		nl80211_set_6ghz_mode(&results->modes[m], start, end, max_bw);
+	}
+}
+
+
 static void nl80211_set_dfs_domain(enum nl80211_dfs_regions region,
 				   u8 *dfs_domain)
 {
@@ -2525,6 +2587,13 @@
 		nl80211_reg_rule_vht(tb_rule, results);
 	}
 
+	nla_for_each_nested(nl_rule, tb_msg[NL80211_ATTR_REG_RULES], rem_rule)
+	{
+		nla_parse(tb_rule, NL80211_FREQUENCY_ATTR_MAX,
+			  nla_data(nl_rule), nla_len(nl_rule), reg_policy);
+		nl80211_reg_rule_6ghz(tb_rule, results);
+	}
+
 	return NL_SKIP;
 }
 
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index 4163f79..9ce73c6 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -476,10 +476,7 @@
 	 * links when the link used for (re)association is removed.
 	 */
 	if (removed_links & BIT(drv->sta_mlo_info.assoc_link_id)) {
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-			if (!(drv->sta_mlo_info.valid_links & BIT(i)))
-				continue;
-
+		for_each_link(drv->sta_mlo_info.valid_links, i) {
 			os_memcpy(drv->bssid, drv->sta_mlo_info.links[i].bssid,
 				  ETH_ALEN);
 			drv->sta_mlo_info.assoc_link_id = i;
@@ -701,10 +698,7 @@
 	}
 
 	/* Get MLO links info for rejected links */
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!((mlo->req_links & ~mlo->valid_links) & BIT(i)))
-			continue;
-
+	for_each_link((mlo->req_links & ~mlo->valid_links), i) {
 		os_memcpy(mlo->links[i].bssid, resp_info.addr[i], ETH_ALEN);
 		os_memcpy(mlo->links[i].addr, req_info.addr[i], ETH_ALEN);
 	}
@@ -874,9 +868,7 @@
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: TID-to-link: Received uplink %x downlink %x",
 			   uplink, downlink);
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-			if (!(drv->sta_mlo_info.valid_links & BIT(i)))
-				continue;
+		for_each_link(drv->sta_mlo_info.valid_links, i) {
 			if (uplink & BIT(i))
 				event.t2l_map_info.t2lmap[i].uplink |=
 					BIT(tidnum);
@@ -1268,14 +1260,23 @@
 	if (cf2)
 		data.ch_switch.cf2 = nla_get_u32(cf2);
 
-	if (finished)
-		bss->flink->freq = data.ch_switch.freq;
-
 	if (link)
 		data.ch_switch.link_id = nla_get_u8(link);
 	else
 		data.ch_switch.link_id = NL80211_DRV_LINK_ID_NA;
 
+	if (finished) {
+		if (data.ch_switch.link_id != NL80211_DRV_LINK_ID_NA) {
+			struct i802_link *mld_link;
+
+			mld_link = nl80211_get_link(bss,
+						    data.ch_switch.link_id);
+			mld_link->freq = data.ch_switch.freq;
+		} else {
+			bss->flink->freq = data.ch_switch.freq;
+		}
+	}
+
 	if (link && is_sta_interface(drv->nlmode)) {
 		u8 link_id = data.ch_switch.link_id;
 
@@ -1618,18 +1619,17 @@
 }
 
 
-static struct i802_link *
-nl80211_get_mld_link_by_freq(struct i802_bss *bss, unsigned int freq)
+static s8
+nl80211_get_link_id_by_freq(struct i802_bss *bss, unsigned int freq)
 {
 	unsigned int i;
 
-	for (i = 0; i < bss->n_links; i++) {
-		if ((unsigned int) bss->links[i].freq == freq &&
-		    bss->links[i].link_id != -1)
-			return &bss->links[i];
+	for_each_link(bss->valid_links, i) {
+		if ((unsigned int) bss->links[i].freq == freq)
+			return i;
 	}
 
-	return NULL;
+	return NL80211_DRV_LINK_ID_NA;
 }
 
 
@@ -1663,12 +1663,12 @@
 	/* Determine the MLD link either by an explicitly provided link id or
 	 * finding a match based on the frequency. */
 	if (link)
-		mld_link = nl80211_get_link(bss, nla_get_u8(link));
+		link_id = nla_get_u8(link);
 	else if (freq)
-		mld_link = nl80211_get_mld_link_by_freq(bss, nla_get_u32(freq));
+		link_id = nl80211_get_link_id_by_freq(bss, nla_get_u32(freq));
 
-	if (mld_link)
-		link_id = mld_link->link_id;
+	if (nl80211_link_valid(bss->valid_links, link_id))
+		mld_link = nl80211_get_link(bss, link_id);
 
 	data = nla_data(frame);
 	len = nla_len(frame);
@@ -1920,7 +1920,7 @@
 	os_memset(&data, 0, sizeof(data));
 	addr = nla_data(tb[NL80211_ATTR_MAC]);
 
-	if (bss->links[0].link_id == NL80211_DRV_LINK_ID_NA &&
+	if (!bss->valid_links &&
 	    (tb[NL80211_ATTR_MLO_LINK_ID] ||
 	     tb[NL80211_ATTR_MLD_ADDR])) {
 		wpa_printf(MSG_ERROR,
@@ -2184,7 +2184,7 @@
 		u8 *req_ies = NULL, *resp_ies = NULL;
 		size_t req_ies_len = 0, resp_ies_len = 0;
 
-		if (bss->links[0].link_id == NL80211_DRV_LINK_ID_NA &&
+		if (!bss->valid_links &&
 		    (tb[NL80211_ATTR_MLO_LINK_ID] ||
 		     tb[NL80211_ATTR_MLD_ADDR])) {
 			wpa_printf(MSG_ERROR,
@@ -2452,7 +2452,6 @@
 {
 	union wpa_event_data data;
 	enum nl80211_radar_event event_type;
-	struct i802_link *mld_link = NULL;
 
 	if (!tb[NL80211_ATTR_WIPHY_FREQ] || !tb[NL80211_ATTR_RADAR_EVENT])
 		return;
@@ -2462,11 +2461,13 @@
 	data.dfs_event.freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
 	event_type = nla_get_u32(tb[NL80211_ATTR_RADAR_EVENT]);
 
-	if (data.dfs_event.freq) {
-		mld_link = nl80211_get_mld_link_by_freq(drv->first_bss,
-							data.dfs_event.freq);
-		if (mld_link)
-			data.dfs_event.link_id = mld_link->link_id;
+	if (tb[NL80211_ATTR_MLO_LINK_ID]) {
+		data.dfs_event.link_id =
+			nla_get_u8(tb[NL80211_ATTR_MLO_LINK_ID]);
+	} else if (data.dfs_event.freq) {
+		data.dfs_event.link_id =
+			nl80211_get_link_id_by_freq(drv->first_bss,
+						    data.dfs_event.freq);
 	}
 
 	/* Check HT params */
@@ -2831,7 +2832,6 @@
 {
 	union wpa_event_data data;
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
-	struct i802_link *mld_link = NULL;
 
 	wpa_printf(MSG_DEBUG,
 		   "nl80211: DFS offload radar vendor event received");
@@ -2850,11 +2850,13 @@
 	data.dfs_event.freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
 	data.dfs_event.link_id = NL80211_DRV_LINK_ID_NA;
 
-	if (data.dfs_event.freq) {
-		mld_link = nl80211_get_mld_link_by_freq(drv->first_bss,
-							data.dfs_event.freq);
-		if (mld_link)
-			data.dfs_event.link_id = mld_link->link_id;
+	if (tb[NL80211_ATTR_MLO_LINK_ID]) {
+		data.dfs_event.link_id =
+			nla_get_u8(tb[NL80211_ATTR_MLO_LINK_ID]);
+	} else if (data.dfs_event.freq) {
+		data.dfs_event.link_id =
+			nl80211_get_link_id_by_freq(drv->first_bss,
+						    data.dfs_event.freq);
 	}
 
 	wpa_printf(MSG_DEBUG, "nl80211: DFS event on freq %d MHz, link=%d",
@@ -2966,6 +2968,7 @@
 	info = &event.scan_info;
 	info->aborted = aborted;
 	info->external_scan = external_scan;
+	info->scan_cookie = nla_get_u64(tb[QCA_WLAN_VENDOR_ATTR_SCAN_COOKIE]);
 
 	if (tb[QCA_WLAN_VENDOR_ATTR_SCAN_SSIDS]) {
 		nla_for_each_nested(nl,
@@ -3748,8 +3751,11 @@
 		   (long long unsigned int) cookie,
 		   match ? " (match)" : "",
 		   drv->send_frame_cookie == cookie ? " (match-saved)" : "");
-	if (drv->send_frame_cookie == cookie)
+	if (drv->send_frame_cookie == cookie) {
 		drv->send_frame_cookie = (u64) -1;
+		if (!match)
+			goto send_event;
+	}
 	if (!match)
 		return;
 
@@ -3759,6 +3765,7 @@
 			   (drv->num_send_frame_cookies - i - 1) * sizeof(u64));
 	drv->num_send_frame_cookies--;
 
+send_event:
 	wpa_supplicant_event(drv->ctx, EVENT_TX_WAIT_EXPIRE, NULL);
 }
 
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index 68ae579..1eb4374 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -728,7 +728,7 @@
 
 static struct wpa_scan_res *
 nl80211_parse_bss_info(struct wpa_driver_nl80211_data *drv,
-		       struct nl_msg *msg)
+		       struct nl_msg *msg, const u8 *bssid)
 {
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
 	struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
@@ -762,6 +762,9 @@
 	if (nla_parse_nested(bss, NL80211_BSS_MAX, tb[NL80211_ATTR_BSS],
 			     bss_policy))
 		return NULL;
+	if (bssid && bss[NL80211_BSS_BSSID] &&
+	    !ether_addr_equal(bssid, nla_data(bss[NL80211_BSS_BSSID])))
+		return NULL;
 	if (bss[NL80211_BSS_INFORMATION_ELEMENTS]) {
 		ie = nla_data(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
 		ie_len = nla_len(bss[NL80211_BSS_INFORMATION_ELEMENTS]);
@@ -866,6 +869,7 @@
 struct nl80211_bss_info_arg {
 	struct wpa_driver_nl80211_data *drv;
 	struct wpa_scan_results *res;
+	const u8 *bssid;
 };
 
 static int bss_info_handler(struct nl_msg *msg, void *arg)
@@ -875,7 +879,7 @@
 	struct wpa_scan_res **tmp;
 	struct wpa_scan_res *r;
 
-	r = nl80211_parse_bss_info(_arg->drv, msg);
+	r = nl80211_parse_bss_info(_arg->drv, msg, _arg->bssid);
 	if (!r)
 		return NL_SKIP;
 
@@ -973,7 +977,7 @@
 
 
 static struct wpa_scan_results *
-nl80211_get_scan_results(struct wpa_driver_nl80211_data *drv)
+nl80211_get_scan_results(struct wpa_driver_nl80211_data *drv, const u8 *bssid)
 {
 	struct nl_msg *msg;
 	struct wpa_scan_results *res;
@@ -993,6 +997,7 @@
 
 	arg.drv = drv;
 	arg.res = res;
+	arg.bssid = bssid;
 	ret = send_and_recv_resp(drv, msg, bss_info_handler, &arg);
 	if (ret == -EAGAIN) {
 		count++;
@@ -1029,16 +1034,18 @@
 
 /**
  * wpa_driver_nl80211_get_scan_results - Fetch the latest scan results
- * @priv: Pointer to private wext data from wpa_driver_nl80211_init()
+ * @priv: Pointer to private nl80211 data from wpa_driver_nl80211_init()
+ * @bssid: Return results only for the specified BSSID, %NULL for all
  * Returns: Scan results on success, -1 on failure
  */
-struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv)
+struct wpa_scan_results * wpa_driver_nl80211_get_scan_results(void *priv,
+							      const u8 *bssid)
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct wpa_scan_results *res;
 
-	res = nl80211_get_scan_results(drv);
+	res = nl80211_get_scan_results(drv, bssid);
 	if (res)
 		wpa_driver_nl80211_check_bss_status(drv, res);
 	return res;
@@ -1055,7 +1062,7 @@
 	struct nl80211_dump_scan_ctx *ctx = arg;
 	struct wpa_scan_res *r;
 
-	r = nl80211_parse_bss_info(ctx->drv, msg);
+	r = nl80211_parse_bss_info(ctx->drv, msg, NULL);
 	if (!r)
 		return NL_SKIP;
 	wpa_printf(MSG_DEBUG, "nl80211: %d " MACSTR " %d%s",
@@ -1268,6 +1275,11 @@
 			goto fail;
 	}
 
+	if (is_ap_interface(drv->nlmode) &&
+	    params->link_id != NL80211_DRV_LINK_ID_NA &&
+	    nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_SCAN_LINK_ID, params->link_id))
+		goto fail;
+
 	nla_nest_end(msg, attr);
 
 	ret = send_and_recv_resp(drv, msg, scan_cookie_handler, &cookie);
diff --git a/src/drivers/drivers.mak b/src/drivers/drivers.mak
index 0186099..3b3098d 100644
--- a/src/drivers/drivers.mak
+++ b/src/drivers/drivers.mak
@@ -160,7 +160,6 @@
 NEED_LINUX_IOCTL=y
 ifdef CONFIG_VLAN_NETLINK
 NEED_LIBNL=y
-CONFIG_LIBNL3_ROUTE=y
 endif
 endif
 
diff --git a/src/drivers/drivers.mk b/src/drivers/drivers.mk
index 8c58456..1cbe652 100644
--- a/src/drivers/drivers.mk
+++ b/src/drivers/drivers.mk
@@ -154,7 +154,6 @@
 NEED_LINUX_IOCTL=y
 ifdef CONFIG_VLAN_NETLINK
 NEED_LIBNL=y
-CONFIG_LIBNL3_ROUTE=y
 endif
 endif
 
diff --git a/src/eap_peer/eap_wsc.c b/src/eap_peer/eap_wsc.c
index a1e7bff..fe61c83 100644
--- a/src/eap_peer/eap_wsc.c
+++ b/src/eap_peer/eap_wsc.c
@@ -255,8 +255,18 @@
 		cfg.new_ap_settings = &new_ap_settings;
 	}
 
-	if (os_strstr(phase1, "multi_ap=1"))
-		cfg.multi_ap_backhaul_sta = 1;
+	pos = os_strstr(phase1, "multi_ap=");
+	if (pos) {
+		u16 id = atoi(pos + 9);
+
+		if (id != 0) {
+			cfg.multi_ap_backhaul_sta = 1;
+			cfg.multi_ap_profile = id;
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "EAP-WSC: Invalid multi_ap setting");
+		}
+	}
 
 	data->wps = wps_init(&cfg);
 	if (data->wps == NULL) {
diff --git a/src/l2_packet/l2_packet_freebsd.c b/src/l2_packet/l2_packet_freebsd.c
index 3f0b299..481c8ca 100644
--- a/src/l2_packet/l2_packet_freebsd.c
+++ b/src/l2_packet/l2_packet_freebsd.c
@@ -30,6 +30,9 @@
 #include "eloop.h"
 #include "l2_packet.h"
 
+#ifndef ETHER_VLAN_ENCAP_LEN
+#define ETHER_VLAN_ENCAP_LEN 4
+#endif
 
 static const u8 pae_group_addr[ETH_ALEN] =
 { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 };
diff --git a/src/pasn/pasn_common.c b/src/pasn/pasn_common.c
new file mode 100644
index 0000000..e2c6681
--- /dev/null
+++ b/src/pasn/pasn_common.c
@@ -0,0 +1,232 @@
+/*
+ * PASN common processing
+ *
+ * Copyright (C) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/wpa_common.h"
+#include "common/sae.h"
+#include "crypto/sha384.h"
+#include "crypto/crypto.h"
+#include "common/ieee802_11_defs.h"
+#include "pasn_common.h"
+
+
+struct pasn_data * pasn_data_init(void)
+{
+	struct pasn_data *pasn = os_zalloc(sizeof(struct pasn_data));
+
+	return pasn;
+}
+
+
+void pasn_data_deinit(struct pasn_data *pasn)
+{
+	bin_clear_free(pasn, sizeof(struct pasn_data));
+}
+
+
+void pasn_register_callbacks(struct pasn_data *pasn, void *cb_ctx,
+			     int (*send_mgmt)(void *ctx, const u8 *data,
+					      size_t data_len, int noack,
+					      unsigned int freq,
+					      unsigned int wait),
+			     int (*validate_custom_pmkid)(void *ctx,
+							  const u8 *addr,
+							  const u8 *pmkid))
+{
+	if (!pasn)
+		return;
+
+	pasn->cb_ctx = cb_ctx;
+	pasn->send_mgmt = send_mgmt;
+	pasn->validate_custom_pmkid = validate_custom_pmkid;
+}
+
+
+void pasn_enable_kdk_derivation(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return;
+	pasn->derive_kdk = true;
+	pasn->kdk_len = WPA_KDK_MAX_LEN;
+}
+
+
+void pasn_disable_kdk_derivation(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return;
+	pasn->derive_kdk = false;
+	pasn->kdk_len = 0;
+}
+
+
+void pasn_set_akmp(struct pasn_data *pasn, int akmp)
+{
+	if (!pasn)
+		return;
+	pasn->akmp = akmp;
+}
+
+
+void pasn_set_cipher(struct pasn_data *pasn, int cipher)
+{
+	if (!pasn)
+		return;
+	pasn->cipher = cipher;
+}
+
+
+void pasn_set_own_addr(struct pasn_data *pasn, const u8 *addr)
+{
+	if (!pasn || !addr)
+		return;
+	os_memcpy(pasn->own_addr, addr, ETH_ALEN);
+}
+
+
+void pasn_set_peer_addr(struct pasn_data *pasn, const u8 *addr)
+{
+	if (!pasn || !addr)
+		return;
+	os_memcpy(pasn->peer_addr, addr, ETH_ALEN);
+}
+
+
+void pasn_set_bssid(struct pasn_data *pasn, const u8 *addr)
+{
+	if (!pasn || !addr)
+		return;
+	os_memcpy(pasn->bssid, addr, ETH_ALEN);
+}
+
+
+int pasn_set_pt(struct pasn_data *pasn, struct sae_pt *pt)
+{
+	if (!pasn)
+		return -1;
+#ifdef CONFIG_SAE
+	pasn->pt = pt;
+	return 0;
+#else /* CONFIG_SAE */
+	return -1;
+#endif /* CONFIG_SAE */
+}
+
+
+void pasn_set_password(struct pasn_data *pasn, const char *password)
+{
+	if (!pasn)
+		return;
+	pasn->password = password;
+}
+
+
+void pasn_set_wpa_key_mgmt(struct pasn_data *pasn, int key_mgmt)
+{
+	if (!pasn)
+		return;
+	pasn->wpa_key_mgmt = key_mgmt;
+}
+
+
+void pasn_set_rsn_pairwise(struct pasn_data *pasn, int rsn_pairwise)
+{
+	if (!pasn)
+		return;
+	pasn->rsn_pairwise = rsn_pairwise;
+}
+
+
+void pasn_set_rsnxe_caps(struct pasn_data *pasn, u16 rsnxe_capab)
+{
+	if (!pasn)
+		return;
+	pasn->rsnxe_capab = rsnxe_capab;
+}
+
+
+void pasn_set_rsnxe_ie(struct pasn_data *pasn, const u8 *rsnxe_ie)
+{
+	if (!pasn || !rsnxe_ie)
+		return;
+	pasn->rsnxe_ie = rsnxe_ie;
+}
+
+
+void pasn_set_custom_pmkid(struct pasn_data *pasn, const u8 *pmkid)
+{
+	if (!pasn || !pmkid)
+		return;
+	os_memcpy(pasn->custom_pmkid, pmkid, PMKID_LEN);
+	pasn->custom_pmkid_valid = true;
+}
+
+
+int pasn_set_extra_ies(struct pasn_data *pasn, const u8 *extra_ies,
+		       size_t extra_ies_len)
+{
+	if (!pasn || !extra_ies_len || !extra_ies)
+		return -1;
+
+	if (pasn->extra_ies) {
+		os_free((u8 *) pasn->extra_ies);
+		pasn->extra_ies_len = extra_ies_len;
+	}
+
+	pasn->extra_ies = os_memdup(extra_ies, extra_ies_len);
+	if (!pasn->extra_ies) {
+		wpa_printf(MSG_ERROR,
+			   "PASN: Extra IEs memory allocation failed");
+		return -1;
+	}
+	pasn->extra_ies_len = extra_ies_len;
+	return 0;
+}
+
+
+int pasn_get_akmp(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return 0;
+	return pasn->akmp;
+}
+
+
+int pasn_get_cipher(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return 0;
+	return pasn->cipher;
+}
+
+
+size_t pasn_get_pmk_len(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return 0;
+	return pasn->pmk_len;
+}
+
+
+u8 * pasn_get_pmk(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return NULL;
+	return pasn->pmk;
+}
+
+
+struct wpa_ptk * pasn_get_ptk(struct pasn_data *pasn)
+{
+	if (!pasn)
+		return NULL;
+	return &pasn->ptk;
+}
diff --git a/src/pasn/pasn_common.h b/src/pasn/pasn_common.h
index a4850a2..36710c2 100644
--- a/src/pasn/pasn_common.h
+++ b/src/pasn/pasn_common.h
@@ -16,8 +16,6 @@
 extern "C" {
 #endif
 
-#ifdef CONFIG_PASN
-
 enum pasn_fils_state {
 	PASN_FILS_STATE_NONE = 0,
 	PASN_FILS_STATE_PENDING_AS,
@@ -35,19 +33,46 @@
 };
 
 struct pasn_data {
+	/* External modules access below variables using setter and getter
+	 * functions */
 	int akmp;
 	int cipher;
+	u8 own_addr[ETH_ALEN];
+	u8 peer_addr[ETH_ALEN];
+	u8 bssid[ETH_ALEN];
+	struct rsn_pmksa_cache *pmksa;
+	bool derive_kdk;
+	size_t kdk_len;
+	void *cb_ctx;
+
+#ifdef CONFIG_SAE
+	struct sae_pt *pt;
+#endif /* CONFIG_SAE */
+
+	/* Responder */
+	const char *password;
+	int wpa_key_mgmt;
+	int rsn_pairwise;
+	u16 rsnxe_capab;
+	const u8 *rsnxe_ie;
+	bool custom_pmkid_valid;
+	u8 custom_pmkid[PMKID_LEN];
+
+	/*
+	 * Extra elements to add into Authentication frames. These can be used,
+	 * e.g., for Wi-Fi Aware use cases.
+	 */
+	const u8 *extra_ies;
+	size_t extra_ies_len;
+
+	/* External modules do not access below variables */
 	u16 group;
 	bool secure_ltf;
 	int freq;
-	size_t kdk_len;
 
 	u8 trans_seq;
 	u8 status;
 
-	u8 own_addr[ETH_ALEN];
-	u8 peer_addr[ETH_ALEN];
-	u8 bssid[ETH_ALEN];
 	size_t pmk_len;
 	u8 pmk[PMK_LEN_MAX];
 	bool using_pmksa;
@@ -63,7 +88,6 @@
 
 #ifdef CONFIG_SAE
 	struct sae_data sae;
-	struct sae_pt *pt;
 #endif /* CONFIG_SAE */
 
 #ifdef CONFIG_FILS
@@ -81,15 +105,12 @@
 	 * differently for the PASN initiator (using RSN Supplicant
 	 * implementation) and PASN responser (using RSN Authenticator
 	 * implementation). Functions cannot be mixed between those cases. */
-	struct rsn_pmksa_cache *pmksa;
 	struct rsn_pmksa_cache_entry *pmksa_entry;
 	struct eapol_sm *eapol;
 	int fast_reauth;
 #ifdef CONFIG_TESTING_OPTIONS
 	int corrupt_mic;
 #endif /* CONFIG_TESTING_OPTIONS */
-	void *cb_ctx;
-	u16 rsnxe_capab;
 	int network_id;
 
 	u8 wrapped_data_format;
@@ -97,16 +118,11 @@
 
 	/* Responder */
 	bool noauth; /* Whether PASN without mutual authentication is enabled */
-	int wpa_key_mgmt;
-	int rsn_pairwise;
-	bool derive_kdk;
-	const char *password;
 	int disable_pmksa_caching;
 	int *pasn_groups;
 	struct wpabuf *wrapped_data;
 	int use_anti_clogging;
 	const u8 *rsn_ie;
-	const u8 *rsnxe_ie;
 	size_t rsn_ie_len;
 
 	u8 *comeback_key;
@@ -114,16 +130,6 @@
 	u16 comeback_idx;
 	u16 *comeback_pending_idx;
 
-	bool custom_pmkid_valid;
-	u8 custom_pmkid[PMKID_LEN];
-
-	/**
-	 * Extra elements to add into Authentication frames. These can be used,
-	 * e.g., for Wi-Fi Aware use cases.
-	 */
-	const u8 *extra_ies;
-	size_t extra_ies_len;
-
 	/**
 	 * send_mgmt - Function handler to transmit a Management frame
 	 * @ctx: Callback context from cb_ctx
@@ -147,7 +153,6 @@
 };
 
 /* Initiator */
-
 void wpa_pasn_reset(struct pasn_data *pasn);
 int wpas_pasn_start(struct pasn_data *pasn, const u8 *own_addr,
 		    const u8 *peer_addr, const u8 *bssid,
@@ -177,7 +182,45 @@
 			  const u8 *peer_addr,
 			  struct rsn_pmksa_cache_entry *pmksa, u16 status);
 
-#endif /* CONFIG_PASN */
+struct pasn_data * pasn_data_init(void);
+void pasn_data_deinit(struct pasn_data *pasn);
+void pasn_register_callbacks(struct pasn_data *pasn, void *cb_ctx,
+			     int (*send_mgmt)(void *ctx, const u8 *data,
+					      size_t data_len, int noack,
+					      unsigned int freq,
+					      unsigned int wait),
+			     int (*validate_custom_pmkid)(void *ctx,
+							  const u8 *addr,
+							  const u8 *pmkid));
+void pasn_enable_kdk_derivation(struct pasn_data *pasn);
+void pasn_disable_kdk_derivation(struct pasn_data *pasn);
+
+void pasn_set_akmp(struct pasn_data *pasn, int akmp);
+void pasn_set_cipher(struct pasn_data *pasn, int cipher);
+void pasn_set_own_addr(struct pasn_data *pasn, const u8 *addr);
+void pasn_set_peer_addr(struct pasn_data *pasn, const u8 *addr);
+void pasn_set_bssid(struct pasn_data *pasn, const u8 *addr);
+void pasn_set_initiator_pmksa(struct pasn_data *pasn,
+			      struct rsn_pmksa_cache *pmksa);
+void pasn_set_responder_pmksa(struct pasn_data *pasn,
+			      struct rsn_pmksa_cache *pmksa);
+int pasn_set_pt(struct pasn_data *pasn, struct sae_pt *pt);
+
+/* Responder */
+void pasn_set_password(struct pasn_data *pasn, const char *password);
+void pasn_set_wpa_key_mgmt(struct pasn_data *pasn, int key_mgmt);
+void pasn_set_rsn_pairwise(struct pasn_data *pasn, int rsn_pairwise);
+void pasn_set_rsnxe_caps(struct pasn_data *pasn, u16 rsnxe_capab);
+void pasn_set_rsnxe_ie(struct pasn_data *pasn, const u8 *rsnxe_ie);
+void pasn_set_custom_pmkid(struct pasn_data *pasn, const u8 *pmkid);
+int pasn_set_extra_ies(struct pasn_data *pasn, const u8 *extra_ies,
+		       size_t extra_ies_len);
+
+int pasn_get_akmp(struct pasn_data *pasn);
+int pasn_get_cipher(struct pasn_data *pasn);
+size_t pasn_get_pmk_len(struct pasn_data *pasn);
+u8 * pasn_get_pmk(struct pasn_data *pasn);
+struct wpa_ptk * pasn_get_ptk(struct pasn_data *pasn);
 
 #ifdef __cplusplus
 }
diff --git a/src/pasn/pasn_initiator.c b/src/pasn/pasn_initiator.c
index 35c6206..d273067 100644
--- a/src/pasn/pasn_initiator.c
+++ b/src/pasn/pasn_initiator.c
@@ -26,6 +26,14 @@
 #include "pasn_common.h"
 
 
+void pasn_set_initiator_pmksa(struct pasn_data *pasn,
+			      struct rsn_pmksa_cache *pmksa)
+{
+	if (pasn)
+		pasn->pmksa = pmksa;
+}
+
+
 #ifdef CONFIG_SAE
 
 static struct wpabuf * wpas_pasn_wd_sae_commit(struct pasn_data *pasn)
@@ -741,6 +749,11 @@
 	pasn->rsn_ie_len = 0;
 	pasn->rsnxe_ie = NULL;
 	pasn->custom_pmkid_valid = false;
+
+	if (pasn->extra_ies) {
+		os_free((u8 *) pasn->extra_ies);
+		pasn->extra_ies = NULL;
+	}
 }
 
 
diff --git a/src/pasn/pasn_responder.c b/src/pasn/pasn_responder.c
index 7501e7a..b991364 100644
--- a/src/pasn/pasn_responder.c
+++ b/src/pasn/pasn_responder.c
@@ -25,6 +25,15 @@
 #include "ap/pmksa_cache_auth.h"
 #include "pasn_common.h"
 
+
+void pasn_set_responder_pmksa(struct pasn_data *pasn,
+			      struct rsn_pmksa_cache *pmksa)
+{
+	if (pasn)
+		pasn->pmksa = pmksa;
+}
+
+
 #ifdef CONFIG_PASN
 #ifdef CONFIG_SAE
 
diff --git a/src/radius/radius_client.c b/src/radius/radius_client.c
index 18aaec1..2a7f361 100644
--- a/src/radius/radius_client.c
+++ b/src/radius/radius_client.c
@@ -1,18 +1,20 @@
 /*
  * RADIUS client
- * Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
  */
 
 #include "includes.h"
+#include <fcntl.h>
 #include <net/if.h>
 
 #include "common.h"
+#include "eloop.h"
+#include "crypto/tls.h"
 #include "radius.h"
 #include "radius_client.h"
-#include "eloop.h"
 
 /* Defaults for RADIUS retransmit values (exponential backoff) */
 
@@ -169,36 +171,36 @@
 	struct hostapd_radius_servers *conf;
 
 	/**
-	 * auth_serv_sock - IPv4 socket for RADIUS authentication messages
-	 */
-	int auth_serv_sock;
-
-	/**
-	 * acct_serv_sock - IPv4 socket for RADIUS accounting messages
-	 */
-	int acct_serv_sock;
-
-	/**
-	 * auth_serv_sock6 - IPv6 socket for RADIUS authentication messages
-	 */
-	int auth_serv_sock6;
-
-	/**
-	 * acct_serv_sock6 - IPv6 socket for RADIUS accounting messages
-	 */
-	int acct_serv_sock6;
-
-	/**
 	 * auth_sock - Currently used socket for RADIUS authentication server
 	 */
 	int auth_sock;
 
 	/**
+	 * auth_tls - Whether current authentication connection uses TLS
+	 */
+	bool auth_tls;
+
+	/**
+	 * auth_tls_ready - Whether authentication TLS is ready
+	 */
+	bool auth_tls_ready;
+
+	/**
 	 * acct_sock - Currently used socket for RADIUS accounting server
 	 */
 	int acct_sock;
 
 	/**
+	 * acct_tls - Whether current accounting connection uses TLS
+	 */
+	bool acct_tls;
+
+	/**
+	 * acct_tls_ready - Whether accounting TLS is ready
+	 */
+	bool acct_tls_ready;
+
+	/**
 	 * auth_handlers - Authentication message handlers
 	 */
 	struct radius_rx_handler *auth_handlers;
@@ -242,6 +244,12 @@
 	 * interim_error_cb_ctx - interim_error_cb() context data
 	 */
 	void *interim_error_cb_ctx;
+
+#ifdef CONFIG_RADIUS_TLS
+	void *tls_ctx;
+	struct tls_connection *auth_tls_conn;
+	struct tls_connection *acct_tls_conn;
+#endif /* CONFIG_RADIUS_TLS */
 };
 
 
@@ -249,7 +257,7 @@
 radius_change_server(struct radius_client_data *radius,
 		     struct hostapd_radius_server *nserv,
 		     struct hostapd_radius_server *oserv,
-		     int sock, int sock6, int auth);
+		     int auth);
 static int radius_client_init_acct(struct radius_client_data *radius);
 static int radius_client_init_auth(struct radius_client_data *radius);
 static void radius_client_auth_failover(struct radius_client_data *radius);
@@ -374,9 +382,19 @@
 	u8 *acct_delay_time;
 	size_t acct_delay_time_len;
 	int num_servers;
+#ifdef CONFIG_RADIUS_TLS
+	struct wpabuf *out = NULL;
+	struct tls_connection *conn = NULL;
+	bool acct = false;
+#endif /* CONFIG_RADIUS_TLS */
 
 	if (entry->msg_type == RADIUS_ACCT ||
 	    entry->msg_type == RADIUS_ACCT_INTERIM) {
+#ifdef CONFIG_RADIUS_TLS
+		acct = true;
+		if (radius->acct_tls)
+			conn = radius->acct_tls_conn;
+#endif /* CONFIG_RADIUS_TLS */
 		num_servers = conf->num_acct_servers;
 		if (radius->acct_sock < 0)
 			radius_client_init_acct(radius);
@@ -394,6 +412,10 @@
 			conf->acct_server->retransmissions++;
 		}
 	} else {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->auth_tls)
+			conn = radius->auth_tls_conn;
+#endif /* CONFIG_RADIUS_TLS */
 		num_servers = conf->num_auth_servers;
 		if (radius->auth_sock < 0)
 			radius_client_init_auth(radius);
@@ -429,6 +451,15 @@
 		return 1;
 	}
 
+#ifdef CONFIG_RADIUS_TLS
+	if ((acct && radius->acct_tls && !radius->acct_tls_ready) ||
+	    (!acct && radius->auth_tls && !radius->auth_tls_ready)) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: TLS connection not yet ready for TX");
+		goto not_ready;
+	}
+#endif /* CONFIG_RADIUS_TLS */
+
 	if (entry->msg_type == RADIUS_ACCT &&
 	    radius_msg_get_attr_ptr(entry->msg, RADIUS_ATTR_ACCT_DELAY_TIME,
 				    &acct_delay_time, &acct_delay_time_len,
@@ -473,11 +504,37 @@
 
 	os_get_reltime(&entry->last_attempt);
 	buf = radius_msg_get_buf(entry->msg);
+#ifdef CONFIG_RADIUS_TLS
+	if (conn) {
+		out = tls_connection_encrypt(radius->tls_ctx, conn, buf);
+		if (!out) {
+			wpa_printf(MSG_INFO,
+				   "RADIUS: Failed to encrypt RADIUS message (TLS)");
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: TLS encryption of %zu bytes of plaintext to %zu bytes of ciphertext",
+			   wpabuf_len(buf), wpabuf_len(out));
+		buf = out;
+	}
+#endif /* CONFIG_RADIUS_TLS */
+
+	wpa_printf(MSG_DEBUG, "RADIUS: Send %zu bytes to the server",
+		   wpabuf_len(buf));
 	if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
 		if (radius_client_handle_send_error(radius, s, entry->msg_type)
-		    > 0)
+		    > 0) {
+#ifdef CONFIG_RADIUS_TLS
+			wpabuf_free(out);
+#endif /* CONFIG_RADIUS_TLS */
 			return 0;
+		}
 	}
+#ifdef CONFIG_RADIUS_TLS
+	wpabuf_free(out);
+
+not_ready:
+#endif /* CONFIG_RADIUS_TLS */
 
 	entry->next_try = now + entry->next_wait;
 	entry->next_wait *= 2;
@@ -598,9 +655,7 @@
 	if (next > &(conf->auth_servers[conf->num_auth_servers - 1]))
 		next = conf->auth_servers;
 	conf->auth_server = next;
-	radius_change_server(radius, next, old,
-			     radius->auth_serv_sock,
-			     radius->auth_serv_sock6, 1);
+	radius_change_server(radius, next, old, 1);
 }
 
 
@@ -628,9 +683,7 @@
 	if (next > &conf->acct_servers[conf->num_acct_servers - 1])
 		next = conf->acct_servers;
 	conf->acct_server = next;
-	radius_change_server(radius, next, old,
-			     radius->acct_serv_sock,
-			     radius->acct_serv_sock6, 0);
+	radius_change_server(radius, next, old, 0);
 }
 
 
@@ -719,6 +772,52 @@
 }
 
 
+static int radius_client_disable_pmtu_discovery(int s)
+{
+	int r = -1;
+#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
+	/* Turn off Path MTU discovery on IPv4/UDP sockets. */
+	int action = IP_PMTUDISC_DONT;
+	r = setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action,
+		       sizeof(action));
+	if (r == -1)
+		wpa_printf(MSG_ERROR, "RADIUS: Failed to set IP_MTU_DISCOVER: %s",
+			   strerror(errno));
+#endif
+	return r;
+}
+
+
+static void radius_close_auth_socket(struct radius_client_data *radius)
+{
+	if (radius->auth_sock >= 0) {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->conf->auth_server->tls)
+			eloop_unregister_sock(radius->auth_sock,
+					      EVENT_TYPE_WRITE);
+#endif /* CONFIG_RADIUS_TLS */
+		eloop_unregister_read_sock(radius->auth_sock);
+		close(radius->auth_sock);
+		radius->auth_sock = -1;
+	}
+}
+
+
+static void radius_close_acct_socket(struct radius_client_data *radius)
+{
+	if (radius->acct_sock >= 0) {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->conf->acct_server->tls)
+			eloop_unregister_sock(radius->acct_sock,
+					      EVENT_TYPE_WRITE);
+#endif /* CONFIG_RADIUS_TLS */
+		eloop_unregister_read_sock(radius->acct_sock);
+		close(radius->acct_sock);
+		radius->acct_sock = -1;
+	}
+}
+
+
 /**
  * radius_client_send - Send a RADIUS request
  * @radius: RADIUS client context from radius_client_init()
@@ -754,8 +853,18 @@
 	char *name;
 	int s, res;
 	struct wpabuf *buf;
+#ifdef CONFIG_RADIUS_TLS
+	struct wpabuf *out = NULL;
+	struct tls_connection *conn = NULL;
+	bool acct = false;
+#endif /* CONFIG_RADIUS_TLS */
 
 	if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) {
+#ifdef CONFIG_RADIUS_TLS
+		acct = true;
+		if (radius->acct_tls)
+			conn = radius->acct_tls_conn;
+#endif /* CONFIG_RADIUS_TLS */
 		if (conf->acct_server && radius->acct_sock < 0)
 			radius_client_init_acct(radius);
 
@@ -774,6 +883,10 @@
 		s = radius->acct_sock;
 		conf->acct_server->requests++;
 	} else {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->auth_tls)
+			conn = radius->auth_tls_conn;
+#endif /* CONFIG_RADIUS_TLS */
 		if (conf->auth_server && radius->auth_sock < 0)
 			radius_client_init_auth(radius);
 
@@ -799,11 +912,42 @@
 	if (conf->msg_dumps)
 		radius_msg_dump(msg);
 
+#ifdef CONFIG_RADIUS_TLS
+	if ((acct && radius->acct_tls && !radius->acct_tls_ready) ||
+	    (!acct && radius->auth_tls && !radius->auth_tls_ready)) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: TLS connection not yet ready for TX");
+		goto skip_send;
+	}
+#endif /* CONFIG_RADIUS_TLS */
+
 	buf = radius_msg_get_buf(msg);
+#ifdef CONFIG_RADIUS_TLS
+	if (conn) {
+		out = tls_connection_encrypt(radius->tls_ctx, conn, buf);
+		if (!out) {
+			wpa_printf(MSG_INFO,
+				   "RADIUS: Failed to encrypt RADIUS message (TLS)");
+			return -1;
+		}
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: TLS encryption of %zu bytes of plaintext to %zu bytes of ciphertext",
+			   wpabuf_len(buf), wpabuf_len(out));
+		buf = out;
+	}
+#endif /* CONFIG_RADIUS_TLS */
+	wpa_printf(MSG_DEBUG, "RADIUS: Send %zu bytes to the server",
+		   wpabuf_len(buf));
 	res = send(s, wpabuf_head(buf), wpabuf_len(buf), 0);
+#ifdef CONFIG_RADIUS_TLS
+	wpabuf_free(out);
+#endif /* CONFIG_RADIUS_TLS */
 	if (res < 0)
 		radius_client_handle_send_error(radius, s, msg_type);
 
+#ifdef CONFIG_RADIUS_TLS
+skip_send:
+#endif /* CONFIG_RADIUS_TLS */
 	radius_client_list_add(radius, msg, msg_type, shared_secret,
 			       shared_secret_len, addr);
 
@@ -811,6 +955,137 @@
 }
 
 
+#ifdef CONFIG_RADIUS_TLS
+
+static void radius_client_close_tcp(struct radius_client_data *radius,
+				    int sock, RadiusType msg_type)
+{
+	wpa_printf(MSG_DEBUG, "RADIUS: Closing TCP connection (sock %d)",
+		   sock);
+	if (msg_type == RADIUS_ACCT) {
+		radius->acct_tls_ready = false;
+		radius_close_acct_socket(radius);
+	} else {
+		radius->auth_tls_ready = false;
+		radius_close_auth_socket(radius);
+	}
+}
+
+
+static void
+radius_client_process_tls_handshake(struct radius_client_data *radius,
+				    int sock, RadiusType msg_type,
+				    u8 *buf, size_t len)
+{
+	struct wpabuf *in, *out = NULL, *appl;
+	struct tls_connection *conn;
+	int res;
+	bool ready = false;
+
+	wpa_printf(MSG_DEBUG,
+		   "RADIUS: Process %zu bytes of received TLS handshake message",
+		   len);
+
+	if (msg_type == RADIUS_ACCT)
+		conn = radius->acct_tls_conn;
+	else
+		conn = radius->auth_tls_conn;
+
+	in = wpabuf_alloc_copy(buf, len);
+	if (!in)
+		return;
+
+	appl = NULL;
+	out = tls_connection_handshake(radius->tls_ctx, conn, in, &appl);
+	wpabuf_free(in);
+	if (!out) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: Could not generate TLS handshake data");
+		goto fail;
+	}
+
+	if (tls_connection_get_failed(radius->tls_ctx, conn)) {
+		wpa_printf(MSG_INFO, "RADIUS: TLS handshake failed");
+		goto fail;
+	}
+
+	if (tls_connection_established(radius->tls_ctx, conn)) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: TLS connection established (sock=%d)",
+			   sock);
+		if (msg_type == RADIUS_ACCT)
+			radius->acct_tls_ready = true;
+		else
+			radius->auth_tls_ready = true;
+		ready = true;
+	}
+
+	wpa_printf(MSG_DEBUG, "RADIUS: Sending %zu bytes of TLS handshake",
+		   wpabuf_len(out));
+	res = send(sock, wpabuf_head(out), wpabuf_len(out), 0);
+	if (res < 0) {
+		wpa_printf(MSG_INFO, "RADIUS: send: %s", strerror(errno));
+		goto fail;
+	}
+	if ((size_t) res != wpabuf_len(out)) {
+		wpa_printf(MSG_INFO,
+			   "RADIUS: Could not send all data for TLS handshake: only %d bytes sent",
+			   res);
+		goto fail;
+	}
+	wpabuf_free(out);
+
+	if (ready) {
+		struct radius_msg_list *entry, *prev, *tmp;
+		struct os_reltime now;
+
+		/* Send all pending message of matching type since the TLS
+		 * tunnel has now been established. */
+
+		os_get_reltime(&now);
+
+		entry = radius->msgs;
+		prev = NULL;
+		while (entry) {
+			if (entry->msg_type != msg_type) {
+				prev = entry;
+				entry = entry->next;
+				continue;
+			}
+
+			if (radius_client_retransmit(radius, entry, now.sec)) {
+				if (prev)
+					prev->next = entry->next;
+				else
+					radius->msgs = entry->next;
+
+				tmp = entry;
+				entry = entry->next;
+				radius_client_msg_free(tmp);
+				radius->num_msgs--;
+				continue;
+			}
+
+			prev = entry;
+			entry = entry->next;
+		}
+	}
+
+	return;
+
+fail:
+	wpabuf_free(out);
+	tls_connection_deinit(radius->tls_ctx, conn);
+	if (msg_type == RADIUS_ACCT)
+		radius->acct_tls_conn = NULL;
+	else
+		radius->auth_tls_conn = NULL;
+	radius_client_close_tcp(radius, sock, msg_type);
+}
+
+#endif /* CONFIG_RADIUS_TLS */
+
+
 static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx)
 {
 	struct radius_client_data *radius = eloop_ctx;
@@ -828,12 +1103,28 @@
 	struct os_reltime now;
 	struct hostapd_radius_server *rconf;
 	int invalid_authenticator = 0;
+#ifdef CONFIG_RADIUS_TLS
+	struct tls_connection *conn = NULL;
+	bool tls, tls_ready;
+#endif /* CONFIG_RADIUS_TLS */
 
 	if (msg_type == RADIUS_ACCT) {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->acct_tls)
+			conn = radius->acct_tls_conn;
+		tls = radius->acct_tls;
+		tls_ready = radius->acct_tls_ready;
+#endif /* CONFIG_RADIUS_TLS */
 		handlers = radius->acct_handlers;
 		num_handlers = radius->num_acct_handlers;
 		rconf = conf->acct_server;
 	} else {
+#ifdef CONFIG_RADIUS_TLS
+		if (radius->auth_tls)
+			conn = radius->auth_tls_conn;
+		tls = radius->auth_tls;
+		tls_ready = radius->auth_tls_ready;
+#endif /* CONFIG_RADIUS_TLS */
 		handlers = radius->auth_handlers;
 		num_handlers = radius->num_auth_handlers;
 		rconf = conf->auth_server;
@@ -849,6 +1140,52 @@
 		wpa_printf(MSG_INFO, "recvmsg[RADIUS]: %s", strerror(errno));
 		return;
 	}
+#ifdef CONFIG_RADIUS_TLS
+	if (tls && len == 0) {
+		wpa_printf(MSG_DEBUG, "RADIUS: No TCP data available");
+		goto close_tcp;
+	}
+
+	if (tls && !tls_ready) {
+		radius_client_process_tls_handshake(radius, sock, msg_type,
+						    buf, len);
+		return;
+	}
+
+	if (conn) {
+		struct wpabuf *out, *in;
+
+		in = wpabuf_alloc_copy(buf, len);
+		if (!in)
+			return;
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: Process %d bytes of encrypted TLS data",
+			   len);
+		out = tls_connection_decrypt(radius->tls_ctx, conn, in);
+		wpabuf_free(in);
+		if (!out) {
+			wpa_printf(MSG_INFO,
+				   "RADIUS: Failed to decrypt TLS data");
+			goto close_tcp;
+		}
+		if (wpabuf_len(out) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "RADIUS: Full message not yet received - continue waiting for additional TLS data");
+			wpabuf_free(out);
+			return;
+		}
+		if (wpabuf_len(out) > RADIUS_MAX_MSG_LEN) {
+			wpa_printf(MSG_INFO,
+				   "RADIUS: Too long RADIUS message from TLS: %zu",
+				   wpabuf_len(out));
+			wpabuf_free(out);
+			goto close_tcp;
+		}
+		os_memcpy(buf, wpabuf_head(out), wpabuf_len(out));
+		len = wpabuf_len(out);
+		wpabuf_free(out);
+	}
+#endif /* CONFIG_RADIUS_TLS */
 
 	hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
 		       HOSTAPD_LEVEL_DEBUG, "Received %d bytes from RADIUS "
@@ -964,9 +1301,121 @@
 
  fail:
 	radius_msg_free(msg);
+	return;
+
+#ifdef CONFIG_RADIUS_TLS
+close_tcp:
+	radius_client_close_tcp(radius, sock, msg_type);
+#endif /* CONFIG_RADIUS_TLS */
 }
 
 
+#ifdef CONFIG_RADIUS_TLS
+static void radius_client_write_ready(int sock, void *eloop_ctx, void *sock_ctx)
+{
+	struct radius_client_data *radius = eloop_ctx;
+	RadiusType msg_type = (uintptr_t) sock_ctx;
+	struct tls_connection *conn = NULL;
+	struct wpabuf *in, *out = NULL, *appl;
+	int res = -1;
+	struct tls_connection_params params;
+	struct hostapd_radius_server *server;
+
+	wpa_printf(MSG_DEBUG, "RADIUS: TCP connection established - start TLS handshake (sock=%d)",
+		   sock);
+
+	if (msg_type == RADIUS_ACCT) {
+		eloop_unregister_sock(sock, EVENT_TYPE_WRITE);
+		eloop_register_read_sock(sock, radius_client_receive, radius,
+					 (void *) RADIUS_ACCT);
+		if (radius->acct_tls_conn) {
+			wpa_printf(MSG_DEBUG,
+				   "RADIUS: Deinit previously used TLS connection");
+			tls_connection_deinit(radius->tls_ctx,
+					      radius->acct_tls_conn);
+			radius->acct_tls_conn = NULL;
+		}
+		server = radius->conf->acct_server;
+	} else {
+		eloop_unregister_sock(sock, EVENT_TYPE_WRITE);
+		eloop_register_read_sock(sock, radius_client_receive, radius,
+					 (void *) RADIUS_AUTH);
+		if (radius->auth_tls_conn) {
+			wpa_printf(MSG_DEBUG,
+				   "RADIUS: Deinit previously used TLS connection");
+			tls_connection_deinit(radius->tls_ctx,
+					      radius->auth_tls_conn);
+			radius->auth_tls_conn = NULL;
+		}
+		server = radius->conf->auth_server;
+	}
+
+	if (!server)
+		goto fail;
+
+	conn = tls_connection_init(radius->tls_ctx);
+	if (!conn) {
+		wpa_printf(MSG_INFO,
+			   "RADIUS: Failed to initiate TLS connection");
+		goto fail;
+	}
+
+	os_memset(&params, 0, sizeof(params));
+	params.ca_cert = server->ca_cert;
+	params.client_cert = server->client_cert;
+	params.private_key = server->private_key;
+	params.private_key_passwd = server->private_key_passwd;
+	params.flags = TLS_CONN_DISABLE_TLSv1_0 | TLS_CONN_DISABLE_TLSv1_1;
+	if (tls_connection_set_params(radius->tls_ctx, conn, &params)) {
+		wpa_printf(MSG_INFO,
+			   "RADIUS: Failed to set TLS connection parameters");
+		goto fail;
+	}
+
+	in = NULL;
+	appl = NULL;
+	out = tls_connection_handshake(radius->tls_ctx, conn, in, &appl);
+	if (!out) {
+		wpa_printf(MSG_DEBUG,
+			   "RADIUS: Could not generate TLS handshake data");
+		goto fail;
+	}
+
+	if (tls_connection_get_failed(radius->tls_ctx, conn)) {
+		wpa_printf(MSG_INFO, "RADIUS: TLS handshake failed");
+		goto fail;
+	}
+
+	wpa_printf(MSG_DEBUG, "RADIUS: Sending %zu bytes of TLS handshake",
+		   wpabuf_len(out));
+	res = send(sock, wpabuf_head(out), wpabuf_len(out), 0);
+	if (res < 0) {
+		wpa_printf(MSG_INFO, "RADIUS: send: %s", strerror(errno));
+		goto fail;
+	}
+	if ((size_t) res != wpabuf_len(out)) {
+		wpa_printf(MSG_INFO,
+			   "RADIUS: Could not send all data for TLS handshake: only %d bytes sent",
+			   res);
+		goto fail;
+	}
+	wpabuf_free(out);
+
+	if (msg_type == RADIUS_ACCT)
+		radius->acct_tls_conn = conn;
+	else
+		radius->auth_tls_conn = conn;
+	return;
+
+fail:
+	wpa_printf(MSG_INFO, "RADIUS: Failed to perform TLS handshake");
+	tls_connection_deinit(radius->tls_ctx, conn);
+	wpabuf_free(out);
+	radius_client_close_tcp(radius, sock, msg_type);
+}
+#endif /* CONFIG_RADIUS_TLS */
+
+
 /**
  * radius_client_get_id - Get an identifier for a new RADIUS message
  * @radius: RADIUS client context from radius_client_init()
@@ -1071,7 +1520,7 @@
 radius_change_server(struct radius_client_data *radius,
 		     struct hostapd_radius_server *nserv,
 		     struct hostapd_radius_server *oserv,
-		     int sock, int sock6, int auth)
+		     int auth)
 {
 	struct sockaddr_in serv, claddr;
 #ifdef CONFIG_IPV6
@@ -1083,9 +1532,17 @@
 	int sel_sock;
 	struct radius_msg_list *entry;
 	struct hostapd_radius_servers *conf = radius->conf;
-	struct sockaddr_in disconnect_addr = {
-		.sin_family = AF_UNSPEC,
-	};
+	int type = SOCK_DGRAM;
+	bool tls = nserv->tls;
+
+	if (tls) {
+#ifdef CONFIG_RADIUS_TLS
+		type = SOCK_STREAM;
+#else /* CONFIG_RADIUS_TLS */
+		wpa_printf(MSG_ERROR, "RADIUS: TLS not supported");
+		return -1;
+#endif /* CONFIG_RADIUS_TLS */
+	}
 
 	hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS,
 		       HOSTAPD_LEVEL_INFO,
@@ -1144,7 +1601,9 @@
 		serv.sin_port = htons(nserv->port);
 		addr = (struct sockaddr *) &serv;
 		addrlen = sizeof(serv);
-		sel_sock = sock;
+		sel_sock = socket(PF_INET, type, 0);
+		if (sel_sock >= 0)
+			radius_client_disable_pmtu_discovery(sel_sock);
 		break;
 #ifdef CONFIG_IPV6
 	case AF_INET6:
@@ -1155,7 +1614,7 @@
 		serv6.sin6_port = htons(nserv->port);
 		addr = (struct sockaddr *) &serv6;
 		addrlen = sizeof(serv6);
-		sel_sock = sock6;
+		sel_sock = socket(PF_INET6, type, 0);
 		break;
 #endif /* CONFIG_IPV6 */
 	default:
@@ -1164,15 +1623,19 @@
 
 	if (sel_sock < 0) {
 		wpa_printf(MSG_INFO,
-			   "RADIUS: No server socket available (af=%d sock=%d sock6=%d auth=%d",
-			   nserv->addr.af, sock, sock6, auth);
+			   "RADIUS: Failed to open server socket (af=%d auth=%d)",
+			   nserv->addr.af, auth);
 		return -1;
 	}
 
-	/* Force a reconnect by disconnecting the socket first */
-	if (connect(sel_sock, (struct sockaddr *) &disconnect_addr,
-		    sizeof(disconnect_addr)) < 0)
-		wpa_printf(MSG_INFO, "disconnect[radius]: %s", strerror(errno));
+#ifdef CONFIG_RADIUS_TLS
+	if (tls && fcntl(sel_sock, F_SETFL, O_NONBLOCK) != 0) {
+		wpa_printf(MSG_DEBUG, "RADIUS: fnctl(O_NONBLOCK) failed: %s",
+			   strerror(errno));
+		close(sel_sock);
+		return -1;
+	}
+#endif /* CONFIG_RADIUS_TLS */
 
 #ifdef __linux__
 	if (conf->force_client_dev && conf->force_client_dev[0]) {
@@ -1214,19 +1677,29 @@
 			break;
 #endif /* CONFIG_IPV6 */
 		default:
+			close(sel_sock);
 			return -1;
 		}
 
 		if (bind(sel_sock, cl_addr, claddrlen) < 0) {
 			wpa_printf(MSG_INFO, "bind[radius]: %s",
 				   strerror(errno));
-			return -1;
+			close(sel_sock);
+			return -2;
 		}
 	}
 
 	if (connect(sel_sock, addr, addrlen) < 0) {
-		wpa_printf(MSG_INFO, "connect[radius]: %s", strerror(errno));
-		return -1;
+		if (nserv->tls && errno == EINPROGRESS) {
+			wpa_printf(MSG_DEBUG,
+				   "RADIUS: TCP connection establishment in progress (sock %d)",
+				   sel_sock);
+		} else {
+			wpa_printf(MSG_INFO, "connect[radius]: %s",
+				   strerror(errno));
+			close(sel_sock);
+			return -2;
+		}
 	}
 
 #ifndef CONFIG_NATIVE_WINDOWS
@@ -1256,10 +1729,34 @@
 	}
 #endif /* CONFIG_NATIVE_WINDOWS */
 
-	if (auth)
+	if (auth) {
+		radius_close_auth_socket(radius);
 		radius->auth_sock = sel_sock;
-	else
+	} else {
+		radius_close_acct_socket(radius);
 		radius->acct_sock = sel_sock;
+	}
+
+	if (!tls)
+		eloop_register_read_sock(sel_sock, radius_client_receive,
+					 radius,
+					 auth ? (void *) RADIUS_AUTH :
+					 (void *) RADIUS_ACCT);
+#ifdef CONFIG_RADIUS_TLS
+	if (tls)
+		eloop_register_sock(sel_sock, EVENT_TYPE_WRITE,
+				    radius_client_write_ready, radius,
+				    auth ? (void *) RADIUS_AUTH :
+				    (void *) RADIUS_ACCT);
+#endif /* CONFIG_RADIUS_TLS */
+
+	if (auth) {
+		radius->auth_tls = nserv->tls;
+		radius->auth_tls_ready = false;
+	} else {
+		radius->acct_tls = nserv->tls;
+		radius->acct_tls_ready = false;
+	}
 
 	return 0;
 }
@@ -1276,12 +1773,10 @@
 		oserv = conf->auth_server;
 		conf->auth_server = conf->auth_servers;
 		if (radius_change_server(radius, conf->auth_server, oserv,
-					 radius->auth_serv_sock,
-					 radius->auth_serv_sock6, 1) < 0) {
+					 1) < 0) {
 			conf->auth_server = oserv;
 			radius_change_server(radius, oserv, conf->auth_server,
-					     radius->auth_serv_sock,
-					     radius->auth_serv_sock6, 1);
+					     1);
 		}
 	}
 
@@ -1290,12 +1785,10 @@
 		oserv = conf->acct_server;
 		conf->acct_server = conf->acct_servers;
 		if (radius_change_server(radius, conf->acct_server, oserv,
-					 radius->acct_serv_sock,
-					 radius->acct_serv_sock6, 0) < 0) {
+					 0) < 0) {
 			conf->acct_server = oserv;
 			radius_change_server(radius, oserv, conf->acct_server,
-					     radius->acct_serv_sock,
-					     radius->acct_serv_sock6, 0);
+					     0);
 		}
 	}
 
@@ -1306,172 +1799,29 @@
 }
 
 
-static int radius_client_disable_pmtu_discovery(int s)
-{
-	int r = -1;
-#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
-	/* Turn off Path MTU discovery on IPv4/UDP sockets. */
-	int action = IP_PMTUDISC_DONT;
-	r = setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action,
-		       sizeof(action));
-	if (r == -1)
-		wpa_printf(MSG_ERROR, "RADIUS: Failed to set IP_MTU_DISCOVER: %s",
-			   strerror(errno));
-#endif
-	return r;
-}
-
-
-static void radius_close_auth_sockets(struct radius_client_data *radius)
-{
-	radius->auth_sock = -1;
-
-	if (radius->auth_serv_sock >= 0) {
-		eloop_unregister_read_sock(radius->auth_serv_sock);
-		close(radius->auth_serv_sock);
-		radius->auth_serv_sock = -1;
-	}
-#ifdef CONFIG_IPV6
-	if (radius->auth_serv_sock6 >= 0) {
-		eloop_unregister_read_sock(radius->auth_serv_sock6);
-		close(radius->auth_serv_sock6);
-		radius->auth_serv_sock6 = -1;
-	}
-#endif /* CONFIG_IPV6 */
-}
-
-
-static void radius_close_acct_sockets(struct radius_client_data *radius)
-{
-	radius->acct_sock = -1;
-
-	if (radius->acct_serv_sock >= 0) {
-		eloop_unregister_read_sock(radius->acct_serv_sock);
-		close(radius->acct_serv_sock);
-		radius->acct_serv_sock = -1;
-	}
-#ifdef CONFIG_IPV6
-	if (radius->acct_serv_sock6 >= 0) {
-		eloop_unregister_read_sock(radius->acct_serv_sock6);
-		close(radius->acct_serv_sock6);
-		radius->acct_serv_sock6 = -1;
-	}
-#endif /* CONFIG_IPV6 */
-}
-
-
 static int radius_client_init_auth(struct radius_client_data *radius)
 {
-	struct hostapd_radius_servers *conf = radius->conf;
-	int ok = 0;
-
-	radius_close_auth_sockets(radius);
-
-	radius->auth_serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
-	if (radius->auth_serv_sock < 0)
-		wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
-			   strerror(errno));
-	else {
-		radius_client_disable_pmtu_discovery(radius->auth_serv_sock);
-		ok++;
-	}
-
-#ifdef CONFIG_IPV6
-	radius->auth_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
-	if (radius->auth_serv_sock6 < 0)
-		wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
-			   strerror(errno));
-	else
-		ok++;
-#endif /* CONFIG_IPV6 */
-
-	if (ok == 0)
-		return -1;
-
-	radius_change_server(radius, conf->auth_server, NULL,
-			     radius->auth_serv_sock, radius->auth_serv_sock6,
-			     1);
-
-	if (radius->auth_serv_sock >= 0 &&
-	    eloop_register_read_sock(radius->auth_serv_sock,
-				     radius_client_receive, radius,
-				     (void *) RADIUS_AUTH)) {
-		wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
-		radius_close_auth_sockets(radius);
-		return -1;
-	}
-
-#ifdef CONFIG_IPV6
-	if (radius->auth_serv_sock6 >= 0 &&
-	    eloop_register_read_sock(radius->auth_serv_sock6,
-				     radius_client_receive, radius,
-				     (void *) RADIUS_AUTH)) {
-		wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server");
-		radius_close_auth_sockets(radius);
-		return -1;
-	}
-#endif /* CONFIG_IPV6 */
-
-	return 0;
+	radius_close_auth_socket(radius);
+	return radius_change_server(radius, radius->conf->auth_server, NULL, 1);
 }
 
 
 static int radius_client_init_acct(struct radius_client_data *radius)
 {
-	struct hostapd_radius_servers *conf = radius->conf;
-	int ok = 0;
-
-	radius_close_acct_sockets(radius);
-
-	radius->acct_serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
-	if (radius->acct_serv_sock < 0)
-		wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s",
-			   strerror(errno));
-	else {
-		radius_client_disable_pmtu_discovery(radius->acct_serv_sock);
-		ok++;
-	}
-
-#ifdef CONFIG_IPV6
-	radius->acct_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0);
-	if (radius->acct_serv_sock6 < 0)
-		wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s",
-			   strerror(errno));
-	else
-		ok++;
-#endif /* CONFIG_IPV6 */
-
-	if (ok == 0)
-		return -1;
-
-	radius_change_server(radius, conf->acct_server, NULL,
-			     radius->acct_serv_sock, radius->acct_serv_sock6,
-			     0);
-
-	if (radius->acct_serv_sock >= 0 &&
-	    eloop_register_read_sock(radius->acct_serv_sock,
-				     radius_client_receive, radius,
-				     (void *) RADIUS_ACCT)) {
-		wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
-		radius_close_acct_sockets(radius);
-		return -1;
-	}
-
-#ifdef CONFIG_IPV6
-	if (radius->acct_serv_sock6 >= 0 &&
-	    eloop_register_read_sock(radius->acct_serv_sock6,
-				     radius_client_receive, radius,
-				     (void *) RADIUS_ACCT)) {
-		wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server");
-		radius_close_acct_sockets(radius);
-		return -1;
-	}
-#endif /* CONFIG_IPV6 */
-
-	return 0;
+	radius_close_acct_socket(radius);
+	return radius_change_server(radius, radius->conf->acct_server, NULL, 0);
 }
 
 
+#ifdef CONFIG_RADIUS_TLS
+static void radius_tls_event_cb(void *ctx, enum tls_event ev,
+				union tls_event_data *data)
+{
+	wpa_printf(MSG_DEBUG, "RADIUS: TLS event %d", ev);
+}
+#endif /* CONFIG_RADIUS_TLS */
+
+
 /**
  * radius_client_init - Initialize RADIUS client
  * @ctx: Callback context to be used in hostapd_logger() calls
@@ -1493,16 +1843,14 @@
 
 	radius->ctx = ctx;
 	radius->conf = conf;
-	radius->auth_serv_sock = radius->acct_serv_sock =
-		radius->auth_serv_sock6 = radius->acct_serv_sock6 =
-		radius->auth_sock = radius->acct_sock = -1;
+	radius->auth_sock = radius->acct_sock = -1;
 
-	if (conf->auth_server && radius_client_init_auth(radius)) {
+	if (conf->auth_server && radius_client_init_auth(radius) == -1) {
 		radius_client_deinit(radius);
 		return NULL;
 	}
 
-	if (conf->acct_server && radius_client_init_acct(radius)) {
+	if (conf->acct_server && radius_client_init_acct(radius) == -1) {
 		radius_client_deinit(radius);
 		return NULL;
 	}
@@ -1512,6 +1860,22 @@
 				       radius_retry_primary_timer, radius,
 				       NULL);
 
+#ifdef CONFIG_RADIUS_TLS
+	if ((conf->auth_server && conf->auth_server->tls) ||
+	    (conf->acct_server && conf->acct_server->tls)) {
+		struct tls_config tls_conf;
+
+		os_memset(&tls_conf, 0, sizeof(tls_conf));
+		tls_conf.event_cb = radius_tls_event_cb;
+		radius->tls_ctx = tls_init(&tls_conf);
+		if (!radius->tls_ctx) {
+			radius_client_deinit(radius);
+			return NULL;
+		}
+	}
+#endif /* CONFIG_RADIUS_TLS */
+
+
 	return radius;
 }
 
@@ -1525,14 +1889,21 @@
 	if (!radius)
 		return;
 
-	radius_close_auth_sockets(radius);
-	radius_close_acct_sockets(radius);
+	radius_close_auth_socket(radius);
+	radius_close_acct_socket(radius);
 
 	eloop_cancel_timeout(radius_retry_primary_timer, radius, NULL);
 
 	radius_client_flush(radius, 0);
 	os_free(radius->auth_handlers);
 	os_free(radius->acct_handlers);
+#ifdef CONFIG_RADIUS_TLS
+	if (radius->tls_ctx) {
+		tls_connection_deinit(radius->tls_ctx, radius->auth_tls_conn);
+		tls_connection_deinit(radius->tls_ctx, radius->acct_tls_conn);
+		tls_deinit(radius->tls_ctx);
+	}
+#endif /* CONFIG_RADIUS_TLS */
 	os_free(radius);
 }
 
diff --git a/src/radius/radius_client.h b/src/radius/radius_client.h
index 687cd81..db40637 100644
--- a/src/radius/radius_client.h
+++ b/src/radius/radius_client.h
@@ -1,6 +1,6 @@
 /*
  * RADIUS client
- * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -36,6 +36,11 @@
 	int port;
 
 	/**
+	 * tls - Whether to use RADIUS/TLS instead of RADIUS/UDP
+	 */
+	bool tls;
+
+	/**
 	 * shared_secret - Shared secret for authenticating RADIUS messages
 	 */
 	u8 *shared_secret;
@@ -45,6 +50,26 @@
 	 */
 	size_t shared_secret_len;
 
+	/**
+	 * ca_cert - Path to trusted CA certificate(s) for RADIUS/TLS
+	 */
+	char *ca_cert;
+
+	/**
+	 * client_cert - Path to client certificate for RADIUS/TLS
+	 */
+	char *client_cert;
+
+	/**
+	 * private_key - Path to clienbt private key for RADIUS/TLS
+	 */
+	char *private_key;
+
+	/**
+	 * private_key_passwd - Password for the private key for RADIUS/TLS
+	 */
+	char *private_key_passwd;
+
 	/* Dynamic (not from configuration file) MIB data */
 
 	/**
diff --git a/src/rsn_supp/pmksa_cache.c b/src/rsn_supp/pmksa_cache.c
index e243756..a402cb6 100644
--- a/src/rsn_supp/pmksa_cache.c
+++ b/src/rsn_supp/pmksa_cache.c
@@ -857,4 +857,99 @@
 	}
 }
 
+#else /* IEEE8021X_EAPOL */
+
+struct rsn_pmksa_cache *
+pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
+				 void *ctx, enum pmksa_free_reason reason),
+		 bool (*is_current_cb)(struct rsn_pmksa_cache_entry *entry,
+				       void *ctx),
+		 void (*notify_cb)(struct rsn_pmksa_cache_entry *entry,
+				   void *ctx),
+		 void *ctx, struct wpa_sm *sm)
+{
+	return (void *) -1;
+}
+
+
+void pmksa_cache_deinit(struct rsn_pmksa_cache *pmksa)
+{
+}
+
+
+struct rsn_pmksa_cache_entry *
+pmksa_cache_get(struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa,
+		const u8 *pmkid, const void *network_ctx, int akmp)
+{
+	return NULL;
+}
+
+
+struct rsn_pmksa_cache_entry *
+pmksa_cache_get_current(struct wpa_sm *sm)
+{
+	return NULL;
+}
+
+
+int pmksa_cache_list(struct rsn_pmksa_cache *pmksa, char *buf, size_t len)
+{
+	return -1;
+}
+
+
+struct rsn_pmksa_cache_entry *
+pmksa_cache_head(struct rsn_pmksa_cache *pmksa)
+{
+	return NULL;
+}
+
+
+struct rsn_pmksa_cache_entry *
+pmksa_cache_add_entry(struct rsn_pmksa_cache *pmksa,
+		      struct rsn_pmksa_cache_entry *entry)
+{
+	return NULL;
+}
+
+
+struct rsn_pmksa_cache_entry *
+pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
+		const u8 *pmkid, const u8 *kck, size_t kck_len,
+		const u8 *aa, const u8 *spa, void *network_ctx, int akmp,
+		const u8 *cache_id)
+{
+	return NULL;
+}
+
+
+void pmksa_cache_clear_current(struct wpa_sm *sm)
+{
+}
+
+
+int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid, const u8 *bssid,
+			    void *network_ctx, int try_opportunistic,
+			    const u8 *fils_cache_id, int akmp, bool associated)
+{
+	return -1;
+}
+
+
+void pmksa_cache_flush(struct rsn_pmksa_cache *pmksa, void *network_ctx,
+		       const u8 *pmk, size_t pmk_len, bool external_only)
+{
+}
+
+
+void pmksa_cache_remove(struct rsn_pmksa_cache *pmksa,
+			struct rsn_pmksa_cache_entry *entry)
+{
+}
+
+
+void pmksa_cache_reconfig(struct rsn_pmksa_cache *pmksa)
+{
+}
+
 #endif /* IEEE8021X_EAPOL */
diff --git a/src/rsn_supp/pmksa_cache.h b/src/rsn_supp/pmksa_cache.h
index 6ba48f7..b1203ad 100644
--- a/src/rsn_supp/pmksa_cache.h
+++ b/src/rsn_supp/pmksa_cache.h
@@ -64,8 +64,6 @@
 	PMKSA_EXPIRE,
 };
 
-#if defined(IEEE8021X_EAPOL) && !defined(CONFIG_NO_WPA)
-
 struct rsn_pmksa_cache *
 pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
 				 void *ctx, enum pmksa_free_reason reason),
@@ -105,95 +103,4 @@
 			struct rsn_pmksa_cache_entry *entry);
 void pmksa_cache_reconfig(struct rsn_pmksa_cache *pmksa);
 
-#else /* IEEE8021X_EAPOL */
-
-static inline struct rsn_pmksa_cache *
-pmksa_cache_init(void (*free_cb)(struct rsn_pmksa_cache_entry *entry,
-				 void *ctx, enum pmksa_free_reason reason),
-		 bool (*is_current_cb)(struct rsn_pmksa_cache_entry *entry,
-				       void *ctx),
-		 void (*notify_cb)(struct rsn_pmksa_cache_entry *entry,
-				   void *ctx),
-		 void *ctx, struct wpa_sm *sm)
-{
-	return (void *) -1;
-}
-
-static inline void pmksa_cache_deinit(struct rsn_pmksa_cache *pmksa)
-{
-}
-
-static inline struct rsn_pmksa_cache_entry *
-pmksa_cache_get(struct rsn_pmksa_cache *pmksa, const u8 *aa, const u8 *spa,
-		const u8 *pmkid, const void *network_ctx, int akmp)
-{
-	return NULL;
-}
-
-static inline struct rsn_pmksa_cache_entry *
-pmksa_cache_get_current(struct wpa_sm *sm)
-{
-	return NULL;
-}
-
-static inline int pmksa_cache_list(struct rsn_pmksa_cache *pmksa, char *buf,
-				   size_t len)
-{
-	return -1;
-}
-
-static inline struct rsn_pmksa_cache_entry *
-pmksa_cache_head(struct rsn_pmksa_cache *pmksa)
-{
-	return NULL;
-}
-
-static inline struct rsn_pmksa_cache_entry *
-pmksa_cache_add_entry(struct rsn_pmksa_cache *pmksa,
-		      struct rsn_pmksa_cache_entry *entry)
-{
-	return NULL;
-}
-
-static inline struct rsn_pmksa_cache_entry *
-pmksa_cache_add(struct rsn_pmksa_cache *pmksa, const u8 *pmk, size_t pmk_len,
-		const u8 *pmkid, const u8 *kck, size_t kck_len,
-		const u8 *aa, const u8 *spa, void *network_ctx, int akmp,
-		const u8 *cache_id)
-{
-	return NULL;
-}
-
-static inline void pmksa_cache_clear_current(struct wpa_sm *sm)
-{
-}
-
-static inline int pmksa_cache_set_current(struct wpa_sm *sm, const u8 *pmkid,
-					  const u8 *bssid,
-					  void *network_ctx,
-					  int try_opportunistic,
-					  const u8 *fils_cache_id,
-					  int akmp, bool associated)
-{
-	return -1;
-}
-
-static inline void pmksa_cache_flush(struct rsn_pmksa_cache *pmksa,
-				     void *network_ctx,
-				     const u8 *pmk, size_t pmk_len,
-				     bool external_only)
-{
-}
-
-static inline void pmksa_cache_remove(struct rsn_pmksa_cache *pmksa,
-				      struct rsn_pmksa_cache_entry *entry)
-{
-}
-
-static inline void pmksa_cache_reconfig(struct rsn_pmksa_cache *pmksa)
-{
-}
-
-#endif /* IEEE8021X_EAPOL */
-
 #endif /* PMKSA_CACHE_H */
diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c
index 8a75091..65960b7 100644
--- a/src/rsn_supp/tdls.c
+++ b/src/rsn_supp/tdls.c
@@ -161,6 +161,8 @@
 	int chan_switch_enabled;
 
 	int mld_link_id;
+	bool disc_resp_rcvd;
+	bool setup_req_rcvd;
 };
 
 
@@ -1569,9 +1571,8 @@
 	} else {
 		int i;
 
-		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-			if ((sm->mlo.valid_links & BIT(i)) &&
-			    ether_addr_equal(lnkid->bssid,
+		for_each_link(sm->mlo.valid_links, i) {
+			if (ether_addr_equal(lnkid->bssid,
 					     sm->mlo.links[i].bssid)) {
 				*link_id = i;
 				break;
@@ -2868,6 +2869,12 @@
 		return 0;
 	}
 
+	if (sm->mlo.valid_links && !peer->disc_resp_rcvd) {
+		wpa_printf(MSG_DEBUG,
+			   "TDLS: MLO STA connection - defer the setup request since Discovery Resp not yet received");
+		peer->setup_req_rcvd = true;
+		return 0;
+	}
 	peer->initiator = 1;
 
 	/* add the peer to the driver as a "setup in progress" peer */
@@ -3236,6 +3243,13 @@
 	wpa_printf(MSG_DEBUG, "TDLS: Link identifier BSS: " MACSTR
 		   " , link id: %u", MAC2STR(lnkid->bssid), link_id);
 
+	peer->disc_resp_rcvd = true;
+	if (peer->setup_req_rcvd) {
+		peer->setup_req_rcvd = false;
+		wpa_printf(MSG_DEBUG, "TDLS: Process the deferred TDLS start");
+		return wpa_tdls_start(sm, addr);
+	}
+
 	return 0;
 }
 
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 3eaa015..f5e24f2 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -801,8 +801,8 @@
 	int i;
 	unsigned int num_links = 0;
 
-	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
-		if (sm->mlo.assoc_link_id != i && (sm->mlo.req_links & BIT(i)))
+	for_each_link(sm->mlo.req_links, i) {
+		if (sm->mlo.assoc_link_id != i)
 			num_links++;
 	}
 
@@ -815,8 +815,8 @@
 	int i;
 	u8 hdr[1 + ETH_ALEN];
 
-	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
-		if (sm->mlo.assoc_link_id == i || !(sm->mlo.req_links & BIT(i)))
+	for_each_link(sm->mlo.req_links, i) {
+		if (sm->mlo.assoc_link_id == i)
 			continue;
 
 		wpa_printf(MSG_DEBUG,
@@ -1551,10 +1551,7 @@
 {
 	u8 i;
 
-	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
-		if (!(sm->mlo.valid_links & BIT(i)))
-			continue;
-
+	for_each_link(sm->mlo.valid_links, i) {
 		if (!ie->mlo_gtk[i]) {
 			wpa_msg(sm->ctx->msg_ctx, MSG_ERROR,
 				"MLO RSN: GTK not found for link ID %u", i);
@@ -1903,10 +1900,7 @@
 	    sm->mgmt_group_cipher == WPA_CIPHER_GTK_NOT_USED)
 		return 0;
 
-	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
-		if (!(sm->mlo.valid_links & BIT(i)))
-			continue;
-
+	for_each_link(sm->mlo.valid_links, i) {
 		if (_mlo_ieee80211w_set_keys(sm, i, ie))
 			return -1;
 	}
@@ -2932,10 +2926,7 @@
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"MLO RSN: Failed to configure MLO IGTK");
 
-	for (i = 0; i < MAX_NUM_MLO_LINKS; i++) {
-		if (!(sm->mlo.valid_links & BIT(i)))
-			continue;
-
+	for_each_link(sm->mlo.valid_links, i) {
 		/*
 		 * AP may send group keys for subset of the all links during
 		 * rekey
diff --git a/src/utils/common.h b/src/utils/common.h
index 079f90e..14c90c9 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -600,6 +600,18 @@
 u8 rssi_to_rcpi(int rssi);
 char * get_param(const char *cmd, const char *param);
 
+#define for_each_link(__links, __i)                            \
+	for ((__i) = 0; (__i) < MAX_NUM_MLD_LINKS; (__i)++)    \
+		if ((__links) & BIT(__i))
+
+/* Iterate all links, or, if no link is defined, iterate given index */
+#define for_each_link_default(_links, _i, _def_idx)	\
+	for ((_i) = (_links) ? 0 : (_def_idx);		\
+	     (_i) < MAX_NUM_MLD_LINKS ||		\
+		     (!(_links) && (_i) == (_def_idx));	\
+	     (_i)++)					\
+		if (!(_links) || (_links) & BIT(_i))
+
 void forced_memzero(void *ptr, size_t len);
 
 /*
diff --git a/src/wps/wps.c b/src/wps/wps.c
index 7cfebfa..fd5bd93 100644
--- a/src/wps/wps.c
+++ b/src/wps/wps.c
@@ -146,6 +146,7 @@
 	}
 
 	data->multi_ap_backhaul_sta = cfg->multi_ap_backhaul_sta;
+	data->multi_ap_profile = cfg->multi_ap_profile;
 
 	return data;
 }
diff --git a/src/wps/wps.h b/src/wps/wps.h
index fed3e28..b99ae94 100644
--- a/src/wps/wps.h
+++ b/src/wps/wps.h
@@ -195,6 +195,11 @@
 	 * enrollee
 	 */
 	int multi_ap_backhaul_sta;
+
+	/*
+	 * multi_ap_profile - Get the Multi-AP Profile
+	 */
+	int multi_ap_profile;
 };
 
 struct wps_data * wps_init(const struct wps_config *cfg);
diff --git a/src/wps/wps_i.h b/src/wps/wps_i.h
index 2cf22d4..5486e2a 100644
--- a/src/wps/wps_i.h
+++ b/src/wps/wps_i.h
@@ -127,6 +127,7 @@
 	struct wps_nfc_pw_token *nfc_pw_token;
 
 	int multi_ap_backhaul_sta;
+	int multi_ap_profile;
 };