[wpa_supplicant] cumilative patch from commit 4b755c967

Bug: 329004037
Test: Connect to open, WPA2, WPA3 and OWE
Test: Establish P2P connection
Test: Basic SoftAp tests
Test: Ran above tests on Pixel6
Test: Regression test (b/329003970)

BYPASS_INCLUSIVE_LANGUAGE_REASON=Merged from open source

4b755c967 build: De-duplicate _DIRS before calling mkdir
9a4423645 hostapd: Only attempt to set QoS map if supported by the driver
dec6fccf1 Support qos_map_set without CONFIG_INTERWORKING
8634e7343 mesh: Allow processing authentication frames in blocked state
a210fdb1c nl80211: Rewrite neigh code to not depend on libnl3-route
3ef057901 ndisc_snoop: Call dl_list_del() before freeing IPv6 addresses
e1cd3fe3c Cancel channel_list_update_timeout() in hostapd_cleanup_iface_partial()
47d7f3169 nl80211: Update drv->ifindex on removing the first BSS
1be706e86 hostapd: Add RRM link measurement request support
92fdb49b2 AP MLD: Set DTIM information properly in per-STA profile
36bd75dfd hostapd: Fix channel switch to a DFS channel
f4b84ecaf AP MLD: Track radar detection in offloaded DFS case
aaf879ef2 AP MLD: Do not update other links' RNR element if not enabled yet
32261721e nl80211: AP MLD: Parse link ID to determine the BSS for radar event
216cfd708 AP MLD: Fix missing check for legacy client case
d5e6f7998 AP MLD: Request Handle OBSS scan for a specific link
c9ad16870 AP MLD: Allow scan processing link to match the request
9b682e72d AP MLD: Find the link that is waiting for scan events
147f83692 PASN: Add set and get API for PASN data context
ab37a5731 Replace PMKSA cache inline stubs with wrapper function stubs
ba55088a7 Replace PTKSA cache inline stubs with wrapper function stubs
1f230a497 MBSSID: Include Extended Capabilities element in non-TX BSSID profile
37c00c3c5 AP MLD: Provide link addresses for non-AP MLDs in control interface
b818a1be1 Add a QCA vendor attribute to set avoid frequencies per netdev
9ac0e785c Revert "nl80211: Skip interface down/up when setting MAC address"
94506e8ed Use the latest updated BSS entry for sending ANQP requests
a9bc6e89d OpenSSL: Fix a memory leak in CMAC
4bc61b657 AP MLD: Remove restriction of having to disable the first link BSS last
a6d92da9a AP MLD: Support removal of link station from AP
1f88b3daf nl80211: Add callback function for removing link STAs
19e50f862 Export hostapd_sta_is_link_sta()
df34c2ced AP MLD: De-initialize/disable link BSS properly
63982fd09 nl80211: Print the MLD capabilities in debug
9fdbaf2ed AP MLD: Fix advertisement of MLD capabilities
7a0501d20 AP MLD: Refresh beacons for other links when one gets disabled/enabled
d2b62b3fe AP MLD: Support link removal before removing interface
55c30e8ab nl80211: Remove AP MLD links while removing the interface
a576180cd nl80211: Use per-BSS command for remove link
b162886fd nl80211: Re-factor nl80211_remove_links() function
b810426ea nl80211: Remove redundant put_freq call in set_ap() for AP MLD
420065733 nl80211: Fix set_ap() to add frequency without CONFIG_IEEE80211AX
f2f0dd354 nl80211: Cache hostapd_data context in per link BSS struct for AP MLD
60e1dca1e AP MLD: Clean up MLD when not required any further
fac34688a AP MLD: Assign link ID during BSS creation
b19aa9c42 AP MLD: Use MLD struct for MLD level information
2f0e5303e AP MLD: Add a separate MLD level structure
259b43a31 hostapd: MLO: Avoid use of mld_id as user configuration
69d53b8b6 nl80211: Fix potential NULL pointer dereference in set_ap()
666e954ca Remove unused wpa_drv_set_ap()
9be122d2e nl80211: Fix AP MLD frequency update on channel switch
9144f876a nl80211: Fix sending NL80211_CMD_DEL_BEACON command to wrong interface
ec4b755b0 wpa_cli: Don't select interface when using global socket
c24453dd9 Add a vendor attribute per MLO link ratemask bitmap configuration
77f39ed23 Document vendor command ratemask bitmap for EHT case
58017de69 Add QCA vendor sub-command and attribute for spectral scan completion
8f9da72d2 Add QCA vendor attribute indicating the spectral transport mode
5b4a78b1f Optimize internal BSS table updates based on a specific BSSID
8d0bd7f9c Update BSS entry on roaming only for actual BSS frequency change
024d4bca1 Multi-AP: WPS support for different Multi-AP profiles
69d086298 Multi-AP: Add support for VLAN related information
210c2b4bd Multi-AP: Add hostapd config option to disallow certain profiles
9a1512532 Multi-AP: Reject non-Multi-AP STA association on backhaul-only BSS
420afbdbd Multi-AP: Allow supported profile to be configured
c3e528653 Multi-AP: Parse Profile subelement
003411242 Multi-AP: Generation of Multi-AP Profile subelement
364cb7c94 Multi-AP: Parse the Multi-AP element using a shared helper function
0e2ca2e4e Multi-AP: Use proper length for remaining buffer for the element
61e46f860 Multi-AP: Move IE parameters into a struct for extensibility
2ae1e6f18 DBus: Add ANQP fields to BSS properties
2ea04435e DBus: Signal ANQP query done
d71c83851 DBus: Add a method to get ANQP fields
5eb409c4b DBus: Add dict helper for uint16 arrays
a438e5293 OpenSSL: Fix a memory leak on hpke_labeled_expand() error path
b35b1036f OpenSSL: Fix a memory leak on openssl_evp_pkey_ec_prime_len() error path
35df7ee09 DPP: Emit a DPP PB_STATUS event when push button starts
69dd408fb EHT: More accurate no-second-channel-offset checks when puncturing
131ee5926 EHT: Support punct_bitmap overriding in HE element generation
c96c3adc3 Move punct_update_legacy_bw() into src/common
9f43c1e26 Provide punct_bitmap to hostapd_set_freq_params()
47dad1ed1 EHT: Move puncturing bitmap determination into a helper function
010d8d10e EHT: Use eht_oper_puncturing_override when constructing VHT elements
c00abc69f Handle 6 GHz channels in Supported Operating Classes with freq_list
9e90486bc 2-octet operating classes in Support Operating Classes element
5dabc1018 Extend support for the 6 GHz operating class 137 (320 MHz)
e74d95e0a nl80211: Process 6 GHz regulatory rules to accurate channel flags
59951ebf0 Use a helper function to free neighbor DB entries
96f0af07e Clear all neighbor entry items explicitly
6f285fbaf Update own report in nr_db if SSID is changed
b653420a2 AP MLD: Set link address only when non-AP MLD is not added to driver
16abdac80 Fix INTERFACES command buffer size to allow more data
b483ceafc hostapd: Dump VHT/HE/EHT full capabilities in STA command output
040ba112a Use os_snprintf_error() more consistently in STA output generation
31bbc9391 Do not change out-of-range configuration parameters
fd24ed949 Fix valid range for disable_ht40
b1a880f38 Enforce valid range check for SET mbo_cell_capa and oce
e5b7e5b90 wpa_supplicant: Fix ignoring boundary 0 in config parser
10122e951 P2P: Fix pri/sec channel switch skipping for GO
e508c070c WNM: Keep BTM information until connection completes
17a2aa822 WNM: Follow BTM procedure if the last link is dropped
770f3cb30 WNM: Remove dialog_token parameter
c7cafef8a WNM: Set wnm_reply to 0 when sending it
261f7f9e5 WNM: Do not store coloc_intf_elems
033adbf83 WNM: Drop explicit wpa_is_bss_tmp_disallowed() check
8b51310f4 WNM: Drop explicit SSID check
939cd294b WNM: Drop check for current_ssid
436f07d02 WNM: Define a stub wnm_is_bss_excluded if WNM is disabled
e164943f4 WNM: Wait for BTM response TX status before roaming
40ef706e5 WNM: Don't scan frequencies of neighbors that should be ignored
20ed289a7 WNM: Clean up old scan data processing
4750a4f62 DPP: Wait for TX wait expiration on moving to neg_freq for Auth Resp
963dbad7d nl80211: Indicate EVENT_TX_WAIT_EXPIRE on match-saved
451d29952 DPP: Wait for ROC cancelled event on Auth Resp TX on another channel
45fffac0f BSS: Switch struct wpa_bss to use valid_links bitmask
e90f6678f nl80211: Remnove unused struct i802_link ctx
0d4288a00 nl80211: Use valid_links bitmask for bss->links array
9ed51186e Use a single define MAX_NUM_MLD_LINKS for the maximum number of links
11f26fed6 Use for_each_link() where possible
dbdf7ef67 Use for_each_link() in most cases
c9f8fe066 common: Introduce for_each_link() macro
6cb421c1f nl80211: Fix link indexing in nl80211_connect_common()
408a399aa nl80211: Explicitly differentiate between 5 GHz and 6 GHz modes
dbcf9ff15 P2P: Notify the IP address of the connected P2P Client
b18d95759 P2P: Disable pri/sec channel switch for GO with forced frequency
0e4aa28da hostapd_cli: Indentation cleanup
a01972a06 Add "stop_ap" command for hostapd_cli
8cdb0d3f2 AP MLD: Stop AP per link
d084ef36b AP MLD: Clean up disassoc handling for non-AP MLD link validity check
7ceafb6e9 AP MLD: Handle disassociation notification with SME offload to driver
95a825bc4 RADIUS: Preliminary support RADIUS/TLS as an alternative to RADIUS/UDP
87f33c26b RADIUS: Simplify IPv4/IPv6 socket handling in client
971b78147 RADIUS: Simplify radius_change_server() parameters
3386e1327 l2_packet_freebsd: Fix macOS build
86c242171 TDLS: Defer the start request until the discovery response RX for MLO
352ad5f1a Apply CHAN_SWITCH in all BSS for MBSSID case
87120a5b6 Add QCA_NL80211_VENDOR_SUBCMD_ADJUST_TX_POWER command
fe82a61ef Add QCA vendor attribute for BTM support configuration

Change-Id: I11d4eeb2e4ab44630e3359a2886361b3fccd7ead
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;