Revert "Revert "[wpa_supplicant] cumilative patch from commit 3a..."

Revert submission 28102966-revert-26533062-Supplicant_merge_June24-CUATTSRBBR

Reason for revert: Fixed the regression issue (ag/28389573)

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

Bug: 329004037
Test: Turn ON/OFF SoftAp multiple times

Change-Id: Ibfff2a847be5678f1a6d77e28506a05936812a91
diff --git a/src/ap/acs.c b/src/ap/acs.c
index e3cfe1d..28b0ba7 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -245,6 +245,8 @@
 	ACS_BW40,
 	ACS_BW80,
 	ACS_BW160,
+	ACS_BW320_1,
+	ACS_BW320_2,
 };
 
 struct bw_item {
@@ -286,10 +288,20 @@
 	{ 6435, 6575, 111 }, { 6595, 6735, 143 },
 	{ 6755, 6895, 175 }, { 6915, 7055, 207 }, { -1, -1, -1 }
 };
+static const struct bw_item bw_320_1[] = {
+	{ 5955, 6255, 31 }, { 6275, 6575, 95 }, { 6595, 6895, 159 },
+	{ -1, -1, -1 }
+};
+static const struct bw_item bw_320_2[] = {
+	{ 6115, 6415, 63 }, { 6435, 6735, 127 }, { 6755, 7055, 191 },
+	{ -1, -1, -1 }
+};
 static const struct bw_item *bw_desc[] = {
 	[ACS_BW40] = bw_40,
 	[ACS_BW80] = bw_80,
 	[ACS_BW160] = bw_160,
+	[ACS_BW320_1] = bw_320_1,
+	[ACS_BW320_2] = bw_320_2,
 };
 
 
@@ -768,6 +780,42 @@
 #endif /* CONFIG_IEEE80211BE */
 
 
+static bool
+acs_usable_bw320_chan(struct hostapd_iface *iface,
+		      struct hostapd_channel_data *chan, int *bw320_offset)
+{
+	const char *bw320_str[] = { "320 MHz", "320 MHz-1", "320 MHz-2" };
+	int conf_bw320_offset = hostapd_get_bw320_offset(iface->conf);
+
+	*bw320_offset = 0;
+	switch (conf_bw320_offset) {
+	case 1:
+		if (acs_usable_bw_chan(chan, ACS_BW320_1))
+			*bw320_offset = 1;
+		break;
+	case 2:
+		if (acs_usable_bw_chan(chan, ACS_BW320_2))
+			*bw320_offset = 2;
+		break;
+	case 0:
+	default:
+		conf_bw320_offset = 0;
+		if (acs_usable_bw_chan(chan, ACS_BW320_1))
+			*bw320_offset = 1;
+		else if (acs_usable_bw_chan(chan, ACS_BW320_2))
+			*bw320_offset = 2;
+		break;
+	}
+
+	if (!*bw320_offset)
+		wpa_printf(MSG_DEBUG,
+			   "ACS: Channel %d: not allowed as primary channel for %s bandwidth",
+			   chan->chan, bw320_str[conf_bw320_offset]);
+
+	return *bw320_offset != 0;
+}
+
+
 static void
 acs_find_ideal_chan_mode(struct hostapd_iface *iface,
 			 struct hostapd_hw_modes *mode,
@@ -779,14 +827,18 @@
 	struct hostapd_channel_data *chan, *adj_chan = NULL, *best;
 	long double factor;
 	int i, j;
+	int bw320_offset = 0, ideal_bw320_offset = 0;
 	unsigned int k;
+	int secondary_channel = 1, freq_offset;
+
+	if (is_24ghz_mode(mode->mode))
+		secondary_channel = iface->conf->secondary_channel;
 
 	for (i = 0; i < mode->num_channels; i++) {
-		double total_weight;
+		double total_weight = 0;
 		struct acs_bias *bias, tmp_bias;
-		bool update_best = true;
 
-		best = chan = &mode->channels[i];
+		chan = &mode->channels[i];
 
 		/* Since in the current ACS implementation the first channel is
 		 * always a primary channel, skip channels not available as
@@ -818,7 +870,7 @@
 		    iface->conf->country[2] == 0x4f)
 			continue;
 
-		if (!chan_bw_allowed(chan, bw, 1, 1)) {
+		if (!chan_bw_allowed(chan, bw, secondary_channel != -1, 1)) {
 			wpa_printf(MSG_DEBUG,
 				   "ACS: Channel %d: BW %u is not supported",
 				   chan->chan, bw);
@@ -839,7 +891,8 @@
 		}
 
 		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
-		    (iface->conf->ieee80211ac || iface->conf->ieee80211ax)) {
+		    (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+		     iface->conf->ieee80211be)) {
 			if (hostapd_get_oper_chwidth(iface->conf) ==
 			    CONF_OPER_CHWIDTH_80MHZ &&
 			    !acs_usable_bw_chan(chan, ACS_BW80)) {
@@ -859,13 +912,25 @@
 			}
 		}
 
+		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		    iface->conf->ieee80211be) {
+			if (hostapd_get_oper_chwidth(iface->conf) ==
+			    CONF_OPER_CHWIDTH_320MHZ &&
+			    !acs_usable_bw320_chan(iface, chan, &bw320_offset))
+				continue;
+		}
+
 		factor = 0;
-		if (acs_usable_chan(chan))
+		best = NULL;
+		if (acs_usable_chan(chan)) {
 			factor = chan->interference_factor;
-		total_weight = 1;
+			total_weight = 1;
+			best = chan;
+		}
 
 		for (j = 1; j < n_chans; j++) {
-			adj_chan = acs_find_chan(iface, chan->freq + (j * 20));
+			adj_chan = acs_find_chan(iface, chan->freq +
+						 j * secondary_channel * 20);
 			if (!adj_chan)
 				break;
 
@@ -876,16 +941,14 @@
 				break;
 			}
 
-			if (acs_usable_chan(adj_chan)) {
-				factor += adj_chan->interference_factor;
-				total_weight += 1;
-			} else {
-				update_best = false;
-			}
+			if (!acs_usable_chan(adj_chan))
+				continue;
+
+			factor += adj_chan->interference_factor;
+			total_weight += 1;
 
 			/* find the best channel in this segment */
-			if (update_best &&
-			    adj_chan->interference_factor <
+			if (!best || adj_chan->interference_factor <
 			    best->interference_factor)
 				best = adj_chan;
 		}
@@ -898,8 +961,9 @@
 
 		/* If the AP is in the 5 GHz or 6 GHz band, lets prefer a less
 		 * crowded primary channel if one was found in the segment */
-		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
-		    chan != best) {
+		if (iface->current_mode &&
+		    iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		    best && chan != best) {
 			wpa_printf(MSG_DEBUG,
 				   "ACS: promoting channel %d over %d (less interference %Lg/%Lg)",
 				   best->chan, chan->chan,
@@ -912,8 +976,9 @@
 		 * channel interference factor. */
 		if (is_24ghz_mode(mode->mode)) {
 			for (j = 0; j < n_chans; j++) {
+				freq_offset = j * 20 * secondary_channel;
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) - 5);
+							 freq_offset - 5);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -921,7 +986,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) - 10);
+							 freq_offset - 10);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_NEXT_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -929,7 +994,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) + 5);
+							 freq_offset + 5);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -937,7 +1002,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) + 10);
+							 freq_offset + 10);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_NEXT_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -946,6 +1011,9 @@
 			}
 		}
 
+		if (total_weight == 0)
+			continue;
+
 		factor /= total_weight;
 
 		bias = NULL;
@@ -983,6 +1051,7 @@
 
 			*ideal_factor = factor;
 			*ideal_chan = chan;
+			ideal_bw320_offset = bw320_offset;
 
 #ifdef CONFIG_IEEE80211BE
 			if (iface->conf->ieee80211be)
@@ -993,9 +1062,13 @@
 		}
 
 		/* This channel would at least be usable */
-		if (!(*rand_chan))
+		if (!(*rand_chan)) {
 			*rand_chan = chan;
+			ideal_bw320_offset = bw320_offset;
+		}
 	}
+
+	hostapd_set_and_check_bw320_offset(iface->conf, ideal_bw320_offset);
 }
 
 
@@ -1022,19 +1095,12 @@
 		goto bw_selected;
 	}
 
-	/* TODO: HT40- support */
-
-	if (iface->conf->ieee80211n &&
-	    iface->conf->secondary_channel == -1) {
-		wpa_printf(MSG_ERROR, "ACS: HT40- is not supported yet. Please try HT40+");
-		return NULL;
-	}
-
 	if (iface->conf->ieee80211n &&
 	    iface->conf->secondary_channel)
 		n_chans = 2;
 
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		switch (hostapd_get_oper_chwidth(iface->conf)) {
 		case CONF_OPER_CHWIDTH_80MHZ:
 			n_chans = 4;
@@ -1042,6 +1108,9 @@
 		case CONF_OPER_CHWIDTH_160MHZ:
 			n_chans = 8;
 			break;
+		case CONF_OPER_CHWIDTH_320MHZ:
+			n_chans = 16;
+			break;
 		default:
 			break;
 		}
@@ -1091,7 +1160,8 @@
 	    acs_find_mode(iface, iface->freq) != HOSTAPD_MODE_IEEE80211A)
 		return;
 
-	wpa_printf(MSG_DEBUG, "ACS: Adjusting HT/VHT/HE secondary frequency");
+	wpa_printf(MSG_DEBUG,
+		   "ACS: Adjusting HT/VHT/HE/EHT secondary frequency");
 
 	for (i = 0; bw_desc[ACS_BW40][i].first != -1; i++) {
 		if (iface->freq == bw_desc[ACS_BW40][i].first)
@@ -1106,7 +1176,7 @@
 {
 	int center;
 
-	wpa_printf(MSG_DEBUG, "ACS: Adjusting VHT center frequency");
+	wpa_printf(MSG_DEBUG, "ACS: Adjusting center frequency");
 
 	switch (hostapd_get_oper_chwidth(iface->conf)) {
 	case CONF_OPER_CHWIDTH_USE_HT:
@@ -1125,11 +1195,28 @@
 	case CONF_OPER_CHWIDTH_160MHZ:
 		center = acs_get_bw_center_chan(iface->freq, ACS_BW160);
 		break;
+	case CONF_OPER_CHWIDTH_320MHZ:
+		switch (hostapd_get_bw320_offset(iface->conf)) {
+		case 1:
+			center = acs_get_bw_center_chan(iface->freq,
+							ACS_BW320_1);
+			break;
+		case 2:
+			center = acs_get_bw_center_chan(iface->freq,
+							ACS_BW320_2);
+			break;
+		default:
+			wpa_printf(MSG_INFO,
+				   "ACS: BW320 offset is not selected");
+			return;
+		}
+
+		break;
 	default:
 		/* TODO: How can this be calculated? Adjust
 		 * acs_find_ideal_chan() */
 		wpa_printf(MSG_INFO,
-			   "ACS: Only VHT20/40/80/160 is supported now");
+			   "ACS: Only VHT20/40/80/160/320 is supported now");
 		return;
 	}
 
@@ -1192,7 +1279,8 @@
 	iface->conf->punct_bitmap = ideal_chan->punct_bitmap;
 #endif /* CONFIG_IEEE80211BE */
 
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		acs_adjust_secondary(iface);
 		acs_adjust_center_freq(iface);
 	}
diff --git a/src/ap/airtime_policy.c b/src/ap/airtime_policy.c
index abe817c..6844311 100644
--- a/src/ap/airtime_policy.c
+++ b/src/ap/airtime_policy.c
@@ -232,7 +232,7 @@
 	struct airtime_sta_weight *wt;
 
 	wt = hapd->conf->airtime_weight_list;
-	while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
+	while (wt && !ether_addr_equal(wt->addr, sta))
 		wt = wt->next;
 
 	return wt ? wt->weight : hapd->conf->airtime_weight;
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 60d3566..445d963 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration helper functions
- * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -165,6 +165,7 @@
 
 #ifdef CONFIG_TESTING_OPTIONS
 	bss->sae_commit_status = -1;
+	bss->test_assoc_comeback_type = -1;
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #ifdef CONFIG_PASN
@@ -283,6 +284,10 @@
 	conf->he_6ghz_max_ampdu_len_exp = 7;
 	conf->he_6ghz_rx_ant_pat = 1;
 	conf->he_6ghz_tx_ant_pat = 1;
+	conf->he_6ghz_reg_pwr_type = HE_REG_INFO_6GHZ_AP_TYPE_VLP;
+	conf->reg_def_cli_eirp_psd = -1;
+	conf->reg_sub_cli_eirp_psd = -1;
+	conf->reg_def_cli_eirp = -1;
 #endif /* CONFIG_IEEE80211AX */
 
 	/* The third octet of the country string uses an ASCII space character
@@ -297,6 +302,8 @@
 	conf->airtime_update_interval = AIRTIME_DEFAULT_UPDATE_INTERVAL;
 #endif /* CONFIG_AIRTIME_POLICY */
 
+	hostapd_set_and_check_bw320_offset(conf, 0);
+
 	return conf;
 }
 
@@ -695,6 +702,33 @@
 }
 
 
+#ifdef CONFIG_IEEE80211R_AP
+
+void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf)
+{
+	struct ft_remote_r0kh *r0kh, *r0kh_prev;
+	struct ft_remote_r1kh *r1kh, *r1kh_prev;
+
+	r0kh = conf->r0kh_list;
+	conf->r0kh_list = NULL;
+	while (r0kh) {
+		r0kh_prev = r0kh;
+		r0kh = r0kh->next;
+		os_free(r0kh_prev);
+	}
+
+	r1kh = conf->r1kh_list;
+	conf->r1kh_list = NULL;
+	while (r1kh) {
+		r1kh_prev = r1kh;
+		r1kh = r1kh->next;
+		os_free(r1kh_prev);
+	}
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 static void hostapd_config_free_anqp_elem(struct hostapd_bss_config *conf)
 {
 	struct anqp_element *elem;
@@ -827,26 +861,9 @@
 	os_free(conf->time_zone);
 
 #ifdef CONFIG_IEEE80211R_AP
-	{
-		struct ft_remote_r0kh *r0kh, *r0kh_prev;
-		struct ft_remote_r1kh *r1kh, *r1kh_prev;
-
-		r0kh = conf->r0kh_list;
-		conf->r0kh_list = NULL;
-		while (r0kh) {
-			r0kh_prev = r0kh;
-			r0kh = r0kh->next;
-			os_free(r0kh_prev);
-		}
-
-		r1kh = conf->r1kh_list;
-		conf->r1kh_list = NULL;
-		while (r1kh) {
-			r1kh_prev = r1kh;
-			r1kh = r1kh->next;
-			os_free(r1kh_prev);
-		}
-	}
+	hostapd_config_clear_rxkhs(conf);
+	os_free(conf->rxkh_file);
+	conf->rxkh_file = NULL;
 #endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_WPS
@@ -944,6 +961,8 @@
 	wpabuf_free(conf->rsnxe_override_ft);
 	wpabuf_free(conf->gtk_rsc_override);
 	wpabuf_free(conf->igtk_rsc_override);
+	wpabuf_free(conf->eapol_m1_elements);
+	wpabuf_free(conf->eapol_m3_elements);
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	os_free(conf->no_probe_resp_if_seen_on);
@@ -1131,10 +1150,9 @@
 	for (psk = conf->ssid.wpa_psk; psk != NULL; psk = psk->next) {
 		if (next_ok &&
 		    (psk->group ||
-		     (addr && os_memcmp(psk->addr, addr, ETH_ALEN) == 0) ||
+		     (addr && ether_addr_equal(psk->addr, addr)) ||
 		     (!addr && p2p_dev_addr &&
-		      os_memcmp(psk->p2p_dev_addr, p2p_dev_addr, ETH_ALEN) ==
-		      0))) {
+		      ether_addr_equal(psk->p2p_dev_addr, p2p_dev_addr)))) {
 			if (vlan_id)
 				*vlan_id = psk->vlan_id;
 			return psk->psk;
@@ -1558,6 +1576,10 @@
 			   "Cannot set ieee80211be without ieee80211ax");
 		return -1;
 	}
+
+	if (full_config)
+		hostapd_set_and_check_bw320_offset(conf,
+						   conf->eht_bw320_offset);
 #endif /* CONFIG_IEEE80211BE */
 
 	if (full_config && conf->mbssid && !conf->ieee80211ax) {
@@ -1750,7 +1772,7 @@
 	int i = 0;
 
 	while (i < *num) {
-		if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal((*acl)[i].addr, addr)) {
 			os_remove_in_array(*acl, *num, sizeof(**acl), i);
 			(*num)--;
 		} else {
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 99a6d18..69db16d 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration definitions and helpers functions
- * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -405,6 +405,7 @@
 	int ft_over_ds;
 	int ft_psk_generate_local;
 	int r1_max_key_lifetime;
+	char *rxkh_file;
 #endif /* CONFIG_IEEE80211R_AP */
 
 	char *ctrl_interface; /* directory for UNIX domain sockets */
@@ -704,6 +705,14 @@
 	unsigned int oci_freq_override_ft_assoc;
 	unsigned int oci_freq_override_fils_assoc;
 	unsigned int oci_freq_override_wnm_sleep;
+	struct wpabuf *eapol_m1_elements;
+	struct wpabuf *eapol_m3_elements;
+	bool eapol_m3_no_encrypt;
+	int test_assoc_comeback_type;
+
+#ifdef CONFIG_IEEE80211BE
+	u16 eht_oper_puncturing_override;
+#endif /* CONFIG_IEEE80211BE */
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #define MESH_ENABLED BIT(0)
@@ -948,6 +957,14 @@
 
 	/* The AP's MLD MAC address within the AP MLD */
 	u8 mld_addr[ETH_ALEN];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	/*
+	 * If set indicate the AP as disabled in the RNR element included in the
+	 * other APs in the AP MLD.
+	 */
+	bool mld_indicate_disabled;
+#endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_IEEE80211BE */
 };
 
@@ -1139,6 +1156,19 @@
 	u8 he_6ghz_rx_ant_pat;
 	u8 he_6ghz_tx_ant_pat;
 	u8 he_6ghz_reg_pwr_type;
+
+	int reg_def_cli_eirp_psd;
+	int reg_sub_cli_eirp_psd;
+
+	/*
+	 * This value should be used when regulatory client EIRP PSD values
+	 * advertised by an AP that is an SP AP or an indoor SP AP are
+	 * insufficient to ensure that regulatory client limits on total EIRP
+	 * are always met for all transmission bandwidths within the bandwidth
+	 * of the AP’s BSS.
+	 */
+	int reg_def_cli_eirp;
+
 	bool require_he;
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1175,6 +1205,8 @@
 	struct eht_phy_capabilities_info eht_phy_capab;
 	u16 punct_bitmap; /* a bitmap of disabled 20 MHz channels */
 	u8 punct_acs_threshold;
+	u8 eht_default_pe_duration;
+	u8 eht_bw320_offset;
 #endif /* CONFIG_IEEE80211BE */
 
 	/* EHT enable/disable config from CHAN_SWITCH */
@@ -1242,7 +1274,8 @@
 #ifdef CONFIG_IEEE80211BE
 	if (conf->ieee80211be)
 		conf->eht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx;
-	if (center_idx_to_bw_6ghz(oper_centr_freq_seg0_idx) == 4)
+	if (is_6ghz_op_class(conf->op_class) &&
+	    center_idx_to_bw_6ghz(oper_centr_freq_seg0_idx) == 4)
 		oper_centr_freq_seg0_idx +=
 			conf->channel > oper_centr_freq_seg0_idx ? 16 : -16;
 #endif /* CONFIG_IEEE80211BE */
@@ -1274,6 +1307,43 @@
 	conf->vht_oper_centr_freq_seg1_idx = oper_centr_freq_seg1_idx;
 }
 
+static inline u8
+hostapd_get_bw320_offset(struct hostapd_config *conf)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) &&
+	    hostapd_get_oper_chwidth(conf) == CONF_OPER_CHWIDTH_320MHZ)
+		return conf->eht_bw320_offset;
+#endif /* CONFIG_IEEE80211BE */
+	return 0;
+}
+
+static inline void
+hostapd_set_and_check_bw320_offset(struct hostapd_config *conf,
+				   u8 bw320_offset)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) &&
+	    op_class_to_ch_width(conf->op_class) == CONF_OPER_CHWIDTH_320MHZ) {
+		if (conf->channel) {
+			/* If the channel is set, then calculate bw320_offset
+			 * by center frequency segment 0.
+			 */
+			u8 seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf);
+
+			conf->eht_bw320_offset = (seg0 - 31) % 64 ? 2 : 1;
+		} else {
+			/* If the channel is not set, bw320_offset indicates
+			 * preferred offset of 320 MHz.
+			 */
+			conf->eht_bw320_offset = bw320_offset;
+		}
+	} else {
+		conf->eht_bw320_offset = 0;
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
 
 int hostapd_mac_comp(const void *a, const void *b);
 struct hostapd_config * hostapd_config_defaults(void);
@@ -1282,6 +1352,7 @@
 void hostapd_config_free_eap_user(struct hostapd_eap_user *user);
 void hostapd_config_free_eap_users(struct hostapd_eap_user *user);
 void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **p);
+void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf);
 void hostapd_config_free_bss(struct hostapd_bss_config *conf);
 void hostapd_config_free(struct hostapd_config *conf);
 int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 8f9cc5b..60d66e4 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -265,9 +265,35 @@
 }
 
 
+static bool hostapd_sta_is_link_sta(struct hostapd_data *hapd,
+				    struct sta_info *sta)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta) &&
+	    sta->mld_assoc_link_id != hapd->mld_link_id)
+		return true;
+#endif /* CONFIG_IEEE80211BE */
+
+	return false;
+}
+
+
 int hostapd_set_authorized(struct hostapd_data *hapd,
 			   struct sta_info *sta, int authorized)
 {
+	/*
+	 * The WPA_STA_AUTHORIZED flag is relevant only for the MLD station and
+	 * not to the link stations (as the authorization is done between the
+	 * MLD peers). Thus, do not propagate the change to the driver for the
+	 * link stations.
+	 */
+	if (hostapd_sta_is_link_sta(hapd, sta)) {
+		wpa_printf(MSG_DEBUG,
+			   "%s: Do not update link station flags (" MACSTR ")",
+			   __func__, MAC2STR(sta->addr));
+		return 0;
+	}
+
 	if (authorized) {
 		return hostapd_sta_set_flags(hapd, sta->addr,
 					     hostapd_sta_flags_to_drv(
@@ -285,11 +311,24 @@
 {
 	int set_flags, total_flags, flags_and, flags_or;
 	total_flags = hostapd_sta_flags_to_drv(sta->flags);
-	set_flags = WPA_STA_SHORT_PREAMBLE | WPA_STA_WMM | WPA_STA_MFP;
-	if (((!hapd->conf->ieee802_1x && !hapd->conf->wpa) ||
-	     sta->auth_alg == WLAN_AUTH_FT) &&
-	    sta->flags & WLAN_STA_AUTHORIZED)
-		set_flags |= WPA_STA_AUTHORIZED;
+	set_flags = WPA_STA_SHORT_PREAMBLE | WPA_STA_WMM | WPA_STA_MFP |
+		WPA_STA_AUTHORIZED;
+
+	/*
+	 * All the station flags other than WPA_STA_SHORT_PREAMBLE are relevant
+	 * only for the MLD station and not to the link stations (as these flags
+	 * are related to the MLD state and not the link state). As for the
+	 * WPA_STA_SHORT_PREAMBLE, since the station is an EHT station, it must
+	 * support short preamble. Thus, do not propagate the change to the
+	 * driver for the link stations.
+	 */
+	if (hostapd_sta_is_link_sta(hapd, sta)) {
+		wpa_printf(MSG_DEBUG,
+			   "%s: Do not update link station flags (" MACSTR ")",
+			   __func__, MAC2STR(sta->addr));
+		return 0;
+	}
+
 	flags_or = total_flags & set_flags;
 	flags_and = total_flags | ~set_flags;
 	return hostapd_sta_set_flags(hapd, sta->addr, total_flags,
@@ -793,15 +832,21 @@
 			   const u8 *addr, int reason)
 {
 	int link_id = -1;
+	const u8 *own_addr = hapd->own_addr;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap)
+	if (hapd->conf->mld_ap) {
+		struct sta_info *sta = ap_get_sta(hapd, addr);
+
 		link_id = hapd->mld_link_id;
+		if (ap_sta_is_mld(hapd, sta))
+			own_addr = hapd->mld_addr;
+	}
 #endif /* CONFIG_IEEE80211BE */
 
 	if (!hapd->driver || !hapd->driver->sta_deauth || !hapd->drv_priv)
 		return 0;
-	return hapd->driver->sta_deauth(hapd->drv_priv, hapd->own_addr, addr,
+	return hapd->driver->sta_deauth(hapd->drv_priv, own_addr, addr,
 					reason, link_id);
 }
 
@@ -809,9 +854,20 @@
 int hostapd_drv_sta_disassoc(struct hostapd_data *hapd,
 			     const u8 *addr, int reason)
 {
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		struct sta_info *sta = ap_get_sta(hapd, addr);
+
+		if (ap_sta_is_mld(hapd, sta))
+			own_addr = hapd->mld_addr;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (!hapd->driver || !hapd->driver->sta_disassoc || !hapd->drv_priv)
 		return 0;
-	return hapd->driver->sta_disassoc(hapd->drv_priv, hapd->own_addr, addr,
+	return hapd->driver->sta_disassoc(hapd->drv_priv, own_addr, addr,
 					  reason);
 }
 
@@ -826,22 +882,22 @@
 }
 
 
-int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
-			    unsigned int wait, const u8 *dst, const u8 *data,
-			    size_t len)
+static int hapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
+				unsigned int wait, const u8 *dst,
+				const u8 *data, size_t len, bool addr3_ap)
 {
+	const u8 *own_addr = hapd->own_addr;
 	const u8 *bssid;
 	const u8 wildcard_bssid[ETH_ALEN] = {
 		0xff, 0xff, 0xff, 0xff, 0xff, 0xff
 	};
+	struct sta_info *sta;
 
 	if (!hapd->driver || !hapd->driver->send_action || !hapd->drv_priv)
 		return 0;
 	bssid = hapd->own_addr;
-	if (!is_multicast_ether_addr(dst) &&
+	if (!addr3_ap && !is_multicast_ether_addr(dst) &&
 	    len > 0 && data[0] == WLAN_ACTION_PUBLIC) {
-		struct sta_info *sta;
-
 		/*
 		 * Public Action frames to a STA that is not a member of the BSS
 		 * shall use wildcard BSSID value.
@@ -849,7 +905,7 @@
 		sta = ap_get_sta(hapd, dst);
 		if (!sta || !(sta->flags & WLAN_STA_ASSOC))
 			bssid = wildcard_bssid;
-	} else if (is_broadcast_ether_addr(dst) &&
+	} else if (!addr3_ap && is_broadcast_ether_addr(dst) &&
 		   len > 0 && data[0] == WLAN_ACTION_PUBLIC) {
 		/*
 		 * The only current use case of Public Action frames with
@@ -858,9 +914,27 @@
 		 * so have to use the wildcard BSSID value.
 		 */
 		bssid = wildcard_bssid;
+#ifdef CONFIG_IEEE80211BE
+	} else if (hapd->conf->mld_ap) {
+		sta = ap_get_sta(hapd, dst);
+
+		if (ap_sta_is_mld(hapd, sta)) {
+			own_addr = hapd->mld_addr;
+			bssid = own_addr;
+		}
+#endif /* CONFIG_IEEE80211BE */
 	}
+
 	return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst,
-					 hapd->own_addr, bssid, data, len, 0);
+					 own_addr, bssid, data, len, 0);
+}
+
+
+int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
+			    unsigned int wait, const u8 *dst, const u8 *data,
+			    size_t len)
+{
+	return hapd_drv_send_action(hapd, freq, wait, dst, data, len, false);
 }
 
 
@@ -869,11 +943,7 @@
 				     unsigned int wait, const u8 *dst,
 				     const u8 *data, size_t len)
 {
-	if (hapd->driver == NULL || hapd->driver->send_action == NULL)
-		return 0;
-	return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst,
-					 hapd->own_addr, hapd->own_addr, data,
-					 len, 0);
+	return hapd_drv_send_action(hapd, freq, wait, dst, data, len, true);
 }
 
 
@@ -1025,6 +1095,12 @@
 
 	os_memset(&params, 0, sizeof(params));
 	params.hw_mode = hapd->iface->conf->hw_mode;
+	params.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && hapd->iconf->ieee80211be &&
+	    !hapd->conf->disable_11be)
+		params.link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	/*
 	 * If no chanlist config parameter is provided, include all enabled
diff --git a/src/ap/ap_list.c b/src/ap/ap_list.c
index 20be7f8..13facab 100644
--- a/src/ap/ap_list.c
+++ b/src/ap/ap_list.c
@@ -55,7 +55,7 @@
 	struct ap_info *s;
 
 	s = iface->ap_hash[STA_HASH(ap)];
-	while (s != NULL && os_memcmp(s->addr, ap, ETH_ALEN) != 0)
+	while (s != NULL && !ether_addr_equal(s->addr, ap))
 		s = s->hnext;
 	return s;
 }
@@ -100,13 +100,13 @@
 
 	s = iface->ap_hash[STA_HASH(ap->addr)];
 	if (s == NULL) return;
-	if (os_memcmp(s->addr, ap->addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(s->addr, ap->addr)) {
 		iface->ap_hash[STA_HASH(ap->addr)] = s->hnext;
 		return;
 	}
 
 	while (s->hnext != NULL &&
-	       os_memcmp(s->hnext->addr, ap->addr, ETH_ALEN) != 0)
+	       !ether_addr_equal(s->hnext->addr, ap->addr))
 		s = s->hnext;
 	if (s->hnext != NULL)
 		s->hnext = s->hnext->hnext;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 1b5cea9..e50f0a0 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -239,12 +239,10 @@
 			continue; /* can use same entry */
 		}
 
-		if (start && prev) {
+		if (start && prev)
 			pos = hostapd_eid_country_add(hapd, pos, end,
 						      chan_spacing,
 						      start, prev);
-			start = NULL;
-		}
 
 		/* Start new group */
 		start = prev = chan;
@@ -565,19 +563,78 @@
 }
 
 
-static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd,
-				   const struct ieee80211_mgmt *req,
-				   int is_p2p, size_t *resp_len,
-				   const u8 *known_bss, u8 known_bss_len)
+static size_t he_elem_len(struct hostapd_data *hapd)
 {
+	size_t len = 0;
+
+#ifdef CONFIG_IEEE80211AX
+	if (!hapd->iconf->ieee80211ax || hapd->conf->disable_11ax)
+		return len;
+
+	len += 3 + sizeof(struct ieee80211_he_capabilities) +
+		3 + sizeof(struct ieee80211_he_operation) +
+		3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
+		3 + sizeof(struct ieee80211_spatial_reuse);
+	if (is_6ghz_op_class(hapd->iconf->op_class)) {
+		len += sizeof(struct ieee80211_he_6ghz_oper_info) +
+			3 + sizeof(struct ieee80211_he_6ghz_band_cap);
+		/* An additional Transmit Power Envelope element for
+		 * subordinate client */
+		if (he_reg_is_indoor(hapd->iconf->he_6ghz_reg_pwr_type))
+			len += 4;
+
+		/* An additional Transmit Power Envelope element for
+		 * default client with unit interpretation of regulatory
+		 * client EIRP */
+		if (hapd->iconf->reg_def_cli_eirp != -1 &&
+		    he_reg_is_sp(hapd->iconf->he_6ghz_reg_pwr_type))
+			len += 4;
+	}
+#endif /* CONFIG_IEEE80211AX */
+
+	return len;
+}
+
+
+struct probe_resp_params {
+	const struct ieee80211_mgmt *req;
+	bool is_p2p;
+
+	/* Generated IEs will be included inside an ML element */
+	bool is_ml_sta_info;
+	struct hostapd_data *mld_ap;
+	struct mld_info *mld_info;
+
 	struct ieee80211_mgmt *resp;
-	u8 *pos, *epos, *csa_pos;
-	size_t buflen;
+	size_t resp_len;
+	u8 *csa_pos;
+	u8 *ecsa_pos;
+	const u8 *known_bss;
+	u8 known_bss_len;
 
-	hapd = hostapd_mbssid_get_tx_bss(hapd);
+#ifdef CONFIG_IEEE80211AX
+	u8 *cca_pos;
+#endif /* CONFIG_IEEE80211AX */
+};
 
-#define MAX_PROBERESP_LEN 768
-	buflen = MAX_PROBERESP_LEN;
+
+static void hostapd_free_probe_resp_params(struct probe_resp_params *params)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (!params)
+		return;
+	ap_sta_free_sta_profile(params->mld_info);
+	os_free(params->mld_info);
+	params->mld_info = NULL;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd,
+					   struct probe_resp_params *params)
+{
+	size_t buflen = 0;
+
 #ifdef CONFIG_WPS
 	if (hapd->wps_probe_resp_ie)
 		buflen += wpabuf_len(hapd->wps_probe_resp_ie);
@@ -597,23 +654,7 @@
 			2 + sizeof(struct ieee80211_vht_operation);
 	}
 
-#ifdef CONFIG_IEEE80211AX
-	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
-		buflen += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation) +
-			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
-			3 + sizeof(struct ieee80211_spatial_reuse);
-		if (is_6ghz_op_class(hapd->iconf->op_class)) {
-			buflen += sizeof(struct ieee80211_he_6ghz_oper_info) +
-				3 + sizeof(struct ieee80211_he_6ghz_band_cap);
-			 /* An additional Transmit Power Envelope element for
-			  * subordinate client */
-			if (hapd->iconf->he_6ghz_reg_pwr_type ==
-			    HE_6GHZ_INDOOR_AP)
-				buflen += 4;
-		}
-	}
-#endif /* CONFIG_IEEE80211AX */
+	buflen += he_elem_len(hapd);
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
@@ -622,58 +663,45 @@
 		if (hapd->iconf->punct_bitmap)
 			buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
 
-		/*
-		 * TODO: Multi-Link element has variable length and can be
-		 * long based on the common info and number of per
-		 * station profiles. For now use 256.
-		 */
-		if (hapd->conf->mld_ap)
-			buflen += 256;
+		if (!params->is_ml_sta_info && hapd->conf->mld_ap) {
+			struct hostapd_data *ml_elem_ap =
+				params->mld_ap ? params->mld_ap : hapd;
+
+			buflen += hostapd_eid_eht_ml_beacon_len(
+				ml_elem_ap, params->mld_info, !!params->mld_ap);
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
 	buflen += hostapd_eid_mbssid_len(hapd, WLAN_FC_STYPE_PROBE_RESP, NULL,
-					 known_bss, known_bss_len, NULL);
-	buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP);
+					 params->known_bss,
+					 params->known_bss_len, NULL);
+	if (!params->is_ml_sta_info)
+		buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP);
 	buflen += hostapd_mbo_ie_len(hapd);
 	buflen += hostapd_eid_owe_trans_len(hapd);
 	buflen += hostapd_eid_dpp_cc_len(hapd);
 
-	resp = os_zalloc(buflen);
-	if (resp == NULL)
-		return NULL;
+	return buflen;
+}
 
-	epos = ((u8 *) resp) + MAX_PROBERESP_LEN;
 
-	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
-					   WLAN_FC_STYPE_PROBE_RESP);
-	/* Unicast the response to all requests on bands other than 6 GHz. For
-	 * the 6 GHz, unicast is used only if the actual SSID is not included in
-	 * the Beacon frames. Otherwise, broadcast response is used per IEEE
-	 * Std 802.11ax-2021, 26.17.2.3.2. Broadcast address is also used for
-	 * the Probe Response frame template for the unsolicited (i.e., not as
-	 * a response to a specific request) case. */
-	if (req && (!is_6ghz_op_class(hapd->iconf->op_class) ||
-		    hapd->conf->ignore_broadcast_ssid))
-		os_memcpy(resp->da, req->sa, ETH_ALEN);
-	else
-		os_memset(resp->da, 0xff, ETH_ALEN);
+static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd,
+					  struct probe_resp_params *params,
+					  u8 *pos, size_t len)
+{
+	u8 *csa_pos;
+	u8 *epos;
 
-	os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN);
+	epos = pos + len;
 
-	os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN);
-	resp->u.probe_resp.beacon_int =
-		host_to_le16(hapd->iconf->beacon_int);
-
-	/* hardware or low-level driver will setup seq_ctrl and timestamp */
-	resp->u.probe_resp.capab_info =
-		host_to_le16(hostapd_own_capab_info(hapd));
-
-	pos = resp->u.probe_resp.variable;
-	*pos++ = WLAN_EID_SSID;
-	*pos++ = hapd->conf->ssid.ssid_len;
-	os_memcpy(pos, hapd->conf->ssid.ssid, hapd->conf->ssid.ssid_len);
-	pos += hapd->conf->ssid.ssid_len;
+	if (!params->is_ml_sta_info) {
+		*pos++ = WLAN_EID_SSID;
+		*pos++ = hapd->conf->ssid.ssid_len;
+		os_memcpy(pos, hapd->conf->ssid.ssid,
+			  hapd->conf->ssid.ssid_len);
+		pos += hapd->conf->ssid.ssid_len;
+	}
 
 	/* Supported rates */
 	pos = hostapd_eid_supp_rates(hapd, pos);
@@ -686,11 +714,18 @@
 	/* Power Constraint element */
 	pos = hostapd_eid_pwr_constraint(hapd, pos);
 
-	/* CSA IE */
-	csa_pos = hostapd_eid_csa(hapd, pos);
-	if (csa_pos != pos)
-		hapd->cs_c_off_proberesp = csa_pos - (u8 *) resp - 1;
-	pos = csa_pos;
+	/*
+	 * CSA IE
+	 * TODO: This should be included inside the ML sta profile
+	 */
+	if (!params->is_ml_sta_info) {
+		csa_pos = hostapd_eid_csa(hapd, pos);
+		if (csa_pos != pos)
+			params->csa_pos = csa_pos - 1;
+		else
+			params->csa_pos = NULL;
+		pos = csa_pos;
+	}
 
 	/* ERP Information element */
 	pos = hostapd_eid_erp_info(hapd, pos);
@@ -701,16 +736,23 @@
 	pos = hostapd_get_rsne(hapd, pos, epos - pos);
 	pos = hostapd_eid_bss_load(hapd, pos, epos - pos);
 	pos = hostapd_eid_mbssid(hapd, pos, epos, WLAN_FC_STYPE_PROBE_RESP, 0,
-				 NULL, known_bss, known_bss_len, NULL, NULL,
-				 NULL, 0);
+				 NULL, params->known_bss, params->known_bss_len,
+				 NULL, NULL, NULL, 0);
 	pos = hostapd_eid_rm_enabled_capab(hapd, pos, epos - pos);
 	pos = hostapd_get_mde(hapd, pos, epos - pos);
 
-	/* eCSA IE */
-	csa_pos = hostapd_eid_ecsa(hapd, pos);
-	if (csa_pos != pos)
-		hapd->cs_c_off_ecsa_proberesp = csa_pos - (u8 *) resp - 1;
-	pos = csa_pos;
+	/*
+	 * eCSA IE
+	 * TODO: This should be included inside the ML sta profile
+	 */
+	if (!params->is_ml_sta_info) {
+		csa_pos = hostapd_eid_ecsa(hapd, pos);
+		if (csa_pos != pos)
+			params->ecsa_pos = csa_pos - 1;
+		else
+			params->ecsa_pos = NULL;
+		pos = csa_pos;
+	}
 
 	pos = hostapd_eid_supported_op_classes(hapd, pos);
 	pos = hostapd_eid_ht_capabilities(hapd, pos);
@@ -720,7 +762,7 @@
 	 * when a list of known BSSes is included in the Probe Request frame. */
 	pos = hostapd_eid_ext_capab(hapd, pos,
 				    hapd->iconf->mbssid >= MBSSID_ENABLED &&
-				    !known_bss_len);
+				    !params->known_bss_len);
 
 	pos = hostapd_eid_time_adv(hapd, pos);
 	pos = hostapd_eid_time_zone(hapd, pos);
@@ -754,7 +796,8 @@
 
 	pos = hostapd_eid_wb_chsw_wrapper(hapd, pos);
 
-	pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP);
+	if (!params->is_ml_sta_info)
+		pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP);
 	pos = hostapd_eid_fils_indic(hapd, pos, 0);
 	pos = hostapd_get_rsnxe(hapd, pos, epos - pos);
 
@@ -768,7 +811,9 @@
 		/* BSS Color Change Announcement element */
 		cca_pos = hostapd_eid_cca(hapd, pos);
 		if (cca_pos != pos)
-			hapd->cca_c_off_proberesp = cca_pos - (u8 *) resp - 2;
+			params->cca_pos = cca_pos - 2;
+		else
+			params->cca_pos = NULL;
 		pos = cca_pos;
 
 		pos = hostapd_eid_spatial_reuse(hapd, pos);
@@ -779,8 +824,14 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
-		if (hapd->conf->mld_ap)
-			pos = hostapd_eid_eht_basic_ml(hapd, pos, NULL, true);
+		struct hostapd_data *ml_elem_ap =
+			params->mld_ap ? params->mld_ap : hapd;
+
+		if (ml_elem_ap->conf->mld_ap)
+			pos = hostapd_eid_eht_ml_beacon(
+				ml_elem_ap, params->mld_info,
+				pos, !!params->mld_ap);
+
 		pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP);
 		pos = hostapd_eid_eht_operation(hapd, pos);
 	}
@@ -807,7 +858,7 @@
 #endif /* CONFIG_WPS */
 
 #ifdef CONFIG_P2P
-	if ((hapd->conf->p2p & P2P_ENABLED) && is_p2p &&
+	if ((hapd->conf->p2p & P2P_ENABLED) && params->is_p2p &&
 	    hapd->p2p_probe_resp_ie) {
 		os_memcpy(pos, wpabuf_head(hapd->p2p_probe_resp_ie),
 			  wpabuf_len(hapd->p2p_probe_resp_ie));
@@ -824,9 +875,9 @@
 	pos = hostapd_eid_hs20_indication(hapd, pos);
 #endif /* CONFIG_HS20 */
 
-	pos = hostapd_eid_mbo(hapd, pos, (u8 *) resp + buflen - pos);
-	pos = hostapd_eid_owe_trans(hapd, pos, (u8 *) resp + buflen - pos);
-	pos = hostapd_eid_dpp_cc(hapd, pos, (u8 *) resp + buflen - pos);
+	pos = hostapd_eid_mbo(hapd, pos, epos - pos);
+	pos = hostapd_eid_owe_trans(hapd, pos, epos - pos);
+	pos = hostapd_eid_dpp_cc(hapd, pos, epos - pos);
 
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements),
@@ -834,11 +885,171 @@
 		pos += wpabuf_len(hapd->conf->vendor_elements);
 	}
 
-	*resp_len = pos - (u8 *) resp;
-	return (u8 *) resp;
+	return pos;
 }
 
 
+static void hostapd_gen_probe_resp(struct hostapd_data *hapd,
+				   struct probe_resp_params *params)
+{
+	u8 *pos;
+	size_t buflen;
+
+	hapd = hostapd_mbssid_get_tx_bss(hapd);
+
+#define MAX_PROBERESP_LEN 768
+	buflen = MAX_PROBERESP_LEN;
+	buflen += hostapd_probe_resp_elems_len(hapd, params);
+	params->resp = os_zalloc(buflen);
+	if (!params->resp) {
+		params->resp_len = 0;
+		return;
+	}
+
+	params->resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+						   WLAN_FC_STYPE_PROBE_RESP);
+	/* Unicast the response to all requests on bands other than 6 GHz. For
+	 * the 6 GHz, unicast is used only if the actual SSID is not included in
+	 * the Beacon frames. Otherwise, broadcast response is used per IEEE
+	 * Std 802.11ax-2021, 26.17.2.3.2. Broadcast address is also used for
+	 * the Probe Response frame template for the unsolicited (i.e., not as
+	 * a response to a specific request) case. */
+	if (params->req && (!is_6ghz_op_class(hapd->iconf->op_class) ||
+		    hapd->conf->ignore_broadcast_ssid))
+		os_memcpy(params->resp->da, params->req->sa, ETH_ALEN);
+	else
+		os_memset(params->resp->da, 0xff, ETH_ALEN);
+	os_memcpy(params->resp->sa, hapd->own_addr, ETH_ALEN);
+
+	os_memcpy(params->resp->bssid, hapd->own_addr, ETH_ALEN);
+	params->resp->u.probe_resp.beacon_int =
+		host_to_le16(hapd->iconf->beacon_int);
+
+	/* hardware or low-level driver will setup seq_ctrl and timestamp */
+	params->resp->u.probe_resp.capab_info =
+		host_to_le16(hostapd_own_capab_info(hapd));
+
+	pos = hostapd_probe_resp_fill_elems(hapd, params,
+					    params->resp->u.probe_resp.variable,
+					    buflen);
+
+	params->resp_len = pos - (u8 *) params->resp;
+}
+
+
+#ifdef CONFIG_IEEE80211BE
+static void hostapd_fill_probe_resp_ml_params(struct hostapd_data *hapd,
+					      struct probe_resp_params *params,
+					      const struct ieee80211_mgmt *mgmt,
+					      int mld_id, u16 links)
+{
+	struct probe_resp_params sta_info_params;
+	struct hostapd_data *link;
+	unsigned int probed_mld_id, i, j;
+
+	params->mld_ap = NULL;
+	params->mld_info = os_zalloc(sizeof(*params->mld_info));
+	if (!params->mld_info)
+		return;
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Got ML probe request with AP MLD ID %d for links %04x",
+		   mld_id, links);
+
+	/*
+	 * 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;
+
+	for_each_mld_link(link, i, j, hapd->iface->interfaces,
+			  probed_mld_id) {
+		struct mld_link_info *link_info;
+		size_t buflen;
+		u8 mld_link_id = link->mld_link_id;
+		u8 *epos;
+		u8 buf[EHT_ML_MAX_STA_PROF_LEN];
+
+		/*
+		 * Set mld_ap iff the ML probe request explicitly
+		 * requested a specific MLD ID. In that case, the targeted
+		 * AP may have been a nontransmitted BSSID on the same
+		 * interface.
+		 */
+		if (mld_id != -1 && link->iface == hapd->iface)
+			params->mld_ap = link;
+
+		/* Never duplicate main Probe Response frame body */
+		if (link == hapd)
+			continue;
+
+		/* Only include requested links */
+		if (!(BIT(mld_link_id) & links))
+			continue;
+
+		link_info = &params->mld_info->links[mld_link_id];
+
+		sta_info_params.req = params->req;
+		sta_info_params.is_p2p = false;
+		sta_info_params.is_ml_sta_info = true;
+		sta_info_params.mld_ap = NULL;
+		sta_info_params.mld_info = NULL;
+
+		buflen = MAX_PROBERESP_LEN;
+		buflen += hostapd_probe_resp_elems_len(link, &sta_info_params);
+
+		if (buflen > EHT_ML_MAX_STA_PROF_LEN) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Not including link %d in ML probe response (%zu bytes is too long)",
+				   mld_link_id, buflen);
+			goto fail;
+		}
+
+		/*
+		 * NOTE: This does not properly handle inheritance and
+		 * various other things.
+		 */
+		link_info->valid = true;
+		epos = buf;
+
+		/* Capabilities is the only fixed parameter */
+		WPA_PUT_LE16(epos, hostapd_own_capab_info(hapd));
+		epos += 2;
+
+		epos = hostapd_probe_resp_fill_elems(
+			link, &sta_info_params, epos,
+			EHT_ML_MAX_STA_PROF_LEN - 2);
+		link_info->resp_sta_profile_len = epos - buf;
+		os_free(link_info->resp_sta_profile);
+		link_info->resp_sta_profile = os_memdup(
+			buf, link_info->resp_sta_profile_len);
+		if (!link_info->resp_sta_profile)
+			link_info->resp_sta_profile_len = 0;
+		os_memcpy(link_info->local_addr, link->own_addr, ETH_ALEN);
+
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe response includes link sta info for %d: %u bytes (estimate %zu)",
+			   mld_link_id, link_info->resp_sta_profile_len,
+			   buflen);
+	}
+
+	if (mld_id != -1 && !params->mld_ap) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: No nontransmitted BSSID for MLD ID %d",
+			   mld_id);
+		goto fail;
+	}
+
+	return;
+
+fail:
+	hostapd_free_probe_resp_params(params);
+	params->mld_ap = NULL;
+	params->mld_info = NULL;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 enum ssid_match_result {
 	NO_SSID_MATCH,
 	EXACT_SSID_MATCH,
@@ -953,7 +1164,7 @@
 	struct hostapd_sta_info *info;
 
 	dl_list_for_each(info, &iface->sta_seen, struct hostapd_sta_info, list)
-		if (os_memcmp(addr, info->addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, info->addr))
 			return info;
 
 	return NULL;
@@ -1037,21 +1248,109 @@
 #endif /* CONFIG_TAXONOMY */
 
 
+#ifdef CONFIG_IEEE80211BE
+static bool parse_ml_probe_req(const struct ieee80211_eht_ml *ml, size_t ml_len,
+			       int *mld_id, u16 *links)
+{
+	u16 ml_control;
+	const struct element *sub;
+	const u8 *pos;
+	size_t len;
+
+	*mld_id = -1;
+	*links = 0xffff;
+
+	if (ml_len < sizeof(struct ieee80211_eht_ml))
+		return false;
+
+	ml_control = le_to_host16(ml->ml_control);
+	if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_PROBE_REQ) {
+		wpa_printf(MSG_DEBUG, "MLD: Not an ML probe req");
+		return false;
+	}
+
+	if (sizeof(struct ieee80211_eht_ml) + 1 > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: ML probe req too short");
+		return false;
+	}
+
+	pos = ml->variable;
+	len = pos[0];
+	if (len < 1 || sizeof(struct ieee80211_eht_ml) + len > ml_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe request with invalid length");
+		return false;
+	}
+
+	if (ml_control & EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID) {
+		if (len < 2) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: ML probe req too short for MLD ID");
+			return false;
+		}
+
+		*mld_id = pos[1];
+	}
+	pos += len;
+
+	/* Parse subelements (if there are any) */
+	len = ml_len - len - sizeof(struct ieee80211_eht_ml);
+	for_each_element_id(sub, 0, pos, len) {
+		const struct ieee80211_eht_per_sta_profile *sta;
+		u16 sta_control;
+
+		if (*links == 0xffff)
+			*links = 0;
+
+		if (sub->datalen <
+		    sizeof(struct ieee80211_eht_per_sta_profile)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: ML probe req %d too short for sta profile",
+				   sub->datalen);
+			return false;
+		}
+
+		sta = (struct ieee80211_eht_per_sta_profile *) sub->data;
+
+		/*
+		 * Extract the link ID, do not return whether a complete or
+		 * partial profile was requested.
+		 */
+		sta_control = le_to_host16(sta->sta_control);
+		*links |= BIT(sta_control & EHT_PER_STA_CTRL_LINK_ID_MSK);
+	}
+
+	if (!for_each_element_completed(sub, pos, len)) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe req sub-elements parsing error");
+		return false;
+	}
+
+	return true;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 void handle_probe_req(struct hostapd_data *hapd,
 		      const struct ieee80211_mgmt *mgmt, size_t len,
 		      int ssi_signal)
 {
-	u8 *resp;
 	struct ieee802_11_elems elems;
 	const u8 *ie;
 	size_t ie_len;
-	size_t i, resp_len;
+	size_t i;
 	int noack;
 	enum ssid_match_result res;
 	int ret;
 	u16 csa_offs[2];
 	size_t csa_offs_len;
 	struct radius_sta rad_info;
+	struct probe_resp_params params;
+#ifdef CONFIG_IEEE80211BE
+	int mld_id;
+	u16 links;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (hapd->iconf->rssi_ignore_probe_request && ssi_signal &&
 	    ssi_signal < hapd->iconf->rssi_ignore_probe_request)
@@ -1217,7 +1516,7 @@
 		else
 			hessid = elems.interworking + 1 + 2;
 		if (!is_broadcast_ether_addr(hessid) &&
-		    os_memcmp(hessid, hapd->conf->hessid, ETH_ALEN) != 0) {
+		    !ether_addr_equal(hessid, hapd->conf->hessid)) {
 			wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR
 				   " for mismatching HESSID " MACSTR
 				   " ignored",
@@ -1283,10 +1582,28 @@
 	wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, RX_PROBE_REQUEST "sa=" MACSTR
 		     " signal=%d", MAC2STR(mgmt->sa), ssi_signal);
 
-	resp = hostapd_gen_probe_resp(hapd, mgmt, elems.p2p != NULL,
-				      &resp_len, elems.mbssid_known_bss,
-				      elems.mbssid_known_bss_len);
-	if (resp == NULL)
+	os_memset(&params, 0, sizeof(params));
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && elems.probe_req_mle &&
+	    parse_ml_probe_req((struct ieee80211_eht_ml *) elems.probe_req_mle,
+			       elems.probe_req_mle_len, &mld_id, &links)) {
+		hostapd_fill_probe_resp_ml_params(hapd, &params, mgmt,
+						  mld_id, links);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	params.req = mgmt;
+	params.is_p2p = !!elems.p2p;
+	params.known_bss = elems.mbssid_known_bss;
+	params.known_bss_len = elems.mbssid_known_bss_len;
+	params.is_ml_sta_info = false;
+
+	hostapd_gen_probe_resp(hapd, &params);
+
+	hostapd_free_probe_resp_params(&params);
+
+	if (!params.resp)
 		return;
 
 	/*
@@ -1298,24 +1615,23 @@
 
 	csa_offs_len = 0;
 	if (hapd->csa_in_progress) {
-		if (hapd->cs_c_off_proberesp)
+		if (params.csa_pos)
 			csa_offs[csa_offs_len++] =
-				hapd->cs_c_off_proberesp;
+				params.csa_pos - (u8 *) params.resp;
 
-		if (hapd->cs_c_off_ecsa_proberesp)
+		if (params.ecsa_pos)
 			csa_offs[csa_offs_len++] =
-				hapd->cs_c_off_ecsa_proberesp;
+				params.ecsa_pos - (u8 *) params.resp;
 	}
 
-	ret = hostapd_drv_send_mlme(hostapd_mbssid_get_tx_bss(hapd), resp,
-				    resp_len, noack,
+	ret = hostapd_drv_send_mlme(hapd, params.resp, params.resp_len, noack,
 				    csa_offs_len ? csa_offs : NULL,
 				    csa_offs_len, 0);
 
 	if (ret < 0)
 		wpa_printf(MSG_INFO, "handle_probe_req: send failed");
 
-	os_free(resp);
+	os_free(params.resp);
 
 	wpa_printf(MSG_EXCESSIVE, "STA " MACSTR " sent probe request for %s "
 		   "SSID", MAC2STR(mgmt->sa),
@@ -1326,6 +1642,8 @@
 static u8 * hostapd_probe_resp_offloads(struct hostapd_data *hapd,
 					size_t *resp_len)
 {
+	struct probe_resp_params params;
+
 	/* check probe response offloading caps and print warnings */
 	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_PROBE_RESP_OFFLOAD))
 		return NULL;
@@ -1355,7 +1673,32 @@
 			   "this");
 
 	/* Generate a Probe Response template for the non-P2P case */
-	return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0);
+	os_memset(&params, 0, sizeof(params));
+	params.req = NULL;
+	params.is_p2p = false;
+	params.known_bss = NULL;
+	params.known_bss_len = 0;
+	params.is_ml_sta_info = false;
+	params.mld_ap = NULL;
+	params.mld_info = NULL;
+
+	hostapd_gen_probe_resp(hapd, &params);
+	*resp_len = params.resp_len;
+	if (!params.resp)
+		return NULL;
+
+	/* TODO: Avoid passing these through struct hostapd_data */
+	if (params.csa_pos)
+		hapd->cs_c_off_proberesp = params.csa_pos - (u8 *) params.resp;
+	if (params.ecsa_pos)
+		hapd->cs_c_off_ecsa_proberesp = params.ecsa_pos -
+			(u8 *) params.resp;
+#ifdef CONFIG_IEEE80211AX
+	if (params.cca_pos)
+		hapd->cca_c_off_proberesp = params.cca_pos - (u8 *) params.resp;
+#endif /* CONFIG_IEEE80211AX */
+
+	return (u8 *) params.resp;
 }
 
 #endif /* NEED_AP_MLME */
@@ -1366,15 +1709,26 @@
 static u8 * hostapd_unsol_bcast_probe_resp(struct hostapd_data *hapd,
 					   struct wpa_driver_ap_params *params)
 {
+	struct probe_resp_params probe_params;
+
 	if (!is_6ghz_op_class(hapd->iconf->op_class))
 		return NULL;
 
 	params->unsol_bcast_probe_resp_interval =
 		hapd->conf->unsol_bcast_probe_resp_interval;
 
-	return hostapd_gen_probe_resp(hapd, NULL, 0,
-				      &params->unsol_bcast_probe_resp_tmpl_len,
-				      NULL, 0);
+	os_memset(&probe_params, 0, sizeof(probe_params));
+	probe_params.req = NULL;
+	probe_params.is_p2p = false;
+	probe_params.known_bss = NULL;
+	probe_params.known_bss_len = 0;
+	probe_params.is_ml_sta_info = false;
+	probe_params.mld_ap = NULL;
+	probe_params.mld_info = NULL;
+
+	hostapd_gen_probe_resp(hapd, &probe_params);
+	params->unsol_bcast_probe_resp_tmpl_len = probe_params.resp_len;
+	return (u8 *) probe_params.resp;
 }
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1606,14 +1960,9 @@
 	buf_len = pos - buf;
 	total_len += buf_len;
 
-#ifdef CONFIG_IEEE80211AX
-	/* Transmit Power Envelope element(s) */
-	if (is_6ghz_op_class(hapd->iconf->op_class)) {
-		total_len += 4;
-		if (hapd->iconf->he_6ghz_reg_pwr_type == HE_6GHZ_INDOOR_AP)
-			total_len += 4;
-	}
-#endif /* CONFIG_IEEE80211AX */
+	/* he_elem_len() may return too large a value for FD frame, but that is
+	 * fine here since this is used as the maximum length of the buffer. */
+	total_len += he_elem_len(hapd);
 
 	head = os_zalloc(total_len);
 	if (!head)
@@ -1763,23 +2112,7 @@
 	}
 #endif /* CONFIG_IEEE80211AC */
 
-#ifdef CONFIG_IEEE80211AX
-	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
-		tail_len += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation) +
-			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
-			3 + sizeof(struct ieee80211_spatial_reuse);
-		if (is_6ghz_op_class(hapd->iconf->op_class)) {
-			tail_len += sizeof(struct ieee80211_he_6ghz_oper_info) +
-				3 + sizeof(struct ieee80211_he_6ghz_band_cap);
-			 /* An additional Transmit Power Envelope element for
-			  * subordinate client */
-			if (hapd->iconf->he_6ghz_reg_pwr_type ==
-			    HE_6GHZ_INDOOR_AP)
-				tail_len += 4;
-		}
-	}
-#endif /* CONFIG_IEEE80211AX */
+	tail_len += he_elem_len(hapd);
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
@@ -1966,8 +2299,8 @@
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
 		if (hapd->conf->mld_ap)
-			tailpos = hostapd_eid_eht_basic_ml(hapd, tailpos, NULL,
-							   true);
+			tailpos = hostapd_eid_eht_ml_beacon(hapd, NULL,
+							    tailpos, false);
 		tailpos = hostapd_eid_eht_capab(hapd, tailpos,
 						IEEE80211_MODE_AP);
 		tailpos = hostapd_eid_eht_operation(hapd, tailpos);
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index a6fcb7e..5378671 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -830,6 +830,17 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+		if (is_6ghz_op_class(iface->conf->op_class) &&
+		    hostapd_get_oper_chwidth(iface->conf) ==
+		    CONF_OPER_CHWIDTH_320MHZ) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "eht_bw320_offset=%d\n",
+					  iface->conf->eht_bw320_offset);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1094,7 +1105,7 @@
 		return -1;
 
 	return wpa_auth_pmksa_add2(hapd->wpa_auth, spa, pmk, pmk_len,
-				   pmkid, expiration, akmp);
+				   pmkid, expiration, akmp, NULL);
 }
 
 
@@ -1315,6 +1326,8 @@
 		req_mode |= WNM_BSS_TM_REQ_ABRIDGED;
 	if (os_strstr(cmd, " disassoc_imminent=1"))
 		req_mode |= WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
+	if (os_strstr(cmd, " link_removal_imminent=1"))
+		req_mode |= WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT;
 
 #ifdef CONFIG_MBO
 	pos = os_strstr(cmd, "mbo=");
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 9a5d3c8..5e4c810 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -188,7 +188,7 @@
 	 * If it's not allowed to use the first channel as primary, decline the
 	 * whole channel range. */
 	if (!chan_pri_allowed(first_chan)) {
-		wpa_printf(MSG_DEBUG, "DFS: primary chanenl not allowed");
+		wpa_printf(MSG_DEBUG, "DFS: primary channel not allowed");
 		return 0;
 	}
 
@@ -551,6 +551,8 @@
 	if (os_get_random((u8 *) &_rand, sizeof(_rand)) < 0)
 		return NULL;
 	chan_idx = _rand % num_available_chandefs;
+	wpa_printf(MSG_DEBUG, "DFS: Picked random entry from the list: %d/%d",
+		   chan_idx, num_available_chandefs);
 	dfs_find_channel(iface, &chan, chan_idx, type);
 	if (!chan) {
 		wpa_printf(MSG_DEBUG, "DFS: no random channel found");
@@ -983,6 +985,11 @@
 	os_memset(&csa_settings, 0, sizeof(csa_settings));
 	csa_settings.cs_count = 5;
 	csa_settings.block_tx = 1;
+	csa_settings.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (iface->bss[0]->conf->mld_ap)
+		csa_settings.link_id = iface->bss[0]->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 #ifdef CONFIG_MESH
 	if (iface->mconf)
 		ieee80211_mode = IEEE80211_MODE_MESH;
@@ -1044,7 +1051,7 @@
 }
 
 
-static void hostpad_dfs_update_background_chain(struct hostapd_iface *iface)
+static void hostapd_dfs_update_background_chain(struct hostapd_iface *iface)
 {
 	int sec = 0;
 	enum dfs_channel_type channel_type = DFS_NO_CAC_YET;
@@ -1119,7 +1126,7 @@
 	hostapd_set_oper_centr_freq_seg1_idx(
 		iface->conf, iface->radar_background.centr_freq_seg1_idx);
 
-	hostpad_dfs_update_background_chain(iface);
+	hostapd_dfs_update_background_chain(iface);
 
 	return hostapd_dfs_request_channel_switch(
 		iface, iface->conf->channel, iface->freq,
@@ -1183,7 +1190,7 @@
 		}
 	} else if (hostapd_dfs_is_background_event(iface, freq)) {
 		iface->radar_background.cac_started = 0;
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 	}
 
 	return 0;
@@ -1317,7 +1324,7 @@
 		 * Just select a new random channel according to the
 		 * regulations for monitoring.
 		 */
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 		return 0;
 	}
 
@@ -1479,7 +1486,7 @@
 	} else if (dfs_use_radar_background(iface) &&
 		   iface->radar_background.channel == -1) {
 		/* Reset radar background chain if disabled */
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 	}
 
 	return 0;
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 7a8ea4e..3f89bc2 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -539,8 +539,15 @@
 		return;
 	}
 
-	if (hapd->dpp_auth_ok_on_ack)
+	if (hapd->dpp_auth_ok_on_ack) {
 		hostapd_dpp_auth_success(hapd, 1);
+		if (!hapd->dpp_auth) {
+			/* The authentication session could have been removed in
+			 * some error cases, e.g., when starting GAS client and
+			 * failing to send the initial request. */
+			return;
+		}
+	}
 
 	if (!is_broadcast_ether_addr(dst) && !ok) {
 		wpa_printf(MSG_DEBUG,
@@ -1413,7 +1420,7 @@
 	}
 
 	if (!is_zero_ether_addr(auth->peer_mac_addr) &&
-	    os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1463,7 +1470,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1572,7 +1579,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1858,7 +1865,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2133,7 +2140,7 @@
 	else
 		expiration = 0;
 
-	if (wpa_auth_pmksa_add3(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
+	if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
 				intro.pmkid, expiration,
 				WPA_KEY_MGMT_DPP, pkhash) < 0) {
 		wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry");
@@ -2907,7 +2914,7 @@
 	else
 		expiration = 0;
 
-	if (wpa_auth_pmksa_add3(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
+	if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
 				intro.pmkid, expiration,
 				WPA_KEY_MGMT_DPP, pkhash) < 0) {
 		wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry");
@@ -3073,7 +3080,7 @@
 
 	wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR, MAC2STR(sa));
 	if (!auth || (!auth->auth_success && !auth->reconfig_success) ||
-	    os_memcmp(sa, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sa, auth->peer_mac_addr)) {
 #ifdef CONFIG_DPP2
 		if (dpp_relay_rx_gas_req(hapd->iface->interfaces->dpp, sa, data,
 				     data_len) == 0) {
@@ -3094,6 +3101,13 @@
 		 * exchange. */
 		dpp_notify_auth_success(hapd->dpp_auth, 1);
 		hapd->dpp_auth_ok_on_ack = 0;
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) {
+			wpa_printf(MSG_INFO,
+				   "DPP: TESTING - stop at Authentication Confirm");
+			return NULL;
+		}
+#endif /* CONFIG_TESTING_OPTIONS */
 	}
 
 	wpa_hexdump(MSG_DEBUG,
@@ -3948,11 +3962,25 @@
 	ifaces->dpp_pb_time.usec = 0;
 	dpp_pkex_free(hapd->dpp_pkex);
 	hapd->dpp_pkex = NULL;
+	hapd->dpp_pkex_bi = NULL;
 	os_free(hapd->dpp_pkex_auth_cmd);
 	hapd->dpp_pkex_auth_cmd = NULL;
 
 	if (ifaces->dpp_pb_bi) {
 		char id[20];
+		size_t i;
+
+		for (i = 0; i < ifaces->count; i++) {
+			struct hostapd_iface *iface = ifaces->iface[i];
+			size_t j;
+
+			for (j = 0; iface && j < iface->num_bss; j++) {
+				struct hostapd_data *h = iface->bss[j];
+
+				if (h->dpp_pkex_bi == ifaces->dpp_pb_bi)
+					h->dpp_pkex_bi = NULL;
+			}
+		}
 
 		os_snprintf(id, sizeof(id), "%u", ifaces->dpp_pb_bi->id);
 		dpp_bootstrap_remove(ifaces->dpp, id);
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 98794c2..533cc54 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -42,6 +42,7 @@
 #include "dpp_hostapd.h"
 #include "fils_hlp.h"
 #include "neighbor_db.h"
+#include "nan_usd_ap.h"
 
 
 #ifdef CONFIG_FILS
@@ -52,6 +53,7 @@
 	struct ieee802_11_elems elems;
 	u8 buf[IEEE80211_MAX_MMPDU_SIZE], *p = buf;
 	int new_assoc;
+	bool updated;
 
 	wpa_printf(MSG_DEBUG, "%s FILS: Finish association with " MACSTR,
 		   __func__, MAC2STR(sta->addr));
@@ -76,11 +78,13 @@
 				      sta->fils_pending_assoc_is_reassoc,
 				      WLAN_STATUS_SUCCESS,
 				      buf, p - buf);
-	ap_sta_set_authorized(hapd, sta, 1);
+	updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 	wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS);
 	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
 	hostapd_new_assoc_sta(hapd, sta, !new_assoc);
@@ -158,7 +162,7 @@
 		return -1;
 	}
 
-	mlebuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlebuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlebuf) {
 		wpa_printf(MSG_ERROR,
 			   "MLO: Basic Multi-Link element not found in (Re)Association Response frame");
@@ -263,6 +267,7 @@
 #ifdef CONFIG_OWE
 	struct hostapd_iface *iface = hapd->iface;
 #endif /* CONFIG_OWE */
+	bool updated = false;
 
 	if (addr == NULL) {
 		/*
@@ -279,7 +284,7 @@
 
 	if (is_multicast_ether_addr(addr) ||
 	    is_zero_ether_addr(addr) ||
-	    os_memcmp(addr, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(addr, hapd->own_addr)) {
 		/* Do not process any frames with unexpected/invalid SA so that
 		 * we do not add any state for unexpected STA addresses or end
 		 * up sending out frames to unexpected destination. */
@@ -358,7 +363,7 @@
 		int i, num_valid_links = 0;
 		u8 link_id = hapd->mld_link_id;
 
-		info->mld_sta = true;
+		ap_sta_set_mld(sta, true);
 		sta->mld_assoc_link_id = link_id;
 		os_memcpy(info->common_info.mld_addr, addr, ETH_ALEN);
 		info->links[link_id].valid = true;
@@ -509,7 +514,7 @@
 			return -1;
 		}
 #ifdef CONFIG_IEEE80211BE
-		if (sta->mld_info.mld_sta) {
+		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,
@@ -845,18 +850,30 @@
 	    sta->auth_alg == WLAN_AUTH_FILS_SK ||
 	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
 	    sta->auth_alg == WLAN_AUTH_FILS_PK)
-		ap_sta_set_authorized(hapd, sta, 1);
+		updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 #else /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 	/* Keep compiler silent about unused variables */
 	if (status) {
 	}
 #endif /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 
+#ifdef CONFIG_IEEE80211BE
+	if (hostapd_process_assoc_ml_info(hapd, sta, req_ies, req_ies_len,
+					  !!reassoc, WLAN_STATUS_SUCCESS,
+					  true)) {
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		reason = WLAN_REASON_UNSPECIFIED;
+		goto fail;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 
 	if (reassoc && (sta->auth_alg == WLAN_AUTH_FT))
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FT);
@@ -1164,6 +1181,8 @@
 	hostapd_set_oper_chwidth(hapd->iconf, chwidth);
 	hostapd_set_oper_centr_freq_seg0_idx(hapd->iconf, seg0_idx);
 	hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, seg1_idx);
+	/* Auto-detect new bw320_offset */
+	hostapd_set_and_check_bw320_offset(hapd->iconf, 0);
 #ifdef CONFIG_IEEE80211BE
 	hapd->iconf->punct_bitmap = punct_bitmap;
 #endif /* CONFIG_IEEE80211BE */
@@ -1270,6 +1289,18 @@
 	int err = 0;
 	struct hostapd_channel_data *pri_chan;
 
+#ifdef CONFIG_IEEE80211BE
+	if (acs_res->link_id != -1) {
+		hapd = hostapd_mld_get_link_bss(hapd, acs_res->link_id);
+		if (!hapd) {
+			wpa_printf(MSG_ERROR,
+				   "MLD: Failed to get link BSS for EVENT_ACS_CHANNEL_SELECTED link_id=%d",
+				   acs_res->link_id);
+			return;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (hapd->iconf->channel) {
 		wpa_printf(MSG_INFO, "ACS: Channel was already set to %d",
 			   hapd->iconf->channel);
@@ -1576,6 +1607,7 @@
 #endif /* CONFIG_FST */
 #ifdef CONFIG_DPP
 	if (plen >= 2 + 4 &&
+	    mgmt->u.action.category == WLAN_ACTION_PUBLIC &&
 	    mgmt->u.action.u.vs_public_action.action ==
 	    WLAN_PA_VENDOR_SPECIFIC &&
 	    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
@@ -1591,6 +1623,23 @@
 		return;
 	}
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+	if (mgmt->u.action.category == WLAN_ACTION_PUBLIC && plen >= 5 &&
+	    mgmt->u.action.u.vs_public_action.action ==
+	    WLAN_PA_VENDOR_SPECIFIC &&
+	    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
+	    OUI_WFA &&
+	    mgmt->u.action.u.vs_public_action.variable[0] == NAN_OUI_TYPE) {
+		const u8 *pos, *end;
+
+		pos = mgmt->u.action.u.vs_public_action.variable;
+		end = drv_mgmt->frame + drv_mgmt->frame_len;
+		pos++;
+		hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, drv_mgmt->freq,
+				       pos, end - pos);
+		return;
+	}
+#endif /* CONFIG_NAN_USD */
 }
 #endif /* NEED_AP_MLME */
 
@@ -1628,7 +1677,7 @@
 		return HAPD_BROADCAST;
 
 	for (i = 0; i < iface->num_bss; i++) {
-		if (os_memcmp(bssid, iface->bss[i]->own_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(bssid, iface->bss[i]->own_addr))
 			return iface->bss[i];
 	}
 
@@ -1682,7 +1731,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->conf->mld_ap &&
-	    os_memcmp(hapd->mld_addr, bssid, ETH_ALEN) == 0)
+	    ether_addr_equal(hapd->mld_addr, bssid))
 		is_mld = true;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1754,8 +1803,7 @@
 		hapd = tmp_hapd;
 #ifdef CONFIG_IEEE80211BE
 	} else if (hapd->conf->mld_ap &&
-		   os_memcmp(hapd->mld_addr, get_hdr_bssid(hdr, len),
-			     ETH_ALEN) == 0) {
+		   ether_addr_equal(hapd->mld_addr, get_hdr_bssid(hdr, len))) {
 		/* AP MLD address match - use hapd pointer as-is */
 #endif /* CONFIG_IEEE80211BE */
 	} else {
@@ -1803,14 +1851,15 @@
 
 
 static struct hostapd_data * hostapd_find_by_sta(struct hostapd_iface *iface,
-						 const u8 *src)
+						 const u8 *src, bool rsn)
 {
 	struct sta_info *sta;
 	unsigned int j;
 
 	for (j = 0; j < iface->num_bss; j++) {
 		sta = ap_get_sta(iface->bss[j], src);
-		if (sta && sta->flags & WLAN_STA_ASSOC)
+		if (sta && (sta->flags & WLAN_STA_ASSOC) &&
+		    (!rsn || sta->wpa_sm))
 			return iface->bss[j];
 	}
 
@@ -1818,6 +1867,40 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static bool search_mld_sta(struct hostapd_data **p_hapd, const u8 *src)
+{
+	struct hostapd_data *hapd = *p_hapd;
+	unsigned int i;
+
+	/* Search for STA on other MLO BSSs */
+	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;
+
+		h_hapd = hostapd_find_by_sta(h, src, false);
+		if (h_hapd) {
+			struct sta_info *sta = ap_get_sta(h_hapd, src);
+
+			if (sta && sta->mld_info.mld_sta &&
+			    sta->mld_assoc_link_id != h_hapd->mld_link_id)
+				continue;
+			*p_hapd = h_hapd;
+			return true;
+		}
+	}
+
+	return false;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
 				   const u8 *data, size_t data_len,
 				   enum frame_encryption encrypted,
@@ -1830,36 +1913,24 @@
 		struct hostapd_data *h_hapd;
 
 		hapd = switch_link_hapd(hapd, link_id);
-		h_hapd = hostapd_find_by_sta(hapd->iface, src);
+		h_hapd = hostapd_find_by_sta(hapd->iface, src, true);
 		if (!h_hapd)
-			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src);
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src,
+						     true);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(hapd->iface, src, false);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src,
+						     false);
 		if (h_hapd)
 			hapd = h_hapd;
 	} else if (hapd->conf->mld_ap) {
-		unsigned int i;
-
-		/* Search for STA on other MLO BSSs */
-		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;
-
-			h_hapd = hostapd_find_by_sta(h, src);
-			if (h_hapd) {
-				hapd = h_hapd;
-				break;
-			}
-		}
+		search_mld_sta(&hapd, src);
 	} else {
-		hapd = hostapd_find_by_sta(hapd->iface, src);
+		hapd = hostapd_find_by_sta(hapd->iface, src, false);
 	}
 #else /* CONFIG_IEEE80211BE */
-	hapd = hostapd_find_by_sta(hapd->iface, src);
+	hapd = hostapd_find_by_sta(hapd->iface, src, false);
 #endif /* CONFIG_IEEE80211BE */
 
 	if (!hapd) {
@@ -2164,8 +2235,8 @@
 		struct mld_info *info = &sta->mld_info;
 		u8 link_id = hapd->mld_link_id;
 
-		info->mld_sta = true;
-		sta->mld_assoc_link_id = link_id;;
+		ap_sta_set_mld(sta, true);
+		sta->mld_assoc_link_id = link_id;
 		os_memcpy(info->common_info.mld_addr, peer, ETH_ALEN);
 		info->links[link_id].valid = true;
 		os_memcpy(info->links[link_id].local_addr, hapd->own_addr,
@@ -2364,6 +2435,18 @@
 	case EVENT_CH_SWITCH:
 		if (!data)
 			break;
+#ifdef CONFIG_IEEE80211BE
+		if (data->ch_switch.link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->ch_switch.link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link (ID %d) BSS for EVENT_CH_SWITCH/EVENT_CH_SWITCH_STARTED",
+					   data->ch_switch.link_id);
+				break;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_event_ch_switch(hapd, data->ch_switch.freq,
 					data->ch_switch.ht_enabled,
 					data->ch_switch.ch_offset,
@@ -2390,26 +2473,31 @@
 	case EVENT_DFS_RADAR_DETECTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_radar_detected(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_PRE_CAC_EXPIRED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_pre_cac_expired(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_CAC_FINISHED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_finished(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_CAC_ABORTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_NOP_FINISHED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_nop_finished(hapd, &data->dfs_event);
 		break;
 	case EVENT_CHANNEL_LIST_CHANGED:
@@ -2423,6 +2511,7 @@
 	case EVENT_DFS_CAC_STARTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_started(hapd, &data->dfs_event);
 		break;
 #endif /* NEED_AP_MLME */
diff --git a/src/ap/fils_hlp.c b/src/ap/fils_hlp.c
index d64fb8c..a34b5ba 100644
--- a/src/ap/fils_hlp.c
+++ b/src/ap/fils_hlp.c
@@ -546,7 +546,7 @@
 		   " src=" MACSTR " len=%u)",
 		   MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
 		   (unsigned int) len);
-	if (os_memcmp(sta->addr, pos + ETH_ALEN, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sta->addr, pos + ETH_ALEN)) {
 		wpa_printf(MSG_DEBUG,
 			   "FILS: Ignore HLP request with unexpected source address"
 			   MACSTR, MAC2STR(pos + ETH_ALEN));
diff --git a/src/ap/gas_query_ap.c b/src/ap/gas_query_ap.c
index 3d94407..a471c79 100644
--- a/src/ap/gas_query_ap.c
+++ b/src/ap/gas_query_ap.c
@@ -185,7 +185,7 @@
 {
 	struct gas_query_pending *q;
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(q->addr, addr) &&
 		    q->dialog_token == dialog_token)
 			return q;
 	}
@@ -223,7 +223,7 @@
 	wpa_printf(MSG_DEBUG, "GAS: TX status: dst=" MACSTR
 		   " ok=%d query=%p dialog_token=%u dur=%d ms",
 		   MAC2STR(dst), ok, query, query->dialog_token, dur);
-	if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(dst, query->addr)) {
 		wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination");
 		return;
 	}
@@ -618,7 +618,7 @@
 {
 	struct gas_query_pending *q;
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(dst, q->addr) &&
 		    dialog_token == q->dialog_token)
 			return 0;
 	}
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 236381f..ddbcabc 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -35,6 +35,7 @@
 #include "wpa_auth.h"
 #include "wps_hostapd.h"
 #include "dpp_hostapd.h"
+#include "nan_usd_ap.h"
 #include "gas_query_ap.h"
 #include "hw_features.h"
 #include "wpa_auth_glue.h"
@@ -413,6 +414,61 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+#ifdef CONFIG_TESTING_OPTIONS
+
+#define TU_TO_USEC(_val) ((_val) * 1024)
+
+static void hostapd_link_remove_timeout_handler(void *eloop_data,
+						void *user_ctx)
+{
+	struct hostapd_data *hapd = (struct hostapd_data *) eloop_data;
+
+	if (hapd->eht_mld_link_removal_count == 0)
+		return;
+	hapd->eht_mld_link_removal_count--;
+
+	wpa_printf(MSG_DEBUG, "MLD: Remove link_id=%u in %u beacons",
+		   hapd->mld_link_id,
+		   hapd->eht_mld_link_removal_count);
+
+	ieee802_11_set_beacon(hapd);
+
+	if (!hapd->eht_mld_link_removal_count) {
+		hostapd_disable_iface(hapd->iface);
+		return;
+	}
+
+	eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int),
+			       hostapd_link_remove_timeout_handler,
+			       hapd, NULL);
+}
+
+
+int hostapd_link_remove(struct hostapd_data *hapd, u32 count)
+{
+	if (!hapd->conf->mld_ap)
+		return -1;
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Remove link_id=%u in %u beacons",
+		   hapd->mld_link_id, count);
+
+	hapd->eht_mld_link_removal_count = count;
+	hapd->eht_mld_bss_param_change++;
+
+	eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int),
+			       hostapd_link_remove_timeout_handler,
+			       hapd, NULL);
+
+	ieee802_11_set_beacon(hapd);
+	return 0;
+}
+
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+
 void hostapd_free_hapd_data(struct hostapd_data *hapd)
 {
 	os_free(hapd->probereq_cb);
@@ -441,6 +497,24 @@
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
 	if (!hapd->mld_first_bss) {
+		struct hapd_interfaces *ifaces = hapd->iface->interfaces;
+		size_t i;
+
+		for (i = 0; i < ifaces->count; i++) {
+			struct hostapd_iface *iface = ifaces->iface[i];
+			size_t j;
+
+			for (j = 0; iface && j < iface->num_bss; j++) {
+				struct hostapd_data *h = iface->bss[j];
+
+				if (hapd == h)
+					continue;
+				if (h->radius == hapd->radius)
+					h->radius = NULL;
+				if (h->radius_das == hapd->radius_das)
+					h->radius_das = NULL;
+			}
+		}
 		radius_client_deinit(hapd->radius);
 		radius_das_deinit(hapd->radius_das);
 	}
@@ -455,6 +529,9 @@
 	gas_query_ap_deinit(hapd->gas);
 	hapd->gas = NULL;
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+	hostapd_nan_usd_deinit(hapd);
+#endif /* CONFIG_NAN_USD */
 
 	authsrv_deinit(hapd);
 
@@ -502,7 +579,9 @@
 	hapd->setup_complete_cb = NULL;
 #endif /* CONFIG_MESH */
 
+#ifndef CONFIG_NO_RRM
 	hostapd_clean_rrm(hapd);
+#endif /* CONFIG_NO_RRM */
 	fils_hlp_deinit(hapd);
 
 #ifdef CONFIG_OCV
@@ -525,6 +604,12 @@
 
 #ifdef CONFIG_IEEE80211AX
 	eloop_cancel_timeout(hostapd_switch_color_timeout_handler, hapd, NULL);
+#ifdef CONFIG_TESTING_OPTIONS
+#ifdef CONFIG_IEEE80211BE
+	eloop_cancel_timeout(hostapd_link_remove_timeout_handler, hapd, NULL);
+#endif /* CONFIG_IEEE80211BE */
+#endif /* CONFIG_TESTING_OPTIONS */
+
 #endif /* CONFIG_IEEE80211AX */
 }
 
@@ -1435,6 +1520,11 @@
 		return -1;
 #endif /* CONFIG_DPP */
 
+#ifdef CONFIG_NAN_USD
+	if (hostapd_nan_usd_init(hapd) < 0)
+		return -1;
+#endif /* CONFIG_NAN_USD */
+
 	if (authsrv_init(hapd) < 0)
 		return -1;
 
@@ -3117,6 +3207,31 @@
 		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;
@@ -3536,7 +3651,7 @@
 	}
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	if (ap_sta_is_mld(hapd, sta) &&
 	    sta->mld_assoc_link_id != hapd->mld_link_id)
 		return;
 #endif /* CONFIG_IEEE80211BE */
@@ -3768,7 +3883,7 @@
 				      struct hostapd_freq_params *old_params)
 {
 	int channel;
-	u8 seg0, seg1;
+	u8 seg0 = 0, seg1 = 0;
 	struct hostapd_hw_modes *mode;
 
 	if (!params->channel) {
@@ -3844,10 +3959,14 @@
 	conf->ieee80211n = params->ht_enabled;
 	conf->ieee80211ac = params->vht_enabled;
 	conf->secondary_channel = params->sec_channel_offset;
-	ieee80211_freq_to_chan(params->center_freq1,
-			       &seg0);
-	ieee80211_freq_to_chan(params->center_freq2,
-			       &seg1);
+	if (params->center_freq1 &&
+	    ieee80211_freq_to_chan(params->center_freq1, &seg0) ==
+	    NUM_HOSTAPD_MODES)
+		return -1;
+	if (params->center_freq2 &&
+	    ieee80211_freq_to_chan(params->center_freq2,
+				   &seg1) == NUM_HOSTAPD_MODES)
+		return -1;
 	hostapd_set_oper_centr_freq_seg0_idx(conf, seg0);
 	hostapd_set_oper_centr_freq_seg1_idx(conf, seg1);
 
@@ -3945,6 +4064,11 @@
 	settings->counter_offset_presp[0] = hapd->cs_c_off_proberesp;
 	settings->counter_offset_beacon[1] = hapd->cs_c_off_ecsa_beacon;
 	settings->counter_offset_presp[1] = hapd->cs_c_off_ecsa_proberesp;
+	settings->link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		settings->link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	return 0;
 }
@@ -4040,13 +4164,17 @@
 		bw = CONF_OPER_CHWIDTH_USE_HT;
 		break;
 	case 80:
-		if (freq_params->center_freq2)
+		if (freq_params->center_freq2) {
 			bw = CONF_OPER_CHWIDTH_80P80MHZ;
-		else
+			iface->conf->vht_capab |=
+				VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ;
+		} else {
 			bw = CONF_OPER_CHWIDTH_80MHZ;
+		}
 		break;
 	case 160:
 		bw = CONF_OPER_CHWIDTH_160MHZ;
+		iface->conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
 		break;
 	case 320:
 		bw = CONF_OPER_CHWIDTH_320MHZ;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 7f703be..bcf980f 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -469,6 +469,17 @@
 #ifdef CONFIG_CTRL_IFACE_UDP
        unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN];
 #endif /* CONFIG_CTRL_IFACE_UDP */
+
+#ifdef CONFIG_IEEE80211BE
+	u8 eht_mld_bss_param_change;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 eht_mld_link_removal_count;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+#ifdef CONFIG_NAN_USD
+	struct nan_de *nan_de;
+#endif /* CONFIG_NAN_USD */
 };
 
 
@@ -771,5 +782,25 @@
 int hostapd_mbssid_get_bss_index(struct hostapd_data *hapd);
 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);
+
+#ifdef CONFIG_IEEE80211BE
+#define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
+	for (_iface_idx = 0;						\
+	     _iface_idx < (_ifaces)->count;				\
+	     _iface_idx++)						\
+		for (_bss_idx = 0;					\
+		     _bss_idx <						\
+			(_ifaces)->iface[_iface_idx]->num_bss;		\
+		     _bss_idx++)					\
+			for (_link =					\
+			     (_ifaces)->iface[_iface_idx]->bss[_bss_idx]; \
+			    _link && _link->conf->mld_ap &&		\
+				_link->conf->mld_id == _mld_id;		\
+			    _link = NULL)
+#else /* CONFIG_IEEE80211BE */
+#define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
+	if (false)
+#endif /* CONFIG_IEEE80211BE */
 
 #endif /* HOSTAPD_H */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 9edbb5a..596f2f0 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -1001,7 +1001,7 @@
 {
 	int secondary_freq;
 	struct hostapd_channel_data *pri_chan;
-	int err;
+	int err, err2;
 
 	if (!iface->current_mode)
 		return 0;
@@ -1044,15 +1044,15 @@
 
 	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
 	secondary_freq = iface->freq + 20;
-	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
-	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
+	err2 = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
 		iface->conf->secondary_channel = 1;
 		return 1;
 	}
 
 	secondary_freq = iface->freq - 20;
-	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
-	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
+	err2 = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
 		iface->conf->secondary_channel = -1;
 		return 1;
 	}
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 1f39107..8b8c1f0 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -56,6 +56,7 @@
 #include "dpp_hostapd.h"
 #include "gas_query_ap.h"
 #include "comeback_token.h"
+#include "nan_usd_ap.h"
 #include "pasn/pasn_common.h"
 
 
@@ -407,7 +408,7 @@
 	 * the MLD MAC address. Thus, use the MLD address instead of translating
 	 * the addresses.
 	 */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		sa = hapd->mld_addr;
 
 		ml_resp = hostapd_ml_auth_resp(hapd);
@@ -556,7 +557,7 @@
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
 		    (!sta ||
-		     os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0))
+		     !ether_addr_equal(pw->peer_addr, sta->addr)))
 			continue;
 		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
 			continue;
@@ -608,7 +609,7 @@
 	const u8 *own_addr = hapd->own_addr;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		own_addr = hapd->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -877,7 +878,7 @@
 	params.status = status;
 
 #ifdef CONFIG_IEEE80211BE
-	if (sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		params.bssid =
 			sta->mld_info.links[sta->mld_assoc_link_id].peer_addr;
 #endif /* CONFIG_IEEE80211BE */
@@ -902,23 +903,27 @@
 			   " to VLAN ID %d",
 			   MAC2STR(sta->addr), sta->sae->tmp->vlan_id);
 
-		os_memset(&vlan_desc, 0, sizeof(vlan_desc));
-		vlan_desc.notempty = 1;
-		vlan_desc.untagged = sta->sae->tmp->vlan_id;
-		if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
-			wpa_printf(MSG_INFO,
-				   "Invalid VLAN ID %d in sae_password",
-				   sta->sae->tmp->vlan_id);
-			return;
-		}
+		if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) {
+			os_memset(&vlan_desc, 0, sizeof(vlan_desc));
+			vlan_desc.notempty = 1;
+			vlan_desc.untagged = sta->sae->tmp->vlan_id;
+			if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
+				wpa_printf(MSG_INFO,
+					   "Invalid VLAN ID %d in sae_password",
+					   sta->sae->tmp->vlan_id);
+				return;
+			}
 
-		if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 ||
-		    ap_sta_bind_vlan(hapd, sta) < 0) {
-			wpa_printf(MSG_INFO,
-				   "Failed to assign VLAN ID %d from sae_password to "
-				   MACSTR, sta->sae->tmp->vlan_id,
-				   MAC2STR(sta->addr));
-			return;
+			if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 ||
+			    ap_sta_bind_vlan(hapd, sta) < 0) {
+				wpa_printf(MSG_INFO,
+					   "Failed to assign VLAN ID %d from sae_password to "
+					   MACSTR, sta->sae->tmp->vlan_id,
+					   MAC2STR(sta->addr));
+				return;
+			}
+		} else {
+			sta->vlan_id = sta->sae->tmp->vlan_id;
 		}
 	}
 #endif /* CONFIG_NO_VLAN */
@@ -1273,7 +1278,8 @@
 		pos = mgmt->u.auth.variable;
 		end = ((const u8 *) mgmt) + len;
 		resp = status_code;
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp, pos, end - pos,
 				"auth-sae-reflection-attack");
 		goto remove_sta;
@@ -1281,7 +1287,8 @@
 
 	if (hapd->conf->sae_commit_override && auth_transaction == 1) {
 		wpa_printf(MSG_DEBUG, "SAE: TESTING - commit override");
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp,
 				wpabuf_head(hapd->conf->sae_commit_override),
 				wpabuf_len(hapd->conf->sae_commit_override),
@@ -1552,7 +1559,8 @@
 			data = wpabuf_alloc_copy(pos, 2);
 
 		sae_sme_send_external_auth_status(hapd, sta, resp);
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp,
 				data ? wpabuf_head(data) : (u8 *) "",
 				data ? wpabuf_len(data) : 0, "auth-sae");
@@ -1664,7 +1672,7 @@
 	dl_list_for_each(q2, &hapd->sae_commit_queue,
 			 struct hostapd_sae_commit_queue, list) {
 		mgmt2 = (const struct ieee80211_mgmt *) q2->msg;
-		if (os_memcmp(mgmt->sa, mgmt2->sa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(mgmt->sa, mgmt2->sa) &&
 		    mgmt->u.auth.auth_transaction ==
 		    mgmt2->u.auth.auth_transaction) {
 			wpa_printf(MSG_DEBUG,
@@ -1695,7 +1703,7 @@
 	dl_list_for_each(q, &hapd->sae_commit_queue,
 			 struct hostapd_sae_commit_queue, list) {
 		mgmt = (const struct ieee80211_mgmt *) q->msg;
-		if (os_memcmp(addr, mgmt->sa, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, mgmt->sa))
 			return 1;
 	}
 
@@ -2021,7 +2029,7 @@
 		}
 
 		os_memcpy(ie_buf, ie, ielen);
-		if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid) < 0) {
+		if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid, true) < 0) {
 			*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto fail;
 		}
@@ -2149,7 +2157,8 @@
 				    pmk, pmk_len,
 				    sta->fils_erp_pmkid,
 				    session_timeout,
-				    wpa_auth_sta_key_mgmt(sta->wpa_sm)) < 0) {
+				    wpa_auth_sta_key_mgmt(sta->wpa_sm),
+				    NULL) < 0) {
 				wpa_printf(MSG_ERROR,
 					   "FILS: Failed to add PMKSA cache entry based on ERP");
 			}
@@ -2520,8 +2529,8 @@
 		    FILS_SESSION_LEN);
 	os_memcpy(fils->session, elems.fils_session, FILS_SESSION_LEN);
 
-	fils_wd = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION,
-				    WLAN_EID_EXT_WRAPPED_DATA);
+	fils_wd = ieee802_11_defrag(elems.wrapped_data, elems.wrapped_data_len,
+				    true);
 
 	if (!fils_wd) {
 		wpa_printf(MSG_DEBUG, "PASN: FILS: Missing wrapped data");
@@ -2612,7 +2621,7 @@
 		return -1;
 	}
 
-	if (os_memcmp(entry->own_addr, own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(entry->own_addr, own_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "PASN: own addr " MACSTR " and PTKSA entry own addr "
 			   MACSTR " differ",
@@ -2639,8 +2648,10 @@
 	struct pasn_data *pasn = sta->pasn;
 	struct ieee802_11_elems elems;
 	struct wpa_ie_data rsn_data;
+#ifdef CONFIG_FILS
 	struct wpa_pasn_params_data pasn_params;
 	struct wpabuf *wrapped_data = NULL;
+#endif /* CONFIG_FILS */
 
 	if (ieee802_11_parse_elems(mgmt->u.auth.variable,
 				   len - offsetof(struct ieee80211_mgmt,
@@ -2690,8 +2701,8 @@
 		return;
 	}
 	if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) {
-		wrapped_data = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION,
-						 WLAN_EID_EXT_WRAPPED_DATA);
+		wrapped_data = ieee802_11_defrag(elems.wrapped_data,
+						 elems.wrapped_data_len, true);
 		if (!wrapped_data) {
 			wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data");
 			return;
@@ -2906,7 +2917,7 @@
 		goto fail;
 	}
 
-	if (os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(mgmt->sa, hapd->own_addr)) {
 		wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate",
 			   MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
@@ -2914,8 +2925,8 @@
 	}
 
 	if (mld_sta &&
-	    (os_memcmp(sa, hapd->own_addr, ETH_ALEN) == 0 ||
-	     os_memcmp(sa, hapd->mld_addr, ETH_ALEN) == 0)) {
+	    (ether_addr_equal(sa, hapd->own_addr) ||
+	     ether_addr_equal(sa, hapd->mld_addr))) {
 		wpa_printf(MSG_INFO,
 			   "Station " MACSTR " not allowed to authenticate",
 			   MAC2STR(sa));
@@ -3068,12 +3079,13 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (auth_transaction == 1) {
+		ap_sta_free_sta_profile(&sta->mld_info);
 		os_memset(&sta->mld_info, 0, sizeof(sta->mld_info));
 
 		if (mld_sta) {
 			u8 link_id = hapd->mld_link_id;
 
-			sta->mld_info.mld_sta = true;
+			ap_sta_set_mld(sta, true);
 			sta->mld_assoc_link_id = link_id;
 
 			/*
@@ -3236,7 +3248,7 @@
 	  * the MLD MAC address. It is the responsibility of the driver to
 	  * handle the translations.
 	  */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		dst = sta->addr;
 		bssid = hapd->mld_addr;
 	}
@@ -3281,7 +3293,7 @@
 
 	/* Do not assign an AID that is in use on any of the affiliated links
 	 * when finding an AID for a non-AP MLD. */
-	if (hapd->conf->mld_ap) {
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
 		int j;
 
 		for (j = 0; j < MAX_NUM_MLD_LINKS; j++) {
@@ -3655,7 +3667,7 @@
 	wpa_hexdump_key(MSG_DEBUG, "OWE: PMK", sta->owe_pmk, sta->owe_pmk_len);
 	wpa_hexdump(MSG_DEBUG, "OWE: PMKID", pmkid, PMKID_LEN);
 	wpa_auth_pmksa_add2(hapd->wpa_auth, sta->addr, sta->owe_pmk,
-			    sta->owe_pmk_len, pmkid, 0, WPA_KEY_MGMT_OWE);
+			    sta->owe_pmk_len, pmkid, 0, WPA_KEY_MGMT_OWE, NULL);
 
 	return WLAN_STATUS_SUCCESS;
 }
@@ -3727,7 +3739,7 @@
 		goto end;
 	}
 #ifdef CONFIG_IEEE80211BE
-	if (sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
 				     sta->mld_assoc_link_id, &sta->mld_info);
 #endif /* CONFIG_IEEE80211BE */
@@ -4009,7 +4021,7 @@
 			}
 
 #ifdef CONFIG_IEEE80211BE
-			if (info->mld_sta) {
+			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,
@@ -4309,22 +4321,23 @@
 
 #ifdef CONFIG_IEEE80211BE
 
-static size_t ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
-					    u16 status_code,
-					    u8 *buf, size_t buflen)
+static void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+					  struct mld_link_info *link)
 {
+	u8 buf[EHT_ML_MAX_STA_PROF_LEN];
 	u8 *p = buf;
+	size_t buflen = sizeof(buf);
 
 	/* Capability Info */
 	WPA_PUT_LE16(p, hostapd_own_capab_info(hapd));
 	p += 2;
 
 	/* Status Code */
-	WPA_PUT_LE16(p, status_code);
+	WPA_PUT_LE16(p, link->status);
 	p += 2;
 
-	if (status_code != WLAN_STATUS_SUCCESS)
-		return p - buf;
+	if (link->status != WLAN_STATUS_SUCCESS)
+		goto out;
 
 	/* AID is not included */
 	p = hostapd_eid_supp_rates(hapd, p);
@@ -4362,20 +4375,24 @@
 		p += wpabuf_len(hapd->conf->assocresp_elements);
 	}
 
-	return p - buf;
+out:
+	os_free(link->resp_sta_profile);
+	link->resp_sta_profile = os_memdup(buf, p - buf);
+	link->resp_sta_profile_len = link->resp_sta_profile ? p - buf : 0;
 }
 
 
-static void ieee80211_ml_process_link(struct hostapd_data *hapd,
-				      struct sta_info *origin_sta,
-				      struct mld_link_info *link,
-				      const u8 *ies, size_t ies_len,
-				      bool reassoc)
+static int ieee80211_ml_process_link(struct hostapd_data *hapd,
+				     struct sta_info *origin_sta,
+				     struct mld_link_info *link,
+				     const u8 *ies, size_t ies_len,
+				     bool reassoc, bool offload)
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *mlbuf = NULL;
 	struct sta_info *sta = NULL;
 	u16 status = WLAN_STATUS_SUCCESS;
+	int i;
 
 	wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR,
 		   hapd->mld_link_id, MAC2STR(link->peer_addr));
@@ -4401,7 +4418,7 @@
 		goto out;
 	}
 
-	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlbuf)
 		goto out;
 
@@ -4421,25 +4438,33 @@
 		goto out;
 	}
 
-	sta->mld_info.mld_sta = true;
+	ap_sta_set_mld(sta, true);
 	sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
 
 	os_memcpy(&sta->mld_info, &origin_sta->mld_info, sizeof(sta->mld_info));
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		struct mld_link_info *li = &sta->mld_info.links[i];
 
-	/*
-	 * Get the AID from the station on which the association was performed,
-	 * and mark it as used.
-	 */
-	sta->aid = origin_sta->aid;
-	if (sta->aid == 0) {
-		wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned");
-		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
-		goto out;
+		li->resp_sta_profile = NULL;
+		li->resp_sta_profile_len = 0;
 	}
-	hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32);
-	sta->listen_interval = origin_sta->listen_interval;
-	if (update_ht_state(hapd, sta) > 0)
-		ieee802_11_update_beacons(hapd->iface);
+
+	if (!offload) {
+		/*
+		 * Get the AID from the station on which the association was
+		 * performed, and mark it as used.
+		 */
+		sta->aid = origin_sta->aid;
+		if (sta->aid == 0) {
+			wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned");
+			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
+		}
+		hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32);
+		sta->listen_interval = origin_sta->listen_interval;
+		if (update_ht_state(hapd, sta) > 0)
+			ieee802_11_update_beacons(hapd->iface);
+	}
 
 	/* RSN Authenticator should always be the one on the original station */
 	wpa_auth_sta_deinit(sta->wpa_sm);
@@ -4465,20 +4490,23 @@
 
 	/* TODO: What other processing is required? */
 
-	if (add_associated_sta(hapd, sta, reassoc))
+	if (!offload && add_associated_sta(hapd, sta, reassoc))
 		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 out:
 	wpabuf_free(mlbuf);
 	link->status = status;
 
-	wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
-	if (sta && status != WLAN_STATUS_SUCCESS)
-		ap_free_sta(hapd, sta);
+	if (!offload)
+		ieee80211_ml_build_assoc_resp(hapd, link);
 
-	link->resp_sta_profile_len =
-		ieee80211_ml_build_assoc_resp(hapd, link->status,
-					      link->resp_sta_profile,
-					      sizeof(link->resp_sta_profile));
+	wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
+	if (status != WLAN_STATUS_SUCCESS) {
+		if (sta)
+			ap_free_sta(hapd, sta);
+		return -1;
+	}
+
+	return 0;
 }
 
 
@@ -4497,16 +4525,17 @@
 #endif /* CONFIG_IEEE80211BE */
 
 
-static void hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
-					  struct sta_info *sta,
-					  const u8 *ies, size_t ies_len,
-					  bool reassoc)
+int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
+				  struct sta_info *sta,
+				  const u8 *ies, size_t ies_len,
+				  bool reassoc, int tx_link_status,
+				  bool offload)
 {
 #ifdef CONFIG_IEEE80211BE
 	unsigned int i, j;
 
 	if (!hostapd_is_mld_ap(hapd))
-		return;
+		return 0;
 
 	/*
 	 * This is not really needed, but make the interaction with the RSN
@@ -4536,22 +4565,29 @@
 				break;
 		}
 
-		if (!iface || j == hapd->iface->interfaces->count) {
+		if (!iface || j == hapd->iface->interfaces->count ||
+		    TEST_FAIL()) {
 			wpa_printf(MSG_DEBUG,
 				   "MLD: No link match for link_id=%u", i);
 
 			link->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
-			link->resp_sta_profile_len =
-				ieee80211_ml_build_assoc_resp(
-					hapd, link->status,
-					link->resp_sta_profile,
-					sizeof(link->resp_sta_profile));
+			if (!offload)
+				ieee80211_ml_build_assoc_resp(hapd, link);
+		} else if (tx_link_status != WLAN_STATUS_SUCCESS) {
+			/* TX link rejected the connection */
+			link->status = WLAN_STATUS_DENIED_TX_LINK_NOT_ACCEPTED;
+			if (!offload)
+				ieee80211_ml_build_assoc_resp(hapd, link);
 		} else {
-			ieee80211_ml_process_link(iface->bss[0], sta, link,
-						  ies, ies_len, reassoc);
+			if (ieee80211_ml_process_link(iface->bss[0], sta, link,
+						      ies, ies_len, reassoc,
+						      offload))
+				return -1;
 		}
 	}
 #endif /* CONFIG_IEEE80211BE */
+
+	return 0;
 }
 
 
@@ -4589,7 +4625,7 @@
 	bool mld_link_sta = false;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		u8 mld_link_id = hapd->mld_link_id;
 
 		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
@@ -4700,7 +4736,7 @@
 static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 			   const u8 *addr, u16 status_code, int reassoc,
 			   const u8 *ies, size_t ies_len, int rssi,
-			   int omit_rsnxe)
+			   int omit_rsnxe, bool allow_mld_addr_trans)
 {
 	int send_len;
 	u8 *buf;
@@ -4750,7 +4786,7 @@
 	 * Once a non-AP MLD is added to the driver, the addressing should use
 	 * MLD MAC address.
 	 */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta) && allow_mld_addr_trans)
 		sa = hapd->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -4893,7 +4929,7 @@
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
 		if (hapd->conf->mld_ap)
-			p = hostapd_eid_eht_basic_ml(hapd, p, sta, false);
+			p = hostapd_eid_eht_ml_assoc(hapd, sta, p);
 		p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP);
 		p = hostapd_eid_eht_operation(hapd, p);
 	}
@@ -5116,7 +5152,8 @@
 	reply_res = send_assoc_resp(hapd, sta, sta->addr, WLAN_STATUS_SUCCESS,
 				    sta->fils_pending_assoc_is_reassoc,
 				    sta->fils_pending_assoc_req,
-				    sta->fils_pending_assoc_req_len, 0, 0);
+				    sta->fils_pending_assoc_req_len, 0, 0,
+				    true);
 	os_free(sta->fils_pending_assoc_req);
 	sta->fils_pending_assoc_req = NULL;
 	sta->fils_pending_assoc_req_len = 0;
@@ -5151,6 +5188,48 @@
 #endif /* CONFIG_FILS */
 
 
+#ifdef CONFIG_IEEE80211BE
+static struct sta_info * handle_mlo_translate(struct hostapd_data *hapd,
+					      const struct ieee80211_mgmt *mgmt,
+					      size_t len, bool reassoc,
+					      struct hostapd_data **assoc_hapd)
+{
+	struct sta_info *sta;
+	struct ieee802_11_elems elems;
+	u8 mld_addr[ETH_ALEN];
+	const u8 *pos;
+
+	if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be)
+		return NULL;
+
+	if (reassoc) {
+		len -= IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req);
+		pos = mgmt->u.reassoc_req.variable;
+	} else {
+		len -= IEEE80211_HDRLEN + sizeof(mgmt->u.assoc_req);
+		pos = mgmt->u.assoc_req.variable;
+	}
+
+	if (ieee802_11_parse_elems(pos, len, &elems, 1) == ParseFailed)
+		return NULL;
+
+	if (hostapd_process_ml_assoc_req_addr(hapd, elems.basic_mle,
+					      elems.basic_mle_len,
+					      mld_addr))
+		return NULL;
+
+	sta = ap_get_sta(hapd, mld_addr);
+	if (!sta)
+		return NULL;
+
+	wpa_printf(MSG_DEBUG, "MLD: assoc: mld=" MACSTR ", link=" MACSTR,
+		   MAC2STR(mld_addr), MAC2STR(mgmt->sa));
+
+	return hostapd_ml_get_assoc_sta(hapd, sta, assoc_hapd);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 static void handle_assoc(struct hostapd_data *hapd,
 			 const struct ieee80211_mgmt *mgmt, size_t len,
 			 int reassoc, int rssi)
@@ -5167,6 +5246,7 @@
 #endif /* CONFIG_FILS */
 	int omit_rsnxe = 0;
 	bool set_beacon = false;
+	bool mld_addrs_not_translated = false;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -5224,6 +5304,28 @@
 	}
 
 	sta = ap_get_sta(hapd, mgmt->sa);
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * It is possible that the association frame is from an associated
+	 * non-AP MLD station, that tries to re-associate using different link
+	 * addresses. In such a case, try to find the station based on the AP
+	 * MLD MAC address.
+	 */
+	if (!sta) {
+		struct hostapd_data *assoc_hapd;
+
+		sta = handle_mlo_translate(hapd, mgmt, len, reassoc,
+					   &assoc_hapd);
+		if (sta) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Switching to assoc hapd/station");
+			hapd = assoc_hapd;
+			mld_addrs_not_translated = true;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (sta && sta->auth_alg == WLAN_AUTH_FT &&
 	    (sta->flags & WLAN_STA_AUTH) == 0) {
@@ -5500,8 +5602,9 @@
 	 *    issues with processing other non-Data Class 3 frames during this
 	 *    window.
 	 */
-	if (resp == WLAN_STATUS_SUCCESS)
-		hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc);
+	if (sta)
+		hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc,
+					      resp, false);
 
 	if (resp == WLAN_STATUS_SUCCESS && sta &&
 	    add_associated_sta(hapd, sta, reassoc))
@@ -5545,8 +5648,12 @@
 #endif /* CONFIG_FILS */
 
 	if (resp >= 0)
-		reply_res = send_assoc_resp(hapd, sta, mgmt->sa, resp, reassoc,
-					    pos, left, rssi, omit_rsnxe);
+		reply_res = send_assoc_resp(hapd,
+					    mld_addrs_not_translated ?
+					    NULL : sta,
+					    mgmt->sa, resp, reassoc,
+					    pos, left, rssi, omit_rsnxe,
+					    !mld_addrs_not_translated);
 	os_free(tmp);
 
 	/*
@@ -5637,44 +5744,6 @@
 }
 
 
-#ifdef CONFIG_IEEE80211BE
-static struct sta_info *
-hostapd_ml_get_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
-			 struct hostapd_data **assoc_hapd)
-{
-	struct hostapd_data *other_hapd = NULL;
-	struct sta_info *tmp_sta;
-
-	*assoc_hapd = hapd;
-
-	/* The station is the one on which the association was performed */
-	if (sta->mld_assoc_link_id == hapd->mld_link_id)
-		return sta;
-
-	other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id);
-	if (!other_hapd) {
-		wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u",
-			   sta->mld_assoc_link_id);
-		return sta;
-	}
-
-	/*
-	 * Iterate over the stations and find the one with the matching link ID
-	 * and association ID.
-	 */
-	for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) {
-		if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id &&
-		    tmp_sta->aid == sta->aid) {
-			*assoc_hapd = other_hapd;
-			return tmp_sta;
-		}
-	}
-
-	return sta;
-}
-#endif /* CONFIG_IEEE80211BE */
-
-
 static bool hostapd_ml_handle_disconnect(struct hostapd_data *hapd,
 					 struct sta_info *sta,
 					 const struct ieee80211_mgmt *mgmt,
@@ -5693,6 +5762,8 @@
 	 * the information about all the other links.
 	 */
 	assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+	if (!assoc_sta)
+		return false;
 
 	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
 		for (i = 0; i < assoc_hapd->iface->interfaces->count; i++) {
@@ -5964,6 +6035,25 @@
 				return 1;
 		}
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+		if (mgmt->u.action.category == WLAN_ACTION_PUBLIC &&
+		    len >= IEEE80211_HDRLEN + 5 &&
+		    mgmt->u.action.u.vs_public_action.action ==
+		    WLAN_PA_VENDOR_SPECIFIC &&
+		    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
+		    OUI_WFA &&
+		    mgmt->u.action.u.vs_public_action.variable[0] ==
+		    NAN_OUI_TYPE) {
+			const u8 *pos, *end;
+
+			pos = mgmt->u.action.u.vs_public_action.variable;
+			end = ((const u8 *) mgmt) + len;
+			pos++;
+			hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, freq,
+					       pos, end - pos);
+			return 1;
+		}
+#endif /* CONFIG_NAN_USD */
 		if (hapd->public_action_cb) {
 			hapd->public_action_cb(hapd->public_action_cb_ctx,
 					       (u8 *) mgmt, len, freq);
@@ -5982,9 +6072,11 @@
 				return 1;
 		}
 		break;
+#ifndef CONFIG_NO_RRM
 	case WLAN_ACTION_RADIO_MEASUREMENT:
 		hostapd_handle_radio_measurement(hapd, (const u8 *) mgmt, len);
 		return 1;
+#endif /* CONFIG_NO_RRM */
 	}
 
 	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
@@ -6069,6 +6161,10 @@
 	int ret = 0;
 	unsigned int freq;
 	int ssi_signal = fi ? fi->ssi_signal : 0;
+#ifdef CONFIG_NAN_USD
+	static const u8 nan_network_id[ETH_ALEN] =
+		{ 0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00 };
+#endif /* CONFIG_NAN_USD */
 
 	if (len < 24)
 		return 0;
@@ -6084,7 +6180,7 @@
 
 	if (is_multicast_ether_addr(mgmt->sa) ||
 	    is_zero_ether_addr(mgmt->sa) ||
-	    os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(mgmt->sa, hapd->own_addr)) {
 		/* Do not process any frames with unexpected/invalid SA so that
 		 * we do not add any state for unexpected STA addresses or end
 		 * up sending out frames to unexpected destination. */
@@ -6110,9 +6206,9 @@
 #endif /* CONFIG_MESH */
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
-	    os_memcmp(mgmt->bssid, hapd->own_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(mgmt->bssid, hapd->own_addr)) {
 		wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address",
 			   MAC2STR(mgmt->bssid));
 		return 0;
@@ -6133,9 +6229,12 @@
 	     stype != WLAN_FC_STYPE_ACTION) &&
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
-	    os_memcmp(mgmt->da, hapd->own_addr, ETH_ALEN) != 0) {
+#ifdef CONFIG_NAN_USD
+	    !ether_addr_equal(mgmt->da, nan_network_id) &&
+#endif /* CONFIG_NAN_USD */
+	    !ether_addr_equal(mgmt->da, hapd->own_addr)) {
 		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "MGMT: DA=" MACSTR " not our address",
@@ -6285,6 +6384,8 @@
 					   struct mld_link_info *link,
 					   bool ok)
 {
+	bool updated = false;
+
 	if (!ok) {
 		hostapd_logger(hapd, link->peer_addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -6305,9 +6406,11 @@
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 
 	if (!hapd->conf->ieee802_1x && !hapd->conf->wpa)
-		ap_sta_set_authorized(hapd, sta, 1);
+		updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 
 	/*
 	 * TODOs:
@@ -6340,7 +6443,7 @@
 			struct hostapd_data *tmp_hapd =
 				hapd->iface->interfaces->iface[i]->bss[0];
 
-			if (tmp_hapd->conf->mld_ap ||
+			if (!tmp_hapd->conf->mld_ap ||
 			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
 				continue;
 
@@ -6379,7 +6482,7 @@
 	}
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	if (ap_sta_is_mld(hapd, sta) &&
 	    hapd->mld_link_id != sta->mld_assoc_link_id) {
 		/* See ieee80211_ml_link_sta_assoc_cb() for the MLD case */
 		wpa_printf(MSG_DEBUG,
@@ -6589,7 +6692,9 @@
 			     size_t len, int ok)
 {
 	struct sta_info *sta;
+#ifndef CONFIG_NO_RRM
 	const struct rrm_measurement_report_element *report;
+#endif /* CONFIG_NO_RRM */
 
 #ifdef CONFIG_DPP
 	if (len >= IEEE80211_HDRLEN + 6 &&
@@ -6643,6 +6748,7 @@
 	}
 #endif /* CONFIG_HS20 */
 
+#ifndef CONFIG_NO_RRM
 	if (len < 24 + 5 + sizeof(*report))
 		return;
 	report = (const struct rrm_measurement_report_element *)
@@ -6653,6 +6759,7 @@
 	    report->len >= 3 &&
 	    report->type == MEASURE_TYPE_BEACON)
 		hostapd_rrm_beacon_req_tx_status(hapd, mgmt, len, ok);
+#endif /* CONFIG_NO_RRM */
 }
 
 
@@ -6864,7 +6971,7 @@
 	wpa_printf(MSG_DEBUG, "Data/PS-poll frame from not associated STA "
 		   MACSTR, MAC2STR(src));
 	if (is_multicast_ether_addr(src) || is_zero_ether_addr(src) ||
-	    os_memcmp(src, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(src, hapd->own_addr)) {
 		/* Broadcast bit set in SA or unexpected SA?! Ignore the frame
 		 * silently. */
 		return;
@@ -6963,21 +7070,35 @@
 		tx_pwr_intrpn = REGULATORY_CLIENT_EIRP_PSD;
 
 		/* Default Transmit Power Envelope for Global Operating Class */
-		tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2;
+		if (hapd->iconf->reg_def_cli_eirp_psd != -1)
+			tx_pwr = hapd->iconf->reg_def_cli_eirp_psd;
+		else
+			tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2;
+
 		eid = hostapd_add_tpe_info(eid, tx_pwr_count, tx_pwr_intrpn,
 					   REG_DEFAULT_CLIENT, tx_pwr);
 
 		/* Indoor Access Point must include an additional TPE for
 		 * subordinate devices */
-		if (iconf->he_6ghz_reg_pwr_type == HE_6GHZ_INDOOR_AP) {
+		if (he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type)) {
 			/* TODO: Extract PSD limits from channel data */
-			tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2;
+			if (hapd->iconf->reg_sub_cli_eirp_psd != -1)
+				tx_pwr = hapd->iconf->reg_sub_cli_eirp_psd;
+			else
+				tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2;
 			eid = hostapd_add_tpe_info(eid, tx_pwr_count,
 						   tx_pwr_intrpn,
 						   REG_SUBORDINATE_CLIENT,
 						   tx_pwr);
 		}
 
+		if (iconf->reg_def_cli_eirp != -1 &&
+		    he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
+			eid = hostapd_add_tpe_info(
+				eid, tx_pwr_count, REGULATORY_CLIENT_EIRP,
+				REG_DEFAULT_CLIENT,
+				hapd->iconf->reg_def_cli_eirp);
+
 		return eid;
 	}
 #endif /* CONFIG_IEEE80211AX */
@@ -7392,7 +7513,7 @@
 		/* BSS parameters */
 		*eid++ = nr->bss_parameters;
 		/* 20 MHz PSD */
-		*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
+		*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER;
 		len += RNR_TBTT_INFO_LEN;
 		*size_offset = (eid - size_offset) - 1;
 	}
@@ -7402,17 +7523,98 @@
 }
 
 
+static bool hostapd_eid_rnr_bss(struct hostapd_data *hapd,
+				struct hostapd_data *reporting_hapd,
+				struct mbssid_ie_profiles *skip_profiles,
+				size_t i, u8 *tbtt_count, size_t *len,
+				u8 **pos)
+{
+	struct hostapd_iface *iface = hapd->iface;
+	struct hostapd_data *bss = iface->bss[i];
+	u8 bss_param = 0;
+	bool ap_mld = false;
+	u8 *eid = *pos;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!bss || !bss->conf || !bss->started ||
+	    bss == reporting_hapd || bss->conf->ignore_broadcast_ssid)
+		return false;
+
+	if (skip_profiles
+	    && i >= skip_profiles->start && i < skip_profiles->end)
+		return false;
+
+	if (*len + RNR_TBTT_INFO_LEN > 255 ||
+	    *tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
+		return true;
+
+	*eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN;
+	os_memcpy(eid, bss->own_addr, ETH_ALEN);
+	eid += ETH_ALEN;
+	os_memcpy(eid, &bss->conf->ssid.short_ssid, 4);
+	eid += 4;
+	if (bss->conf->ssid.short_ssid == reporting_hapd->conf->ssid.short_ssid)
+		bss_param |= RNR_BSS_PARAM_SAME_SSID;
+
+	if (iface->conf->mbssid != MBSSID_DISABLED && iface->num_bss > 1) {
+		bss_param |= RNR_BSS_PARAM_MULTIPLE_BSSID;
+		if (bss == hostapd_mbssid_get_tx_bss(hapd))
+			bss_param |= RNR_BSS_PARAM_TRANSMITTED_BSSID;
+	}
+
+	if (is_6ghz_op_class(hapd->iconf->op_class) &&
+	    bss->conf->unsol_bcast_probe_resp_interval)
+		bss_param |= RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE;
+
+	bss_param |= RNR_BSS_PARAM_CO_LOCATED;
+
+	*eid++ = bss_param;
+	*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER;
+
+	if (!ap_mld) {
+		*len += RNR_TBTT_INFO_LEN;
+	} else {
+#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)
+			*eid++ = 0;
+		else
+			*eid++ = hapd->conf->mld_id;
+
+		*eid++ = hapd->mld_link_id | ((param_ch & 0xF) << 4);
+		*eid = (param_ch >> 4) & 0xF;
+#ifdef CONFIG_TESTING_OPTIONS
+		if (hapd->conf->mld_indicate_disabled)
+			*eid |= RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED;
+#endif /* CONFIG_TESTING_OPTIONS */
+		eid++;
+
+		*len += RNR_TBTT_INFO_MLD_LEN;
+#endif /* CONFIG_IEEE80211BE */
+	}
+
+	(*tbtt_count)++;
+	*pos = eid;
+
+	return false;
+}
+
+
 static u8 * hostapd_eid_rnr_iface(struct hostapd_data *hapd,
 				  struct hostapd_data *reporting_hapd,
 				  u8 *eid, size_t *current_len,
 				  struct mbssid_ie_profiles *skip_profiles)
 {
-	struct hostapd_data *bss;
 	struct hostapd_iface *iface = hapd->iface;
 	size_t i, start = 0;
 	size_t len = *current_len;
 	u8 *tbtt_count_pos, *eid_start = eid, *size_offset = (eid - len) + 1;
-	u8 tbtt_count = 0, op_class, channel, bss_param;
+	u8 tbtt_count = 0, op_class, channel;
 	bool ap_mld = false;
 
 #ifdef CONFIG_IEEE80211BE
@@ -7447,62 +7649,10 @@
 		len += RNR_TBTT_HEADER_LEN;
 
 		for (i = start; i < iface->num_bss; i++) {
-			bss_param = 0;
-			bss = iface->bss[i];
-			if (!bss || !bss->conf || !bss->started)
-				continue;
-
-			if (bss == reporting_hapd ||
-			    bss->conf->ignore_broadcast_ssid)
-				continue;
-
-			if (skip_profiles &&
-			    i >= skip_profiles->start && i < skip_profiles->end)
-				continue;
-
-			if (len + RNR_TBTT_INFO_LEN > 255 ||
-			    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
+			if (hostapd_eid_rnr_bss(hapd, reporting_hapd,
+						skip_profiles, i,
+						&tbtt_count, &len, &eid))
 				break;
-
-			*eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN;
-			os_memcpy(eid, bss->own_addr, ETH_ALEN);
-			eid += ETH_ALEN;
-			os_memcpy(eid, &bss->conf->ssid.short_ssid, 4);
-			eid += 4;
-			if (bss->conf->ssid.short_ssid ==
-			    reporting_hapd->conf->ssid.short_ssid)
-				bss_param |= RNR_BSS_PARAM_SAME_SSID;
-
-			if (iface->conf->mbssid != MBSSID_DISABLED &&
-			    iface->num_bss > 1) {
-				bss_param |= RNR_BSS_PARAM_MULTIPLE_BSSID;
-				if (bss == hostapd_mbssid_get_tx_bss(hapd))
-					bss_param |=
-						RNR_BSS_PARAM_TRANSMITTED_BSSID;
-			}
-
-			if (is_6ghz_op_class(hapd->iconf->op_class) &&
-			    bss->conf->unsol_bcast_probe_resp_interval)
-				bss_param |=
-					RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE;
-
-			bss_param |= RNR_BSS_PARAM_CO_LOCATED;
-
-			*eid++ = bss_param;
-			*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
-
-			if (!ap_mld) {
-				len += RNR_TBTT_INFO_LEN;
-			} else {
-#ifdef CONFIG_IEEE80211BE
-				*eid++ = hapd->conf->mld_id;
-				*eid++ = hapd->mld_link_id | (1 << 4);
-				*eid++ = 0;
-				len += RNR_TBTT_INFO_MLD_LEN;
-#endif /* CONFIG_IEEE80211BE */
-			}
-
-			tbtt_count += 1;
 		}
 
 		start = i;
@@ -7579,7 +7729,7 @@
 
 	case WLAN_FC_STYPE_ACTION:
 		if (hapd->iface->num_bss > 1 && mode == STANDALONE_6GHZ)
-			eid = hostapd_eid_rnr_iface(hapd, hapd,	eid,
+			eid = hostapd_eid_rnr_iface(hapd, hapd, eid,
 						    &current_len, NULL);
 		break;
 
@@ -7610,7 +7760,18 @@
 					  size_t known_bss_len)
 {
 	struct hostapd_data *tx_bss = hostapd_mbssid_get_tx_bss(hapd);
-	size_t len = 3, i;
+	size_t len, i;
+
+	/* Element ID: 1 octet
+	 * Length: 1 octet
+	 * MaxBSSID Indicator: 1 octet
+	 * Optional Subelements: vatiable
+	 *
+	 * Total fixed length: 3 octets
+	 *
+	 * 1 octet in len for the MaxBSSID Indicator field.
+	 */
+	len = 1;
 
 	for (i = *bss_index; i < hapd->iface->num_bss; i++) {
 		struct hostapd_data *bss = hapd->iface->bss[i];
@@ -7663,7 +7824,9 @@
 	}
 
 	*bss_index = i;
-	return len;
+
+	/* Add 2 octets to get the full size of the element */
+	return len + 2;
 }
 
 
@@ -7792,13 +7955,14 @@
 				eid += 2 + rsnx[1];
 			}
 		}
+		/* 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;
-		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
-			non_inherit_ie[ie_count++] = WLAN_EID_RSNX;
 		if (hapd->conf->xrates_supported &&
 		    !bss->conf->xrates_supported)
 			non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES;
+		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
+			non_inherit_ie[ie_count++] = WLAN_EID_RSNX;
 		if (ie_count) {
 			*eid++ = WLAN_EID_EXTENSION;
 			*eid++ = 2 + ie_count + 1;
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 4b58fee..5fd380a 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -23,6 +23,7 @@
 struct sae_pk;
 struct sae_pt;
 struct sae_password_entry;
+struct mld_info;
 
 int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len,
 		    struct hostapd_frame_info *fi);
@@ -88,8 +89,14 @@
 			   const struct ieee80211_eht_capabilities *src,
 			   struct ieee80211_eht_capabilities *dest,
 			   size_t len);
-u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
-			      struct sta_info *info, bool include_mld_id);
+u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd,
+			       struct mld_info *mld_info,
+			       u8 *eid, bool include_mld_id);
+u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info,
+			      u8 *eid);
+size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd,
+				     struct mld_info *info,
+				     bool include_mld_id);
 struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd);
 const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
 				   const struct ieee80211_mgmt *mgmt,
@@ -97,6 +104,9 @@
 u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
 				 struct ieee802_11_elems *elems,
 				 struct sta_info *sta);
+int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd,
+				      const u8 *basic_mle, size_t basic_mle_len,
+				      u8 *mld_addr);
 int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta);
 u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 		      const u8 *ht_capab);
@@ -245,5 +255,13 @@
 			      struct sta_info *sta, const char *rx_id,
 			      struct sae_password_entry **pw_entry,
 			      struct sae_pt **s_pt, const struct sae_pk **s_pk);
+struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct hostapd_data **assoc_hapd);
+int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
+				  struct sta_info *sta,
+				  const u8 *ies, size_t ies_len,
+				  bool reassoc, int tx_link_status,
+				  bool offload);
 
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index 4277d82..e723ae7 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -84,7 +84,7 @@
 	os_get_reltime(&now);
 
 	for (entry = hapd->acl_cache; entry; entry = entry->next) {
-		if (os_memcmp(entry->addr, addr, ETH_ALEN) != 0)
+		if (!ether_addr_equal(entry->addr, addr))
 			continue;
 
 		if (os_reltime_expired(&now, &entry->timestamp,
@@ -281,7 +281,7 @@
 
 		query = hapd->acl_queries;
 		while (query) {
-			if (os_memcmp(query->addr, addr, ETH_ALEN) == 0) {
+			if (ether_addr_equal(query->addr, addr)) {
 				/* pending query in RADIUS retransmit queue;
 				 * do not generate a new one */
 				return HOSTAPD_ACL_PENDING;
diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index c1ccda4..840fc20 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -44,12 +44,13 @@
 
 
 static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass,
-				     u8 he_oper_chwidth, const u8 *he_phy_cap,
+				     int he_oper_chwidth, const u8 *he_phy_cap,
 				     const u8 *eht_phy_cap)
 {
 	u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS;
 	bool band24, band5, band6;
 	u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK;
+	u8 cap_chwidth;
 
 	switch (he_oper_chwidth) {
 	case CONF_OPER_CHWIDTH_80P80MHZ:
@@ -66,7 +67,11 @@
 		break;
 	}
 
-	he_phy_cap_chwidth &= he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+	cap_chwidth = he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+	if (he_oper_chwidth != -1)
+		he_phy_cap_chwidth &= cap_chwidth;
+	else
+		he_phy_cap_chwidth = cap_chwidth;
 
 	band24 = mode == HOSTAPD_MODE_IEEE80211B ||
 		mode == HOSTAPD_MODE_IEEE80211G ||
@@ -199,12 +204,33 @@
 	struct ieee80211_eht_operation *oper;
 	u8 *pos = eid, seg0 = 0, seg1 = 0;
 	enum oper_chan_width chwidth;
-	size_t elen = 1 + 4 + 3;
+	size_t elen = 1 + 4;
+	bool eht_oper_info_present;
+	u16 punct_bitmap = conf->punct_bitmap;
 
 	if (!hapd->iface->current_mode)
 		return eid;
 
-	if (hapd->iconf->punct_bitmap)
+#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
+		chwidth = conf->eht_oper_chwidth;
+
+	eht_oper_info_present = chwidth == CONF_OPER_CHWIDTH_320MHZ ||
+		punct_bitmap;
+
+	if (eht_oper_info_present)
+		elen += 3;
+
+	if (punct_bitmap)
 		elen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
 
 	*pos++ = WLAN_EID_EXTENSION;
@@ -212,7 +238,10 @@
 	*pos++ = WLAN_EID_EXT_EHT_OPERATION;
 
 	oper = (struct ieee80211_eht_operation *) pos;
-	oper->oper_params = EHT_OPER_INFO_PRESENT;
+	oper->oper_params = 0;
+
+	if (hapd->iconf->eht_default_pe_duration)
+		oper->oper_params |= EHT_OPER_DEFAULT_PE_DURATION;
 
 	/* TODO: Fill in appropriate EHT-MCS max Nss information */
 	oper->basic_eht_mcs_nss_set[0] = 0x11;
@@ -220,11 +249,10 @@
 	oper->basic_eht_mcs_nss_set[2] = 0x00;
 	oper->basic_eht_mcs_nss_set[3] = 0x00;
 
-	if (is_6ghz_op_class(conf->op_class))
-		chwidth = op_class_to_ch_width(conf->op_class);
-	else
-		chwidth = conf->eht_oper_chwidth;
+	if (!eht_oper_info_present)
+		return pos + elen;
 
+	oper->oper_params |= EHT_OPER_INFO_PRESENT;
 	seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf);
 
 	switch (chwidth) {
@@ -258,10 +286,10 @@
 	oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel;
 	oper->oper_info.ccfs1 = seg1;
 
-	if (hapd->iconf->punct_bitmap) {
+	if (punct_bitmap) {
 		oper->oper_params |= EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT;
 		oper->oper_info.disabled_chan_bitmap =
-			host_to_le16(hapd->iconf->punct_bitmap);
+			host_to_le16(punct_bitmap);
 	}
 
 	return pos + elen;
@@ -338,9 +366,8 @@
 
 
 static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode,
-					   u8 opclass, u8 he_oper_chwidth,
-					   const u8 *he_cap, const u8 *eht_cap,
-					   size_t len)
+					   u8 opclass, const u8 *he_cap,
+					   const u8 *eht_cap, size_t len)
 {
 	const struct ieee80211_he_capabilities *he_capab;
 	struct ieee80211_eht_capabilities *cap;
@@ -355,8 +382,8 @@
 	if (len < cap_len)
 		return true;
 
-	cap_len += ieee80211_eht_mcs_set_size(mode, opclass, he_oper_chwidth,
-					      he_phy_cap, cap->phy_cap);
+	cap_len += ieee80211_eht_mcs_set_size(mode, opclass, -1, he_phy_cap,
+					      cap->phy_cap);
 	if (len < cap_len)
 		return true;
 
@@ -380,7 +407,6 @@
 	    !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN ||
 	    !eht_capab ||
 	    ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class,
-					   hapd->iconf->he_oper_chwidth,
 					   he_capab, eht_capab,
 					   eht_capab_len) ||
 	    !check_valid_eht_mcs(hapd, eht_capab, opmode)) {
@@ -421,8 +447,9 @@
 }
 
 
-u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
-			      struct sta_info *info, bool include_mld_id)
+static u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd,
+					    u8 *eid, struct mld_info *mld_info,
+					    bool include_mld_id)
 {
 	struct wpabuf *buf;
 	u16 control;
@@ -454,7 +481,8 @@
 	 * BSS Parameters Change Count (1) + EML Capabilities (2) +
 	 * MLD Capabilities and Operations (2)
 	 */
-	common_info_len = 13;
+#define EHT_ML_COMMON_INFO_LEN 13
+	common_info_len = EHT_ML_COMMON_INFO_LEN;
 
 	if (include_mld_id) {
 		/* AP MLD ID */
@@ -489,12 +517,12 @@
 		wpabuf_put_u8(buf, hapd->conf->mld_id);
 	}
 
-	if (!info)
+	if (!mld_info)
 		goto out;
 
 	/* Add link info for the other links */
 	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		struct mld_link_info *link = &info->mld_info.links[link_id];
+		struct mld_link_info *link = &mld_info->links[link_id];
 		struct hostapd_data *link_bss;
 
 		/*
@@ -502,8 +530,9 @@
 		 * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS
 		 * parameters change counter (1) + station profile length.
 		 */
-		const size_t fixed_len = 22;
-		size_t total_len = fixed_len + link->resp_sta_profile_len;
+#define EHT_ML_STA_INFO_LEN 22
+		size_t total_len = EHT_ML_STA_INFO_LEN +
+			link->resp_sta_profile_len;
 
 		/* Skip the local one */
 		if (link_id == hapd->mld_link_id || !link->valid)
@@ -537,7 +566,7 @@
 		/* STA Info */
 
 		/* STA Info Length */
-		wpabuf_put_u8(buf, fixed_len - 2);
+		wpabuf_put_u8(buf, EHT_ML_STA_INFO_LEN - 2);
 		wpabuf_put_data(buf, link->local_addr, ETH_ALEN);
 		wpabuf_put_le16(buf, link_bss->iconf->beacon_int);
 
@@ -552,9 +581,10 @@
 		wpabuf_put_le16(buf, link_bss->conf->dtim_period);
 
 		/* BSS Parameters Change Count */
-		/* TODO: Currently hard code the BSS Parameters Change Count to
-		 * 0x1 */
-		wpabuf_put_u8(buf, 0x1);
+		wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change);
+
+		if (!link->resp_sta_profile)
+			continue;
 
 		/* Fragment the sub element if needed */
 		if (total_len <= 255) {
@@ -564,7 +594,7 @@
 			ptr = link->resp_sta_profile;
 			len = link->resp_sta_profile_len;
 
-			slice_len = 255 - fixed_len;
+			slice_len = 255 - EHT_ML_STA_INFO_LEN;
 
 			wpabuf_put_data(buf, ptr, slice_len);
 			len -= slice_len;
@@ -625,6 +655,151 @@
 }
 
 
+static u8 * hostapd_eid_eht_reconf_ml(struct hostapd_data *hapd, u8 *eid)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	struct hostapd_data *other_hapd;
+	u16 control;
+	u8 *pos = eid;
+	unsigned int i;
+
+	wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML");
+
+	/* First check if the element needs to be added */
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+
+		wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: %u",
+			   other_hapd->eht_mld_link_removal_count);
+
+		if (other_hapd->eht_mld_link_removal_count)
+			break;
+	}
+
+	/* No link is going to be removed */
+	if (i == hapd->iface->interfaces->count)
+		return eid;
+
+	wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: Adding element");
+
+	/* The length will be set at the end */
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 0;
+	*pos++ = WLAN_EID_EXT_MULTI_LINK;
+
+	/* Set the Multi-Link Control field */
+	control = MULTI_LINK_CONTROL_TYPE_RECONF;
+	WPA_PUT_LE16(pos, control);
+	pos += 2;
+
+	/* Common Info doesn't include any information */
+	*pos++ = 1;
+
+	/* Add the per station profiles */
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+		if (!other_hapd->eht_mld_link_removal_count)
+			continue;
+
+		/* Subelement ID is 0 */
+		*pos++ = 0;
+		*pos++ = 5;
+
+		control = other_hapd->mld_link_id |
+			EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER;
+
+		WPA_PUT_LE16(pos, control);
+		pos += 2;
+
+		/* STA profile length */
+		*pos++ = 3;
+
+		WPA_PUT_LE16(pos, other_hapd->eht_mld_link_removal_count);
+		pos += 2;
+	}
+
+	eid[1] = pos - eid - 2;
+
+	wpa_hexdump(MSG_DEBUG, "MLD: Reconfiguration ML", eid, eid[1] + 2);
+	return pos;
+#else /* CONFIG_TESTING_OPTIONS */
+	return eid;
+#endif /* CONFIG_TESTING_OPTIONS */
+}
+
+
+static size_t hostapd_eid_eht_ml_len(struct mld_info *info,
+				     bool include_mld_id)
+{
+	size_t len = 0;
+	size_t eht_ml_len = 2 + EHT_ML_COMMON_INFO_LEN;
+	u8 link_id;
+
+	if (include_mld_id)
+		eht_ml_len++;
+
+	for (link_id = 0; info && link_id < ARRAY_SIZE(info->links);
+	     link_id++) {
+		struct mld_link_info *link;
+		size_t sta_len = EHT_ML_STA_INFO_LEN;
+
+		link = &info->links[link_id];
+		if (!link->valid)
+			continue;
+
+		sta_len += link->resp_sta_profile_len;
+
+		/* Element data and (fragmentation) headers */
+		eht_ml_len += sta_len;
+		eht_ml_len += 2 + sta_len / 255 * 2;
+	}
+
+	/* Element data */
+	len += eht_ml_len;
+
+	/* First header (254 bytes of data) */
+	len += 3;
+
+	/* Fragmentation headers; +1 for shorter first chunk */
+	len += (eht_ml_len + 1) / 255 * 2;
+
+	return len;
+}
+#undef EHT_ML_COMMON_INFO_LEN
+#undef EHT_ML_STA_INFO_LEN
+
+
+u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd,
+			       struct mld_info *info,
+			       u8 *eid, bool include_mld_id)
+{
+	eid = hostapd_eid_eht_basic_ml_common(hapd, eid, info, include_mld_id);
+	return hostapd_eid_eht_reconf_ml(hapd, eid);
+}
+
+
+
+u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info,
+			      u8 *eid)
+{
+	if (!ap_sta_is_mld(hapd, info))
+		return eid;
+
+	eid = hostapd_eid_eht_basic_ml_common(hapd, eid, &info->mld_info,
+					      false);
+	ap_sta_free_sta_profile(&info->mld_info);
+	return hostapd_eid_eht_reconf_ml(hapd, eid);
+}
+
+
+size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd,
+				     struct mld_info *info,
+				     bool include_mld_id)
+{
+	return hostapd_eid_eht_ml_len(info, include_mld_id);
+}
+
+
 struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd)
 {
 	struct wpabuf *buf = wpabuf_alloc(12);
@@ -841,11 +1016,12 @@
 
 
 static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd,
-					   struct mld_info *info)
+					   struct sta_info *sta)
 {
 	u8 i, link_id;
+	struct mld_info *info = &sta->mld_info;
 
-	if (!info->mld_sta) {
+	if (!ap_sta_is_mld(hapd, sta)) {
 		wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD");
 		return 0;
 	}
@@ -894,6 +1070,85 @@
 }
 
 
+int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd,
+				      const u8 *basic_mle, size_t basic_mle_len,
+				      u8 *mld_addr)
+{
+	struct wpabuf *mlbuf = ieee802_11_defrag(basic_mle, basic_mle_len,
+						 true);
+	struct ieee80211_eht_ml *ml;
+	struct eht_ml_basic_common_info *common_info;
+	size_t ml_len, common_info_len;
+	int ret = -1;
+	u16 ml_control;
+
+	if (!mlbuf)
+		return WLAN_STATUS_SUCCESS;
+
+	ml = (struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+	ml_len = wpabuf_len(mlbuf);
+
+	if (ml_len < sizeof(*ml))
+		goto out;
+
+	ml_control = le_to_host16(ml->ml_control);
+	if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_BASIC) {
+		wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u",
+			   ml_control & MULTI_LINK_CONTROL_TYPE_MASK);
+		goto out;
+	}
+
+	/* Common Info Length and MLD MAC Address must always be present */
+	common_info_len = 1 + ETH_ALEN;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) {
+		wpa_printf(MSG_DEBUG, "MLD: Link ID Info not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: BSS Parameters Change Count not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Medium Synchronization Delay Information not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA)
+		common_info_len += 2;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA)
+		common_info_len += 2;
+
+	if (sizeof(*ml) + common_info_len > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info");
+		goto out;
+	}
+
+	common_info = (struct eht_ml_basic_common_info *) ml->variable;
+
+	/* Common information length includes the length octet */
+	if (common_info->len != common_info_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Invalid common info len=%u", common_info->len);
+		goto out;
+	}
+
+	/* Get the MLD MAC Address */
+	os_memcpy(mld_addr, common_info->mld_addr, ETH_ALEN);
+	ret = 0;
+
+out:
+	wpabuf_free(mlbuf);
+	return ret;
+}
+
+
 u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
 				 struct ieee802_11_elems *elems,
 				 struct sta_info *sta)
@@ -908,7 +1163,7 @@
 	int ret = -1;
 	u16 ml_control;
 
-	mlbuf = ieee802_11_defrag_mle(elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlbuf = ieee802_11_defrag(elems->basic_mle, elems->basic_mle_len, true);
 	if (!mlbuf)
 		return WLAN_STATUS_SUCCESS;
 
@@ -990,8 +1245,8 @@
 		   info->common_info.eml_capa, info->common_info.mld_capa);
 
 	/* Check the MLD MAC Address */
-	if (os_memcmp(info->common_info.mld_addr, common_info->mld_addr,
-		      ETH_ALEN) != 0) {
+	if (!ether_addr_equal(info->common_info.mld_addr,
+			      common_info->mld_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: MLD address mismatch between authentication ("
 			   MACSTR ") and association (" MACSTR ")",
@@ -1000,7 +1255,7 @@
 		goto out;
 	}
 
-	info->links[hapd->mld_link_id].valid = true;
+	info->links[hapd->mld_link_id].valid = 1;
 
 	/* Parse the link info field */
 	ml_len -= sizeof(*ml) + common_info_len;
@@ -1031,9 +1286,11 @@
 
 		if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
 			wpa_printf(MSG_DEBUG,
-				   "MLD: Unexpected Multi-Link element subelement ID=%u",
+				   "MLD: Skip unknown Multi-Link element subelement ID=%u",
 				   *pos);
-			goto out;
+			pos += 2 + sub_elem_len;
+			ml_len -= 2 + sub_elem_len;
+			continue;
 		}
 
 		/* Skip the subelement ID and the length */
@@ -1138,7 +1395,7 @@
 		goto out;
 	}
 
-	ret = hostapd_mld_validate_assoc_info(hapd, info);
+	ret = hostapd_mld_validate_assoc_info(hapd, sta);
 out:
 	wpabuf_free(mlbuf);
 	if (ret) {
diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index 548a448..4b693a7 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -254,16 +254,15 @@
 			control = 3;
 		else
 			control = center_idx_to_bw_6ghz(seg0);
-		if (hapd->iconf->he_6ghz_reg_pwr_type == 1)
-			control |= HE_6GHZ_STANDARD_POWER_AP <<
-				HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
-		else
-			control |= HE_6GHZ_INDOOR_AP <<
-				HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
+
+		control |= hapd->iconf->he_6ghz_reg_pwr_type <<
+			HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
+
 		*pos++ = control;
 
 		/* Channel Center Freq Seg0/Seg1 */
-		if (oper_chwidth == 2) {
+		if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ ||
+		    oper_chwidth == CONF_OPER_CHWIDTH_320MHZ) {
 			/*
 			 * Seg 0 indicates the channel center frequency index of
 			 * the 160 MHz channel.
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index 31dfb62..0c38483 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / IEEE 802.11 Management
- * Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -21,16 +21,25 @@
 #include "ieee802_11.h"
 
 
+static u8 * hostapd_eid_timeout_interval(u8 *pos, u8 type, u32 value)
+{
+	*pos++ = WLAN_EID_TIMEOUT_INTERVAL;
+	*pos++ = 5;
+	*pos++ = type;
+	WPA_PUT_LE32(pos, value);
+	pos += 4;
+
+	return pos;
+}
+
+
 u8 * hostapd_eid_assoc_comeback_time(struct hostapd_data *hapd,
 				     struct sta_info *sta, u8 *eid)
 {
-	u8 *pos = eid;
 	u32 timeout, tu;
 	struct os_reltime now, passed;
+	u8 type = WLAN_TIMEOUT_ASSOC_COMEBACK;
 
-	*pos++ = WLAN_EID_TIMEOUT_INTERVAL;
-	*pos++ = 5;
-	*pos++ = WLAN_TIMEOUT_ASSOC_COMEBACK;
 	os_get_reltime(&now);
 	os_reltime_sub(&now, &sta->sa_query_start, &passed);
 	tu = (passed.sec * 1000000 + passed.usec) / 1024;
@@ -40,10 +49,12 @@
 		timeout = 0;
 	if (timeout < hapd->conf->assoc_sa_query_max_timeout)
 		timeout++; /* add some extra time for local timers */
-	WPA_PUT_LE32(pos, timeout);
-	pos += 4;
 
-	return pos;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (hapd->conf->test_assoc_comeback_type != -1)
+		type = hapd->conf->test_assoc_comeback_type;
+#endif /* CONFIG_TESTING_OPTIONS */
+	return hostapd_eid_timeout_interval(eid, type, timeout);
 }
 
 
@@ -51,13 +62,14 @@
 void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
 				  const u8 *addr, const u8 *trans_id)
 {
-#ifdef CONFIG_OCV
-	struct sta_info *sta;
-#endif /* CONFIG_OCV */
+#if defined(CONFIG_OCV) || defined(CONFIG_IEEE80211BE)
+	struct sta_info *sta = ap_get_sta(hapd, addr);
+#endif /* CONFIG_OCV || CONFIG_IEEE80211BE */
 	struct ieee80211_mgmt *mgmt;
 	u8 *oci_ie = NULL;
 	u8 oci_ie_len = 0;
 	u8 *end;
+	const u8 *own_addr = hapd->own_addr;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Request to "
 		   MACSTR, MAC2STR(addr));
@@ -65,7 +77,6 @@
 		    trans_id, WLAN_SA_QUERY_TR_ID_LEN);
 
 #ifdef CONFIG_OCV
-	sta = ap_get_sta(hapd, addr);
 	if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) {
 		struct wpa_channel_info ci;
 
@@ -108,11 +119,16 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_SA_QUERY;
 	mgmt->u.action.u.sa_query_req.action = WLAN_SA_QUERY_REQUEST;
 	os_memcpy(mgmt->u.action.u.sa_query_req.trans_id, trans_id,
@@ -141,6 +157,7 @@
 	u8 *oci_ie = NULL;
 	u8 oci_ie_len = 0;
 	u8 *end;
+	const u8 *own_addr = hapd->own_addr;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Received SA Query Request from "
 		   MACSTR, MAC2STR(sa));
@@ -200,11 +217,16 @@
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Response to "
 		   MACSTR, MAC2STR(sa));
 
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(resp->da, sa, ETH_ALEN);
-	os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(resp->sa, own_addr, ETH_ALEN);
+	os_memcpy(resp->bssid, own_addr, ETH_ALEN);
 	resp->u.action.category = WLAN_ACTION_SA_QUERY;
 	resp->u.action.u.sa_query_req.action = WLAN_SA_QUERY_RESPONSE;
 	os_memcpy(resp->u.action.u.sa_query_req.trans_id, trans_id,
@@ -1137,3 +1159,44 @@
 
 	return WLAN_STATUS_SUCCESS;
 }
+
+
+struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct hostapd_data **assoc_hapd)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *other_hapd = NULL;
+	struct sta_info *tmp_sta;
+
+	if (!ap_sta_is_mld(hapd, sta))
+		return NULL;
+
+	*assoc_hapd = hapd;
+
+	/* The station is the one on which the association was performed */
+	if (sta->mld_assoc_link_id == hapd->mld_link_id)
+		return sta;
+
+	other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id);
+	if (!other_hapd) {
+		wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u",
+			   sta->mld_assoc_link_id);
+		return sta;
+	}
+
+	/*
+	 * Iterate over the stations and find the one with the matching link ID
+	 * and association ID.
+	 */
+	for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) {
+		if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id &&
+		    tmp_sta->aid == sta->aid) {
+			*assoc_hapd = other_hapd;
+			return tmp_sta;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return sta;
+}
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 052231e..f13c60a 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -114,12 +114,15 @@
 				      bool authorized, bool mld)
 {
 	int res;
+	bool update;
 
 	if (sta->flags & WLAN_STA_PREAUTH)
 		return;
 
-	ap_sta_set_authorized(hapd, sta, authorized);
+	update = ap_sta_set_authorized_flag(hapd, sta, authorized);
 	res = hostapd_set_authorized(hapd, sta, authorized);
+	if (update)
+		ap_sta_set_authorized_event(hapd, sta, authorized);
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
 		       HOSTAPD_LEVEL_DEBUG, "%sauthorizing port",
 		       authorized ? "" : "un");
diff --git a/src/ap/nan_usd_ap.c b/src/ap/nan_usd_ap.c
new file mode 100644
index 0000000..52a967a
--- /dev/null
+++ b/src/ap/nan_usd_ap.c
@@ -0,0 +1,267 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/wpa_ctrl.h"
+#include "common/nan_de.h"
+#include "hostapd.h"
+#include "ap_drv_ops.h"
+#include "nan_usd_ap.h"
+
+
+static int hostapd_nan_de_tx(void *ctx, unsigned int freq,
+			     unsigned int wait_time,
+			     const u8 *dst, const u8 *src, const u8 *bssid,
+			     const struct wpabuf *buf)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_printf(MSG_DEBUG, "NAN: TX NAN SDF A1=" MACSTR " A2=" MACSTR
+		   " A3=" MACSTR " len=%zu",
+		   MAC2STR(dst), MAC2STR(src), MAC2STR(bssid),
+		   wpabuf_len(buf));
+
+	/* TODO: Force use of OFDM */
+	return hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst,
+				       wpabuf_head(buf), wpabuf_len(buf));
+}
+
+
+static int hostapd_nan_de_listen(void *ctx, unsigned int freq,
+			      unsigned int duration)
+{
+	return 0;
+}
+
+
+static void
+hostapd_nan_de_discovery_result(void *ctx, int subscribe_id,
+				enum nan_service_protocol_type srv_proto_type,
+				const u8 *ssi, size_t ssi_len,
+				int peer_publish_id, const u8 *peer_addr,
+				bool fsd, bool fsd_gas)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_DISCOVERY_RESULT
+		"subscribe_id=%d publish_id=%d address=" MACSTR
+		" fsd=%d fsd_gas=%d srv_proto_type=%u ssi=%s",
+		subscribe_id, peer_publish_id, MAC2STR(peer_addr),
+		fsd, fsd_gas, srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static void
+hostapd_nan_de_replied(void *ctx, int publish_id, const u8 *peer_addr,
+		       int peer_subscribe_id,
+		       enum nan_service_protocol_type srv_proto_type,
+		       const u8 *ssi, size_t ssi_len)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_REPLIED
+		"publish_id=%d address=" MACSTR
+		" subscribe_id=%d srv_proto_type=%u ssi=%s",
+		publish_id, MAC2STR(peer_addr), peer_subscribe_id,
+		srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static const char * nan_reason_txt(enum nan_de_reason reason)
+{
+	switch (reason) {
+	case NAN_DE_REASON_TIMEOUT:
+		return "timeout";
+	case NAN_DE_REASON_USER_REQUEST:
+		return "user-request";
+	case NAN_DE_REASON_FAILURE:
+		return "failure";
+	}
+
+	return "unknown";
+}
+
+
+static void hostapd_nan_de_publish_terminated(void *ctx, int publish_id,
+					      enum nan_de_reason reason)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_PUBLISH_TERMINATED
+		"publish_id=%d reason=%s",
+		publish_id, nan_reason_txt(reason));
+}
+
+
+static void hostapd_nan_de_subscribe_terminated(void *ctx, int subscribe_id,
+						enum nan_de_reason reason)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_SUBSCRIBE_TERMINATED
+		"subscribe_id=%d reason=%s",
+		subscribe_id, nan_reason_txt(reason));
+}
+
+
+static void hostapd_nan_de_receive(void *ctx, int id, int peer_instance_id,
+				   const u8 *ssi, size_t ssi_len,
+				   const u8 *peer_addr)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_RECEIVE
+		"id=%d peer_instance_id=%d address=" MACSTR " ssi=%s",
+		id, peer_instance_id, MAC2STR(peer_addr), ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+int hostapd_nan_usd_init(struct hostapd_data *hapd)
+{
+	struct nan_callbacks cb;
+
+	os_memset(&cb, 0, sizeof(cb));
+	cb.ctx = hapd;
+	cb.tx = hostapd_nan_de_tx;
+	cb.listen = hostapd_nan_de_listen;
+	cb.discovery_result = hostapd_nan_de_discovery_result;
+	cb.replied = hostapd_nan_de_replied;
+	cb.publish_terminated = hostapd_nan_de_publish_terminated;
+	cb.subscribe_terminated = hostapd_nan_de_subscribe_terminated;
+	cb.receive = hostapd_nan_de_receive;
+
+	hapd->nan_de = nan_de_init(hapd->own_addr, true, &cb);
+	if (!hapd->nan_de)
+		return -1;
+	return 0;
+}
+
+
+void hostapd_nan_usd_deinit(struct hostapd_data *hapd)
+{
+	nan_de_deinit(hapd->nan_de);
+	hapd->nan_de = NULL;
+}
+
+
+void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src,
+			    unsigned int freq, const u8 *buf, size_t len)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_rx_sdf(hapd->nan_de, src, freq, buf, len);
+}
+
+
+void hostapd_nan_usd_flush(struct hostapd_data *hapd)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_flush(hapd->nan_de);
+}
+
+
+int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name,
+			    enum nan_service_protocol_type srv_proto_type,
+			    const struct wpabuf *ssi,
+			    struct nan_publish_params *params)
+{
+	int publish_id;
+	struct wpabuf *elems = NULL;
+
+	if (!hapd->nan_de)
+		return -1;
+
+	publish_id = nan_de_publish(hapd->nan_de, service_name, srv_proto_type,
+				    ssi, elems, params);
+	wpabuf_free(elems);
+	return publish_id;
+}
+
+
+void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_cancel_publish(hapd->nan_de, publish_id);
+}
+
+
+int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id,
+				   const struct wpabuf *ssi)
+{
+	int ret;
+
+	if (!hapd->nan_de)
+		return -1;
+	ret = nan_de_update_publish(hapd->nan_de, publish_id, ssi);
+	return ret;
+}
+
+
+int hostapd_nan_usd_subscribe(struct hostapd_data *hapd,
+			      const char *service_name,
+			      enum nan_service_protocol_type srv_proto_type,
+			      const struct wpabuf *ssi,
+			      struct nan_subscribe_params *params)
+{
+	int subscribe_id;
+	struct wpabuf *elems = NULL;
+
+	if (!hapd->nan_de)
+		return -1;
+
+	subscribe_id = nan_de_subscribe(hapd->nan_de, service_name,
+					srv_proto_type, ssi, elems, params);
+	wpabuf_free(elems);
+	return subscribe_id;
+}
+
+
+void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd,
+				      int subscribe_id)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_cancel_subscribe(hapd->nan_de, subscribe_id);
+}
+
+
+int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle,
+			     const struct wpabuf *ssi,
+			     const struct wpabuf *elems,
+			     const u8 *peer_addr, u8 req_instance_id)
+{
+	if (!hapd->nan_de)
+		return -1;
+	return nan_de_transmit(hapd->nan_de, handle, ssi, elems, peer_addr,
+			       req_instance_id);
+}
diff --git a/src/ap/nan_usd_ap.h b/src/ap/nan_usd_ap.h
new file mode 100644
index 0000000..58ff5fc
--- /dev/null
+++ b/src/ap/nan_usd_ap.h
@@ -0,0 +1,46 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef NAN_USD_AP_H
+#define NAN_USD_AP_H
+
+struct nan_subscribe_params;
+struct nan_publish_params;
+enum nan_service_protocol_type;
+
+int hostapd_nan_usd_init(struct hostapd_data *hapd);
+void hostapd_nan_usd_deinit(struct hostapd_data *hapd);
+void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src,
+			    unsigned int freq, const u8 *buf, size_t len);
+void hostapd_nan_usd_flush(struct hostapd_data *hapd);
+int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name,
+			    enum nan_service_protocol_type srv_proto_type,
+			    const struct wpabuf *ssi,
+			    struct nan_publish_params *params);
+void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id);
+int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id,
+				   const struct wpabuf *ssi);
+int hostapd_nan_usd_subscribe(struct hostapd_data *hapd,
+			      const char *service_name,
+			      enum nan_service_protocol_type srv_proto_type,
+			      const struct wpabuf *ssi,
+			      struct nan_subscribe_params *params);
+void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd,
+				      int subscribe_id);
+int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle,
+			     const struct wpabuf *ssi,
+			     const struct wpabuf *elems,
+			     const u8 *peer_addr, u8 req_instance_id);
+void hostapd_nan_usd_remain_on_channel_cb(struct hostapd_data *hapd,
+					  unsigned int freq,
+					  unsigned int duration);
+void hostapd_nan_usd_cancel_remain_on_channel_cb(struct hostapd_data *hapd,
+						 unsigned int freq);
+void hostapd_nan_usd_tx_wait_expire(struct hostapd_data *hapd);
+
+#endif /* NAN_USD_AP_H */
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index 5b276e8..2a25ae2 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -24,7 +24,7 @@
 
 	dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
 			 list) {
-		if (os_memcmp(bssid, nr->bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(bssid, nr->bssid) &&
 		    (!ssid ||
 		     (ssid->ssid_len == nr->ssid.ssid_len &&
 		      os_memcmp(ssid->ssid, nr->ssid.ssid,
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index ee4232f..2fce838 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -487,14 +487,14 @@
 		for (entry = pmksa->pmkid[PMKID_HASH(pmkid)]; entry;
 		     entry = entry->hnext) {
 			if ((spa == NULL ||
-			     os_memcmp(entry->spa, spa, ETH_ALEN) == 0) &&
+			     ether_addr_equal(entry->spa, spa)) &&
 			    os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0)
 				return entry;
 		}
 	} else {
 		for (entry = pmksa->pmksa; entry; entry = entry->next) {
 			if (spa == NULL ||
-			    os_memcmp(entry->spa, spa, ETH_ALEN) == 0)
+			    ether_addr_equal(entry->spa, spa))
 				return entry;
 		}
 	}
@@ -521,7 +521,7 @@
 	u8 new_pmkid[PMKID_LEN];
 
 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
-		if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0)
+		if (!ether_addr_equal(entry->spa, spa))
 			continue;
 		if (wpa_key_mgmt_sae(entry->akmp) ||
 		    wpa_key_mgmt_fils(entry->akmp)) {
@@ -575,7 +575,7 @@
 	int match = 0;
 
 	if (attr->sta_addr) {
-		if (os_memcmp(attr->sta_addr, entry->spa, ETH_ALEN) != 0)
+		if (!ether_addr_equal(attr->sta_addr, entry->spa))
 			return 0;
 		match++;
 	}
@@ -717,7 +717,7 @@
 	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
 	 */
 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
-		if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0)
+		if (addr && !ether_addr_equal(entry->spa, addr))
 			continue;
 
 		ret = os_snprintf(pos, end - pos, MACSTR " ",
diff --git a/src/ap/preauth_auth.c b/src/ap/preauth_auth.c
index 3284a10..cb225c6 100644
--- a/src/ap/preauth_auth.c
+++ b/src/ap/preauth_auth.c
@@ -58,7 +58,7 @@
 	ethhdr = (struct l2_ethhdr *) buf;
 	hdr = (struct ieee802_1x_hdr *) (ethhdr + 1);
 
-	if (os_memcmp(ethhdr->h_dest, hapd->own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(ethhdr->h_dest, hapd->own_addr)) {
 		wpa_printf(MSG_DEBUG, "RSN: pre-auth for foreign address "
 			   MACSTR, MAC2STR(ethhdr->h_dest));
 		return;
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index a00f896..2178d65 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -92,7 +92,7 @@
 		if (p2p_dev_addr == NULL)
 			continue;
 
-		if (os_memcmp(p2p_dev_addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(p2p_dev_addr, addr))
 			return sta;
 	}
 
@@ -140,7 +140,7 @@
 	}
 
 	while (s->hnext != NULL &&
-	       os_memcmp(s->hnext->addr, sta->addr, ETH_ALEN) != 0)
+	       !ether_addr_equal(s->hnext->addr, sta->addr))
 		s = s->hnext;
 	if (s->hnext != NULL)
 		s->hnext = s->hnext->hnext;
@@ -303,7 +303,7 @@
 	ieee802_1x_free_station(hapd, sta);
 
 #ifdef CONFIG_IEEE80211BE
-	if (!hapd->conf->mld_ap || !sta->mld_info.mld_sta ||
+	if (!ap_sta_is_mld(hapd, sta) ||
 	    hapd->mld_link_id == sta->mld_assoc_link_id)
 		wpa_auth_sta_deinit(sta->wpa_sm);
 #else
@@ -350,6 +350,7 @@
 #ifdef CONFIG_INTERWORKING
 	if (sta->gas_dialog) {
 		int i;
+
 		for (i = 0; i < GAS_DIALOG_MAX; i++)
 			gas_serv_dialog_clear(&sta->gas_dialog[i]);
 		os_free(sta->gas_dialog);
@@ -420,6 +421,10 @@
 
 	os_free(sta->ifname_wds);
 
+#ifdef CONFIG_IEEE80211BE
+	ap_sta_free_sta_profile(&sta->mld_info);
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_TESTING_OPTIONS
 	os_free(sta->sae_postponed_commit);
 	forced_memzero(sta->last_tk, WPA_TK_MAX_LEN);
@@ -846,32 +851,20 @@
 }
 
 
-void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
-			 u16 reason)
+static void ap_sta_disconnect_common(struct hostapd_data *hapd,
+				     struct sta_info *sta, unsigned int timeout)
 {
-	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
-		   hapd->conf->iface, MAC2STR(sta->addr));
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
-	if (hapd->iface->current_mode &&
-	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
-		/* Skip deauthentication in DMG/IEEE 802.11ad */
-		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
-				WLAN_STA_ASSOC_REQ_OK);
-		sta->timeout_next = STA_REMOVE;
-	} else {
-		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
-		sta->timeout_next = STA_DEAUTH;
-	}
+
 	ap_sta_set_authorized(hapd, sta, 0);
 	hostapd_set_sta_flags(hapd, sta);
-	wpa_printf(MSG_DEBUG, "%s: reschedule ap_handle_timer timeout "
-		   "for " MACSTR " (%d seconds - "
-		   "AP_MAX_INACTIVITY_AFTER_DISASSOC)",
-		   __func__, MAC2STR(sta->addr),
-		   AP_MAX_INACTIVITY_AFTER_DISASSOC);
+
+	wpa_printf(MSG_DEBUG,
+		   "reschedule ap_handle_timer timeout (%u sec) for " MACSTR,
+		   MAC2STR(sta->addr), timeout);
+
 	eloop_cancel_timeout(ap_handle_timer, hapd, sta);
-	eloop_register_timeout(AP_MAX_INACTIVITY_AFTER_DISASSOC, 0,
-			       ap_handle_timer, hapd, sta);
+	eloop_register_timeout(timeout, 0, ap_handle_timer, hapd, sta);
 	accounting_sta_stop(hapd, sta);
 	ieee802_1x_free_station(hapd, sta);
 #ifdef CONFIG_IEEE80211BE
@@ -883,6 +876,27 @@
 #endif /* CONFIG_IEEE80211BE */
 
 	sta->wpa_sm = NULL;
+}
+
+
+static void ap_sta_handle_disassociate(struct hostapd_data *hapd,
+				       struct sta_info *sta, u16 reason)
+{
+	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
+		   hapd->conf->iface, MAC2STR(sta->addr));
+
+	if (hapd->iface->current_mode &&
+	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
+		/* Skip deauthentication in DMG/IEEE 802.11ad */
+		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
+				WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_REMOVE;
+	} else {
+		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_DEAUTH;
+	}
+
+	ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DISASSOC);
 
 	sta->disassoc_reason = reason;
 	sta->flags |= WLAN_STA_PENDING_DISASSOC_CB;
@@ -905,8 +919,8 @@
 }
 
 
-void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
-			   u16 reason)
+static void ap_sta_handle_deauthenticate(struct hostapd_data *hapd,
+					 struct sta_info *sta, u16 reason)
 {
 	if (hapd->iface->current_mode &&
 	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
@@ -918,21 +932,11 @@
 
 	wpa_printf(MSG_DEBUG, "%s: deauthenticate STA " MACSTR,
 		   hapd->conf->iface, MAC2STR(sta->addr));
-	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
+
 	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
-	ap_sta_set_authorized(hapd, sta, 0);
-	hostapd_set_sta_flags(hapd, sta);
+
 	sta->timeout_next = STA_REMOVE;
-	wpa_printf(MSG_DEBUG, "%s: reschedule ap_handle_timer timeout "
-		   "for " MACSTR " (%d seconds - "
-		   "AP_MAX_INACTIVITY_AFTER_DEAUTH)",
-		   __func__, MAC2STR(sta->addr),
-		   AP_MAX_INACTIVITY_AFTER_DEAUTH);
-	eloop_cancel_timeout(ap_handle_timer, hapd, sta);
-	eloop_register_timeout(AP_MAX_INACTIVITY_AFTER_DEAUTH, 0,
-			       ap_handle_timer, hapd, sta);
-	accounting_sta_stop(hapd, sta);
-	ieee802_1x_free_station(hapd, sta);
+	ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DEAUTH);
 
 	sta->deauth_reason = reason;
 	sta->flags |= WLAN_STA_PENDING_DEAUTH_CB;
@@ -943,6 +947,105 @@
 }
 
 
+static bool ap_sta_ml_disconnect(struct hostapd_data *hapd,
+				 struct sta_info *sta, u16 reason,
+				 bool disassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *assoc_hapd, *tmp_hapd;
+	struct sta_info *assoc_sta;
+	unsigned int i, link_id;
+	struct hapd_interfaces *interfaces;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return false;
+
+	/*
+	 * Get the station on which the association was performed, as it holds
+	 * the information about all the other links.
+	 */
+	assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+	if (!assoc_sta)
+		return false;
+	interfaces = assoc_hapd->iface->interfaces;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		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)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				/*
+				 * Handle the station on which the association
+				 * was done only after all other link station
+				 * are removed. Since there is a only a single
+				 * station per hapd with the same association
+				 * link simply break;
+				 */
+				if (tmp_sta == assoc_sta)
+					break;
+
+				if (tmp_sta->mld_assoc_link_id !=
+				    assoc_sta->mld_assoc_link_id ||
+				    tmp_sta->aid != assoc_sta->aid)
+					continue;
+
+				if (disassoc)
+					ap_sta_handle_disassociate(tmp_hapd,
+								   tmp_sta,
+								   reason);
+				else
+					ap_sta_handle_deauthenticate(tmp_hapd,
+								     tmp_sta,
+								     reason);
+
+				break;
+			}
+		}
+	}
+
+	/* Disconnect the station on which the association was performed. */
+	if (disassoc)
+		ap_sta_handle_disassociate(assoc_hapd, assoc_sta, reason);
+	else
+		ap_sta_handle_deauthenticate(assoc_hapd, assoc_sta, reason);
+
+	return true;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
+			 u16 reason)
+{
+	if (ap_sta_ml_disconnect(hapd, sta, reason, true))
+		return;
+
+	ap_sta_handle_disassociate(hapd, sta, reason);
+}
+
+
+void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
+			   u16 reason)
+{
+	if (ap_sta_ml_disconnect(hapd, sta, reason, false))
+		return;
+
+	ap_sta_handle_deauthenticate(hapd, sta, reason);
+}
+
+
 #ifdef CONFIG_WPS
 int ap_sta_wps_cancel(struct hostapd_data *hapd,
 		      struct sta_info *sta, void *ctx)
@@ -1292,25 +1395,17 @@
 }
 
 
-void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
-			   int authorized)
+bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta,
+				int authorized)
 {
-	const u8 *dev_addr = NULL;
-	char buf[100];
-#ifdef CONFIG_P2P
-	u8 addr[ETH_ALEN];
-	u8 ip_addr_buf[4];
-#endif /* CONFIG_P2P */
-	u8 *ip_ptr = NULL;
-
 	if (!!authorized == !!(sta->flags & WLAN_STA_AUTHORIZED))
-		return;
+		return false;
 
 	if (authorized) {
 		int mld_assoc_link_id = -1;
 
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+		if (ap_sta_is_mld(hapd, sta)) {
 			if (sta->mld_assoc_link_id == hapd->mld_link_id)
 				mld_assoc_link_id = sta->mld_assoc_link_id;
 			else
@@ -1325,6 +1420,21 @@
 		sta->flags &= ~WLAN_STA_AUTHORIZED;
 	}
 
+	return true;
+}
+
+
+void ap_sta_set_authorized_event(struct hostapd_data *hapd,
+				 struct sta_info *sta, int authorized)
+{
+	const u8 *dev_addr = NULL;
+	char buf[100];
+#ifdef CONFIG_P2P
+	u8 addr[ETH_ALEN];
+	u8 ip_addr_buf[4];
+#endif /* CONFIG_P2P */
+	u8 *ip_ptr = NULL;
+
 #ifdef CONFIG_P2P
 	if (hapd->p2p_group == NULL) {
 		if (sta->p2p_ie != NULL &&
@@ -1413,6 +1523,15 @@
 }
 
 
+void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
+			   int authorized)
+{
+	if (!ap_sta_set_authorized_flag(hapd, sta, authorized))
+		return;
+	ap_sta_set_authorized_event(hapd, sta, authorized);
+}
+
+
 void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *addr, u16 reason)
 {
@@ -1604,6 +1723,34 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static void ap_sta_remove_link_sta(struct hostapd_data *hapd,
+				   struct sta_info *sta)
+{
+	struct hostapd_data *tmp_hapd;
+	unsigned int i, j;
+
+	for_each_mld_link(tmp_hapd, i, j, hapd->iface->interfaces,
+			  hapd->conf->mld_id) {
+		struct sta_info *tmp_sta;
+
+		if (hapd == tmp_hapd)
+			continue;
+
+		for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+		     tmp_sta = tmp_sta->next) {
+			if (tmp_sta == sta ||
+			    !ether_addr_equal(tmp_sta->addr, sta->addr))
+				continue;
+
+			ap_free_sta(tmp_hapd, tmp_sta);
+			break;
+		}
+	}
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	const u8 *mld_link_addr = NULL;
@@ -1618,11 +1765,17 @@
 	 */
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		u8 mld_link_id = hapd->mld_link_id;
 
 		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
 		mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr;
+
+		/*
+		 * In case the AP is affiliated with an AP MLD, we need to
+		 * remove the station from all relevant links/APs.
+		 */
+		ap_sta_remove_link_sta(hapd, sta);
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1646,3 +1799,19 @@
 	sta->added_unassoc = 1;
 	return 0;
 }
+
+
+#ifdef CONFIG_IEEE80211BE
+void ap_sta_free_sta_profile(struct mld_info *info)
+{
+	int i;
+
+	if (!info)
+		return;
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		os_free(info->links[i].resp_sta_profile);
+		info->links[i].resp_sta_profile = NULL;
+	}
+}
+#endif /* CONFIG_IEEE80211BE */
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index e2b9dde..b136ff7 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -17,6 +17,7 @@
 #include "common/sae.h"
 #include "crypto/sha384.h"
 #include "pasn/pasn_common.h"
+#include "hostapd.h"
 
 /* STA flags */
 #define WLAN_STA_AUTH BIT(0)
@@ -81,18 +82,18 @@
 	} common_info;
 
 	struct mld_link_info {
-		u8 valid;
+		u8 valid:1;
+		u8 nstr_bitmap_len:2;
 		u8 local_addr[ETH_ALEN];
 		u8 peer_addr[ETH_ALEN];
 
-		size_t nstr_bitmap_len;
 		u8 nstr_bitmap[2];
 
 		u16 capability;
 
 		u16 status;
-		size_t resp_sta_profile_len;
-		u8 resp_sta_profile[EHT_ML_MAX_STA_PROF_LEN];
+		u16 resp_sta_profile_len;
+		u8 *resp_sta_profile;
 
 		const u8 *rsne, *rsnxe;
 	} links[MAX_NUM_MLD_LINKS];
@@ -393,6 +394,10 @@
 void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *addr, u16 reason);
 
+bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta,
+				int authorized);
+void ap_sta_set_authorized_event(struct hostapd_data *hapd,
+				 struct sta_info *sta, int authorized);
 void ap_sta_set_authorized(struct hostapd_data *hapd,
 			   struct sta_info *sta, int authorized);
 static inline int ap_sta_is_authorized(struct sta_info *sta)
@@ -415,4 +420,24 @@
 
 void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta);
 
+static inline bool ap_sta_is_mld(struct hostapd_data *hapd,
+				 struct sta_info *sta)
+{
+#ifdef CONFIG_IEEE80211BE
+	return hapd->conf->mld_ap && sta && sta->mld_info.mld_sta;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+static inline void ap_sta_set_mld(struct sta_info *sta, bool mld)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (sta)
+		sta->mld_info.mld_sta = mld;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+void ap_sta_free_sta_profile(struct mld_info *info);
+
 #endif /* STA_INFO_H */
diff --git a/src/ap/wmm.c b/src/ap/wmm.c
index 9ebb01e..dad768e 100644
--- a/src/ap/wmm.c
+++ b/src/ap/wmm.c
@@ -20,13 +20,6 @@
 #include "ap_drv_ops.h"
 #include "wmm.h"
 
-#ifndef MIN
-#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#endif
-#ifndef MAX
-#define MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
-
 
 static inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci)
 {
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 153ee40..b77e21b 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -44,6 +44,20 @@
 }
 
 
+static const u8 * wnm_ap_get_own_addr(struct hostapd_data *hapd,
+				      struct sta_info *sta)
+{
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && (!sta || ap_sta_is_mld(hapd, sta)))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
+	return own_addr;
+}
+
+
 /* MLME-SLEEPMODE.response */
 static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 					 const u8 *addr, u8 dialog_token,
@@ -63,6 +77,7 @@
 	struct sta_info *sta;
 	enum wnm_oper tfs_oper = action_type == WNM_SLEEP_MODE_ENTER ?
 		WNM_SLEEP_TFS_RESP_IE_ADD : WNM_SLEEP_TFS_RESP_IE_NONE;
+	const u8 *own_addr;
 
 	sta = ap_get_sta(hapd, addr);
 	if (sta == NULL) {
@@ -143,9 +158,12 @@
 		res = -1;
 		goto fail;
 	}
+
+	own_addr = wnm_ap_get_own_addr(hapd, sta);
+
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
@@ -366,6 +384,8 @@
 						  u8 dialog_token)
 {
 	struct ieee80211_mgmt *mgmt;
+	const u8 *own_addr;
+	struct sta_info *sta;
 	size_t len;
 	u8 *pos;
 	int res;
@@ -373,9 +393,13 @@
 	mgmt = os_zalloc(sizeof(*mgmt));
 	if (mgmt == NULL)
 		return -1;
+
+	sta = ap_get_sta(hapd, addr);
+	own_addr = wnm_ap_get_own_addr(hapd, sta);
+
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
@@ -821,14 +845,15 @@
 {
 	u8 buf[1000], *pos;
 	struct ieee80211_mgmt *mgmt;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	os_memset(buf, 0, sizeof(buf));
 	mgmt = (struct ieee80211_mgmt *) buf;
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
@@ -887,14 +912,15 @@
 	u8 buf[1000], *pos;
 	struct ieee80211_mgmt *mgmt;
 	size_t url_len;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	os_memset(buf, 0, sizeof(buf));
 	mgmt = (struct ieee80211_mgmt *) buf;
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
@@ -939,6 +965,7 @@
 	u8 *buf, *pos;
 	struct ieee80211_mgmt *mgmt;
 	size_t url_len;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to "
 		   MACSTR
@@ -952,8 +979,8 @@
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token;
@@ -1002,6 +1029,26 @@
 	os_free(buf);
 
 	if (disassoc_timer) {
+#ifdef CONFIG_IEEE80211BE
+		if (ap_sta_is_mld(hapd, sta)) {
+			int i;
+			unsigned int links = 0;
+
+			for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+				if (sta->mld_info.links[i].valid)
+					links++;
+			}
+
+			if (links > 1) {
+				wpa_printf(MSG_DEBUG,
+					   "WNM: Only terminating one link - other links remains associated for "
+					   MACSTR,
+					   MAC2STR(sta->mld_info.common_info.mld_addr));
+				return 0;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
+
 		/* send disassociation frame after time-out */
 		set_disassoc_timer(hapd, sta, disassoc_timer);
 	}
@@ -1016,6 +1063,7 @@
 	u8 buf[100], *pos;
 	struct ieee80211_mgmt *mgmt;
 	u8 dialog_token = 1;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	if (auto_report > 3 || timeout > 63)
 		return -1;
@@ -1024,8 +1072,8 @@
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.coloc_intf_req.action =
 		WNM_COLLOCATED_INTERFERENCE_REQ;
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index a662201..3002d91 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -194,6 +194,9 @@
 
 	if (!wpa_auth->cb->get_seqnum)
 		return -1;
+#ifdef CONFIG_TESTING_OPTIONS
+	os_memset(seq, 0, WPA_KEY_RSC_LEN);
+#endif /* CONFIG_TESTING_OPTIONS */
 	res = wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
 #ifdef CONFIG_TESTING_OPTIONS
 	if (!addr && idx < 4 && wpa_auth->conf.gtk_rsc_override_set) {
@@ -599,6 +602,15 @@
 	}
 #endif /* CONFIG_P2P */
 
+	if (conf->tx_bss_auth && conf->beacon_prot) {
+		conf->tx_bss_auth->non_tx_beacon_prot = true;
+		if (!conf->tx_bss_auth->conf.beacon_prot)
+			conf->tx_bss_auth->conf.beacon_prot = true;
+		if (!conf->tx_bss_auth->conf.group_mgmt_cipher)
+			conf->tx_bss_auth->conf.group_mgmt_cipher =
+				conf->group_mgmt_cipher;
+	}
+
 	return wpa_auth;
 }
 
@@ -618,6 +630,17 @@
 }
 
 
+static void wpa_auth_free_conf(struct wpa_auth_config *conf)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	wpabuf_free(conf->eapol_m1_elements);
+	conf->eapol_m1_elements = NULL;
+	wpabuf_free(conf->eapol_m3_elements);
+	conf->eapol_m3_elements = NULL;
+#endif /* CONFIG_TESTING_OPTIONS */
+}
+
+
 /**
  * wpa_deinit - Deinitialize WPA authenticator
  * @wpa_auth: Pointer to WPA authenticator data from wpa_init()
@@ -651,6 +674,7 @@
 		bin_clear_free(prev, sizeof(*prev));
 	}
 
+	wpa_auth_free_conf(&wpa_auth->conf);
 	os_free(wpa_auth);
 }
 
@@ -668,6 +692,7 @@
 	if (!wpa_auth)
 		return 0;
 
+	wpa_auth_free_conf(&wpa_auth->conf);
 	os_memcpy(&wpa_auth->conf, conf, sizeof(*conf));
 	if (wpa_auth_gen_wpa_ie(wpa_auth)) {
 		wpa_printf(MSG_ERROR, "Could not generate WPA IE.");
@@ -900,19 +925,70 @@
 			       struct wpa_state_machine *sm,
 			       struct wpa_eapol_ie_parse *kde)
 {
-	struct wpa_ie_data ie;
+	struct wpa_ie_data ie, assoc_ie;
 	struct rsn_mdie *mdie;
+	unsigned int i, j;
+	bool found = false;
+
+	/* Verify that PMKR1Name from EAPOL-Key message 2/4 matches the value
+	 * we derived. */
 
 	if (wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &ie) < 0 ||
-	    ie.num_pmkid != 1 || !ie.pmkid) {
+	    ie.num_pmkid < 1 || !ie.pmkid) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: No PMKR1Name in FT 4-way handshake message 2/4");
 		return -1;
 	}
 
-	os_memcpy(sm->sup_pmk_r1_name, ie.pmkid, PMKID_LEN);
-	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from Supplicant",
-		    sm->sup_pmk_r1_name, PMKID_LEN);
+	if (wpa_parse_wpa_ie_rsn(sm->wpa_ie, sm->wpa_ie_len, &assoc_ie) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FT: Could not parse (Re)Association Request frame RSNE");
+		os_memset(&assoc_ie, 0, sizeof(assoc_ie));
+		/* Continue to allow PMKR1Name matching to be done to cover the
+		 * case where it is the only listed PMKID. */
+	}
+
+	for (i = 0; i < ie.num_pmkid; i++) {
+		const u8 *pmkid = ie.pmkid + i * PMKID_LEN;
+
+		if (os_memcmp_const(pmkid, sm->pmk_r1_name,
+				    WPA_PMK_NAME_LEN) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "FT: RSNE[PMKID[%u]] from supplicant matches PMKR1Name",
+				   i);
+			found = true;
+		} else {
+			for (j = 0; j < assoc_ie.num_pmkid; j++) {
+				if (os_memcmp(pmkid,
+					      assoc_ie.pmkid + j * PMKID_LEN,
+					      PMKID_LEN) == 0)
+					break;
+			}
+
+			if (j == assoc_ie.num_pmkid) {
+				wpa_printf(MSG_DEBUG,
+					   "FT: RSNE[PMKID[%u]] from supplicant is neither PMKR1Name nor included in AssocReq",
+					   i);
+				found = false;
+				break;
+			}
+			wpa_printf(MSG_DEBUG,
+				   "FT: RSNE[PMKID[%u]] from supplicant is not PMKR1Name, but matches a PMKID in AssocReq",
+				   i);
+		}
+	}
+
+	if (!found) {
+		wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_DEBUG,
+				"PMKR1Name mismatch in FT 4-way handshake");
+		wpa_hexdump(MSG_DEBUG,
+			    "FT: PMKIDs/PMKR1Name from Supplicant",
+			    ie.pmkid, ie.num_pmkid * PMKID_LEN);
+		wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
+			    sm->pmk_r1_name, WPA_PMK_NAME_LEN);
+		return -1;
+	}
 
 	if (!kde->mdie || !kde->ftie) {
 		wpa_printf(MSG_DEBUG,
@@ -1076,28 +1152,166 @@
 }
 
 
+enum eapol_key_msg { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST };
+
+static bool wpa_auth_valid_key_desc_ver(struct wpa_authenticator *wpa_auth,
+					struct wpa_state_machine *sm, u16 ver)
+{
+	if (ver > WPA_KEY_INFO_TYPE_AES_128_CMAC) {
+		wpa_printf(MSG_INFO, "RSN: " MACSTR
+			   " used undefined Key Descriptor Version %d",
+			   MAC2STR(wpa_auth_get_spa(sm)), ver);
+		return false;
+	}
+
+	if (!wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    wpa_use_cmac(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"advertised support for AES-128-CMAC, but did not use it");
+		return false;
+	}
+
+	if (sm->pairwise != WPA_CIPHER_TKIP &&
+	    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    !wpa_use_cmac(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"did not use HMAC-SHA1-AES with CCMP/GCMP");
+		return false;
+	}
+
+	if (wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases");
+		return false;
+	}
+
+	return true;
+}
+
+
+static bool wpa_auth_valid_request_counter(struct wpa_authenticator *wpa_auth,
+					   struct wpa_state_machine *sm,
+					   const u8 *replay_counter)
+{
+
+	if (sm->req_replay_counter_used &&
+	    os_memcmp(replay_counter, sm->req_replay_counter,
+		      WPA_REPLAY_COUNTER_LEN) <= 0) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"received EAPOL-Key request with replayed counter");
+		return false;
+	}
+
+	return true;
+}
+
+
+static bool wpa_auth_valid_counter(struct wpa_authenticator *wpa_auth,
+				   struct wpa_state_machine *sm,
+				   const struct wpa_eapol_key *key,
+				   enum eapol_key_msg msg,
+				   const char *msgtxt)
+{
+	int i;
+
+	if (msg == REQUEST)
+		return wpa_auth_valid_request_counter(wpa_auth, sm,
+						      key->replay_counter);
+
+	if (wpa_replay_counter_valid(sm->key_replay, key->replay_counter))
+		return true;
+
+	if (msg == PAIRWISE_2 &&
+	    wpa_replay_counter_valid(sm->prev_key_replay,
+				     key->replay_counter) &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+	    os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0) {
+		/*
+		 * Some supplicant implementations (e.g., Windows XP
+		 * WZC) update SNonce for each EAPOL-Key 2/4. This
+		 * breaks the workaround on accepting any of the
+		 * pending requests, so allow the SNonce to be updated
+		 * even if we have already sent out EAPOL-Key 3/4.
+		 */
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "Process SNonce update from STA based on retransmitted EAPOL-Key 1/4");
+		sm->update_snonce = 1;
+		os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN);
+		sm->alt_snonce_valid = true;
+		os_memcpy(sm->alt_replay_counter,
+			  sm->key_replay[0].counter,
+			  WPA_REPLAY_COUNTER_LEN);
+		return true;
+	}
+
+	if (msg == PAIRWISE_4 && sm->alt_snonce_valid &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+	    os_memcmp(key->replay_counter, sm->alt_replay_counter,
+		      WPA_REPLAY_COUNTER_LEN) == 0) {
+		/*
+		 * Supplicant may still be using the old SNonce since
+		 * there was two EAPOL-Key 2/4 messages and they had
+		 * different SNonce values.
+		 */
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4");
+		return true;
+	}
+
+	if (msg == PAIRWISE_2 &&
+	    wpa_replay_counter_valid(sm->prev_key_replay,
+				     key->replay_counter) &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "ignore retransmitted EAPOL-Key %s - SNonce did not change",
+				 msgtxt);
+	} else {
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "received EAPOL-Key %s with unexpected replay counter",
+				 msgtxt);
+	}
+	for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
+		if (!sm->key_replay[i].valid)
+			break;
+		wpa_hexdump(MSG_DEBUG, "pending replay counter",
+			    sm->key_replay[i].counter,
+			    WPA_REPLAY_COUNTER_LEN);
+	}
+	wpa_hexdump(MSG_DEBUG, "received replay counter",
+		    key->replay_counter, WPA_REPLAY_COUNTER_LEN);
+	return false;
+}
+
+
 void wpa_receive(struct wpa_authenticator *wpa_auth,
 		 struct wpa_state_machine *sm,
 		 u8 *data, size_t data_len)
 {
 	struct ieee802_1x_hdr *hdr;
 	struct wpa_eapol_key *key;
-	u16 key_info, key_data_length;
-	enum { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST } msg;
-	char *msgtxt;
-	struct wpa_eapol_ie_parse kde;
+	u16 key_info, ver, key_data_length;
+	enum eapol_key_msg msg;
+	const char *msgtxt;
 	const u8 *key_data;
 	size_t keyhdrlen, mic_len;
 	u8 *mic;
-	bool is_mld = false;
+	u8 *key_data_buf = NULL;
+	size_t key_data_buf_len = 0;
 
 	if (!wpa_auth || !wpa_auth->conf.wpa || !sm)
 		return;
 
-#ifdef CONFIG_IEEE80211BE
-	is_mld = sm->mld_assoc_link_id >= 0;
-#endif /* CONFIG_IEEE80211BE */
-
 	wpa_hexdump(MSG_MSGDUMP, "WPA: RX EAPOL data", data, data_len);
 
 	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
@@ -1167,11 +1381,31 @@
 		return;
 	}
 
-	/* TODO: Make this more robust for distinguising EAPOL-Key msg 2/4 from
-	 * 4/4. Secure=1 is used in msg 2/4 when doing PTK rekeying, so the
-	 * MLD mechanism here does not work without the somewhat undesired check
-	 * on wpa_ptk_state.. Would likely need to decrypt Key Data first to be
-	 * able to know which message this is in MLO cases.. */
+	ver = key_info & WPA_KEY_INFO_TYPE_MASK;
+	if (!wpa_auth_valid_key_desc_ver(wpa_auth, sm, ver))
+		goto out;
+	if (mic_len > 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
+	    sm->PTK_valid &&
+	    (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES ||
+	     ver == WPA_KEY_INFO_TYPE_AES_128_CMAC ||
+	     wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) &&
+	    key_data_length >= 8 && key_data_length % 8 == 0) {
+		key_data_length -= 8; /* AES-WRAP adds 8 bytes */
+		key_data_buf = os_malloc(key_data_length);
+		if (!key_data_buf)
+			goto out;
+		key_data_buf_len = key_data_length;
+		if (aes_unwrap(sm->PTK.kek, sm->PTK.kek_len,
+			       key_data_length / 8, key_data, key_data_buf)) {
+			wpa_printf(MSG_INFO,
+				   "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data");
+			goto out;
+		}
+		key_data = key_data_buf;
+		wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data",
+				key_data, key_data_length);
+	}
+
 	if (key_info & WPA_KEY_INFO_REQUEST) {
 		msg = REQUEST;
 		msgtxt = "Request";
@@ -1179,10 +1413,13 @@
 		msg = GROUP_2;
 		msgtxt = "2/2 Group";
 	} else if (key_data_length == 0 ||
+		   (sm->wpa == WPA_VERSION_WPA2 &&
+		    (!(key_info & WPA_KEY_INFO_ENCR_KEY_DATA) ||
+		     key_data_buf) &&
+		    (key_info & WPA_KEY_INFO_SECURE) &&
+		    !get_ie(key_data, key_data_length, WLAN_EID_RSN)) ||
 		   (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
-		    key_data_length == AES_BLOCK_SIZE) ||
-		   (is_mld && (key_info & WPA_KEY_INFO_SECURE) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING)) {
+		    key_data_length == AES_BLOCK_SIZE)) {
 		msg = PAIRWISE_4;
 		msgtxt = "4/4 Pairwise";
 	} else {
@@ -1190,127 +1427,15 @@
 		msgtxt = "2/4 Pairwise";
 	}
 
-	if (msg == REQUEST || msg == PAIRWISE_2 || msg == PAIRWISE_4 ||
-	    msg == GROUP_2) {
-		u16 ver = key_info & WPA_KEY_INFO_TYPE_MASK;
-		if (sm->pairwise == WPA_CIPHER_CCMP ||
-		    sm->pairwise == WPA_CIPHER_GCMP) {
-			if (wpa_use_cmac(sm->wpa_key_mgmt) &&
-			    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-			    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) {
-				wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-						LOGGER_WARNING,
-						"advertised support for AES-128-CMAC, but did not use it");
-				return;
-			}
+	if (!wpa_auth_valid_counter(wpa_auth, sm, key, msg, msgtxt))
+		goto out;
 
-			if (!wpa_use_cmac(sm->wpa_key_mgmt) &&
-			    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-			    ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) {
-				wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-						LOGGER_WARNING,
-						"did not use HMAC-SHA1-AES with CCMP/GCMP");
-				return;
-			}
-		}
-
-		if (wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-		    ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) {
-			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_WARNING,
-					"did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases");
-			return;
-		}
-	}
-
-	if (key_info & WPA_KEY_INFO_REQUEST) {
-		if (sm->req_replay_counter_used &&
-		    os_memcmp(key->replay_counter, sm->req_replay_counter,
-			      WPA_REPLAY_COUNTER_LEN) <= 0) {
-			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_WARNING,
-					"received EAPOL-Key request with replayed counter");
-			return;
-		}
-	}
-
-	if (!(key_info & WPA_KEY_INFO_REQUEST) &&
-	    !wpa_replay_counter_valid(sm->key_replay, key->replay_counter)) {
-		int i;
-
-		if (msg == PAIRWISE_2 &&
-		    wpa_replay_counter_valid(sm->prev_key_replay,
-					     key->replay_counter) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
-		    os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0)
-		{
-			/*
-			 * Some supplicant implementations (e.g., Windows XP
-			 * WZC) update SNonce for each EAPOL-Key 2/4. This
-			 * breaks the workaround on accepting any of the
-			 * pending requests, so allow the SNonce to be updated
-			 * even if we have already sent out EAPOL-Key 3/4.
-			 */
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "Process SNonce update from STA based on retransmitted EAPOL-Key 1/4");
-			sm->update_snonce = 1;
-			os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN);
-			sm->alt_snonce_valid = true;
-			os_memcpy(sm->alt_replay_counter,
-				  sm->key_replay[0].counter,
-				  WPA_REPLAY_COUNTER_LEN);
-			goto continue_processing;
-		}
-
-		if (msg == PAIRWISE_4 && sm->alt_snonce_valid &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
-		    os_memcmp(key->replay_counter, sm->alt_replay_counter,
-			      WPA_REPLAY_COUNTER_LEN) == 0) {
-			/*
-			 * Supplicant may still be using the old SNonce since
-			 * there was two EAPOL-Key 2/4 messages and they had
-			 * different SNonce values.
-			 */
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4");
-			goto continue_processing;
-		}
-
-		if (msg == PAIRWISE_2 &&
-		    wpa_replay_counter_valid(sm->prev_key_replay,
-					     key->replay_counter) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "ignore retransmitted EAPOL-Key %s - SNonce did not change",
-					 msgtxt);
-		} else {
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "received EAPOL-Key %s with unexpected replay counter",
-					 msgtxt);
-		}
-		for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
-			if (!sm->key_replay[i].valid)
-				break;
-			wpa_hexdump(MSG_DEBUG, "pending replay counter",
-				    sm->key_replay[i].counter,
-				    WPA_REPLAY_COUNTER_LEN);
-		}
-		wpa_hexdump(MSG_DEBUG, "received replay counter",
-			    key->replay_counter, WPA_REPLAY_COUNTER_LEN);
-		return;
-	}
-
-continue_processing:
 #ifdef CONFIG_FILS
 	if (sm->wpa == WPA_VERSION_WPA2 && mic_len == 0 &&
 	    !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
 		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
 				 "WPA: Encr Key Data bit not set even though AEAD cipher is supposed to be used - drop frame");
-		return;
+		goto out;
 	}
 #endif /* CONFIG_FILS */
 
@@ -1324,7 +1449,7 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 2/4 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_state);
-			return;
+			goto out;
 		}
 		random_add_randomness(key->key_nonce, WPA_NONCE_LEN);
 		if (sm->group->reject_4way_hs_for_entropy) {
@@ -1342,7 +1467,7 @@
 			random_mark_pool_ready();
 			wpa_sta_disconnect(wpa_auth, sm->addr,
 					   WLAN_REASON_PREV_AUTH_NOT_VALID);
-			return;
+			goto out;
 		}
 		break;
 	case PAIRWISE_4:
@@ -1352,7 +1477,7 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 4/4 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_state);
-			return;
+			goto out;
 		}
 		break;
 	case GROUP_2:
@@ -1362,10 +1487,20 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 2/2 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_group_state);
-			return;
+			goto out;
 		}
 		break;
 	case REQUEST:
+		if (sm->wpa_ptk_state == WPA_PTK_PTKSTART ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING2 ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
+			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+					 LOGGER_INFO,
+					 "received EAPOL-Key Request in invalid state (%d) - dropped",
+					 sm->wpa_ptk_state);
+			goto out;
+		}
 		break;
 	}
 
@@ -1375,14 +1510,14 @@
 	if (key_info & WPA_KEY_INFO_ACK) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key Ack set");
-		return;
+		goto out;
 	}
 
 	if (!wpa_key_mgmt_fils(sm->wpa_key_mgmt) &&
 	    !(key_info & WPA_KEY_INFO_MIC)) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key MIC not set");
-		return;
+		goto out;
 	}
 
 #ifdef CONFIG_FILS
@@ -1390,7 +1525,7 @@
 	    (key_info & WPA_KEY_INFO_MIC)) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key MIC set");
-		return;
+		goto out;
 	}
 #endif /* CONFIG_FILS */
 
@@ -1409,7 +1544,7 @@
 				   "TEST: Ignore Key MIC failure for fuzz testing");
 			goto continue_fuzz;
 #endif /* TEST_FUZZ */
-			return;
+			goto out;
 		}
 #ifdef CONFIG_FILS
 		if (!mic_len &&
@@ -1423,7 +1558,7 @@
 				   "TEST: Ignore Key MIC failure for fuzz testing");
 			goto continue_fuzz;
 #endif /* TEST_FUZZ */
-			return;
+			goto out;
 		}
 #endif /* CONFIG_FILS */
 #ifdef TEST_FUZZ
@@ -1435,6 +1570,12 @@
 	}
 
 	if (key_info & WPA_KEY_INFO_REQUEST) {
+		if (!(key_info & WPA_KEY_INFO_SECURE)) {
+			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+					LOGGER_INFO,
+					"received EAPOL-Key request without Secure=1");
+			goto out;
+		}
 		if (sm->MICVerified) {
 			sm->req_replay_counter_used = 1;
 			os_memcpy(sm->req_replay_counter, key->replay_counter,
@@ -1443,28 +1584,19 @@
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
 					"received EAPOL-Key request with invalid MIC");
-			return;
+			goto out;
 		}
 
-		/*
-		 * TODO: should decrypt key data field if encryption was used;
-		 * even though MAC address KDE is not normally encrypted,
-		 * supplicant is allowed to encrypt it.
-		 */
 		if (key_info & WPA_KEY_INFO_ERROR) {
 			if (wpa_receive_error_report(
 				    wpa_auth, sm,
 				    !(key_info & WPA_KEY_INFO_KEY_TYPE)) > 0)
-				return; /* STA entry was removed */
+				goto out; /* STA entry was removed */
 		} else if (key_info & WPA_KEY_INFO_KEY_TYPE) {
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
 					"received EAPOL-Key Request for new 4-Way Handshake");
 			wpa_request_new_ptk(sm);
-		} else if (key_data_length > 0 &&
-			   wpa_parse_kde_ies(key_data, key_data_length,
-					     &kde) == 0 &&
-			   kde.mac_addr) {
 		} else {
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
@@ -1506,7 +1638,7 @@
 	os_free(sm->last_rx_eapol_key);
 	sm->last_rx_eapol_key = os_memdup(data, data_len);
 	if (!sm->last_rx_eapol_key)
-		return;
+		goto out;
 	sm->last_rx_eapol_key_len = data_len;
 
 	sm->rx_eapol_key_secure = !!(key_info & WPA_KEY_INFO_SECURE);
@@ -1515,6 +1647,9 @@
 	sm->EAPOLKeyRequest = !!(key_info & WPA_KEY_INFO_REQUEST);
 	os_memcpy(sm->SNonce, key->key_nonce, WPA_NONCE_LEN);
 	wpa_sm_step(sm);
+
+out:
+	bin_clear_free(key_data_buf, key_data_buf_len);
 }
 
 
@@ -2332,10 +2467,14 @@
 
 SM_STATE(WPA_PTK, PTKSTART)
 {
-	u8 buf[2 * (2 + RSN_SELECTOR_LEN) + PMKID_LEN + ETH_ALEN];
+	u8 *buf;
+	size_t buf_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
 	u8 *pmkid = NULL;
 	size_t kde_len = 0;
 	u16 key_info;
+#ifdef CONFIG_TESTING_OPTIONS
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+#endif /* CONFIG_TESTING_OPTIONS */
 
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
 	sm->PTKRequest = false;
@@ -2350,6 +2489,19 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		buf_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+#endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m1_elements)
+		buf_len += wpabuf_len(conf->eapol_m1_elements);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	buf = os_zalloc(buf_len);
+	if (!buf)
+		return;
+
 	wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
 			"sending 1/4 msg of 4-Way Handshake");
 	/*
@@ -2453,11 +2605,20 @@
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m1_elements) {
+		os_memcpy(buf + kde_len, wpabuf_head(conf->eapol_m1_elements),
+			  wpabuf_len(conf->eapol_m1_elements));
+		kde_len += wpabuf_len(conf->eapol_m1_elements);
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	key_info = WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE;
 	if (sm->pairwise_set && sm->wpa != WPA_VERSION_WPA)
 		key_info |= WPA_KEY_INFO_SECURE;
 	wpa_send_eapol(sm->wpa_auth, sm, key_info, NULL,
 		       sm->ANonce, kde_len ? buf : NULL, kde_len, 0, 0);
+	os_free(buf);
 }
 
 
@@ -3178,7 +3339,7 @@
 
 	/* MLD MAC address must be the same */
 	if (!kde->mac_addr ||
-	    os_memcmp(kde->mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(kde->mac_addr, sm->peer_mld_addr)) {
 		wpa_printf(MSG_DEBUG, "RSN: MLD: Invalid MLD address");
 		return -1;
 	}
@@ -3205,8 +3366,8 @@
 			return -1;
 		}
 
-		if (os_memcmp(sm->mld_links[i].peer_addr, kde->mlo_link[i] + 1,
-			      ETH_ALEN) != 0) {
+		if (!ether_addr_equal(sm->mld_links[i].peer_addr,
+				      kde->mlo_link[i] + 1)) {
 			wpa_printf(MSG_DEBUG,
 				   "RSN: MLD: invalid MAC address=" MACSTR
 				   " expected " MACSTR " (link ID %u)",
@@ -3240,7 +3401,7 @@
 	size_t pmk_len;
 	int ft;
 	const u8 *eapol_key_ie, *key_data, *mic;
-	u16 key_data_length;
+	u16 key_info, ver, key_data_length;
 	size_t mic_len, eapol_key_ie_len;
 	struct ieee802_1x_hdr *hdr;
 	struct wpa_eapol_key *key;
@@ -3250,6 +3411,8 @@
 	u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN];
 	u8 pmk_r1[PMK_LEN_MAX];
 	size_t key_len;
+	u8 *key_data_buf = NULL;
+	size_t key_data_buf_len = 0;
 
 	SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk);
 	sm->EAPOLKeyReceived = false;
@@ -3357,12 +3520,46 @@
 	hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key;
 	key = (struct wpa_eapol_key *) (hdr + 1);
 	mic = (u8 *) (key + 1);
+	key_info = WPA_GET_BE16(key->key_info);
 	key_data = mic + mic_len + 2;
 	key_data_length = WPA_GET_BE16(mic + mic_len);
 	if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) -
 	    sizeof(*key) - mic_len - 2)
 		goto out;
 
+	ver = key_info & WPA_KEY_INFO_TYPE_MASK;
+	if (mic_len && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		if (ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES &&
+		    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC &&
+		    !wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) {
+			wpa_printf(MSG_INFO,
+				   "Unsupported EAPOL-Key Key Data field encryption");
+			goto out;
+		}
+
+		if (key_data_length < 8 || key_data_length % 8) {
+			wpa_printf(MSG_INFO,
+				   "RSN: Unsupported AES-WRAP len %u",
+				   key_data_length);
+			goto out;
+		}
+		key_data_length -= 8; /* AES-WRAP adds 8 bytes */
+		key_data_buf = os_malloc(key_data_length);
+		if (!key_data_buf)
+			goto out;
+		key_data_buf_len = key_data_length;
+		if (aes_unwrap(PTK.kek, PTK.kek_len, key_data_length / 8,
+			       key_data, key_data_buf)) {
+			bin_clear_free(key_data_buf, key_data_buf_len);
+			wpa_printf(MSG_INFO,
+				   "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data");
+			goto out;
+		}
+		key_data = key_data_buf;
+		wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data",
+				key_data, key_data_length);
+	}
+
 	if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) {
 		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				 "received EAPOL-Key msg 2/4 with invalid Key Data contents");
@@ -3507,27 +3704,6 @@
 		return;
 	}
 
-#ifdef CONFIG_IEEE80211R_AP
-	if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
-		/*
-		 * Verify that PMKR1Name from EAPOL-Key message 2/4 matches
-		 * with the value we derived.
-		 */
-		if (os_memcmp_const(sm->sup_pmk_r1_name, sm->pmk_r1_name,
-				    WPA_PMK_NAME_LEN) != 0) {
-			wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_DEBUG,
-					"PMKR1Name mismatch in FT 4-way handshake");
-			wpa_hexdump(MSG_DEBUG,
-				    "FT: PMKR1Name from Supplicant",
-				    sm->sup_pmk_r1_name, WPA_PMK_NAME_LEN);
-			wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
-				    sm->pmk_r1_name, WPA_PMK_NAME_LEN);
-			goto out;
-		}
-	}
-#endif /* CONFIG_IEEE80211R_AP */
-
 	if (vlan_id && wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) &&
 	    wpa_auth_update_vlan(wpa_auth, sm->addr, vlan_id) < 0) {
 		wpa_sta_disconnect(wpa_auth, sm->addr,
@@ -3562,6 +3738,7 @@
 out:
 	forced_memzero(pmk_r0, sizeof(pmk_r0));
 	forced_memzero(pmk_r1, sizeof(pmk_r1));
+	bin_clear_free(key_data_buf, key_data_buf_len);
 }
 
 
@@ -3575,14 +3752,18 @@
 static int ieee80211w_kde_len(struct wpa_state_machine *sm)
 {
 	size_t len = 0;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
 
 	if (sm->mgmt_frame_prot) {
 		len += 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN;
-		len += wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+		len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	}
+
+	if (wpa_auth->conf.tx_bss_auth)
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
 	if (sm->mgmt_frame_prot && sm->wpa_auth->conf.beacon_prot) {
 		len += 2 + RSN_SELECTOR_LEN + WPA_BIGTK_KDE_PREFIX_LEN;
-		len += wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+		len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	}
 
 	return len;
@@ -3595,7 +3776,8 @@
 	struct wpa_bigtk_kde bigtk;
 	struct wpa_group *gsm = sm->group;
 	u8 rsc[WPA_KEY_RSC_LEN];
-	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_auth_config *conf = &wpa_auth->conf;
 	size_t len = wpa_cipher_key_len(conf->group_mgmt_cipher);
 
 	if (!sm->mgmt_frame_prot)
@@ -3627,7 +3809,14 @@
 			  NULL, 0);
 	forced_memzero(&igtk, sizeof(igtk));
 
-	if (!conf->beacon_prot)
+	if (wpa_auth->conf.tx_bss_auth) {
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
+		conf = &wpa_auth->conf;
+		len = wpa_cipher_key_len(conf->group_mgmt_cipher);
+		gsm = wpa_auth->group;
+	}
+
+	if (!sm->wpa_auth->conf.beacon_prot)
 		return pos;
 
 	bigtk.keyid[0] = gsm->GN_bigtk;
@@ -3785,6 +3974,11 @@
 	if (!beacon_prot)
 		return;
 
+	if (a->conf.tx_bss_auth) {
+		a = a->conf.tx_bss_auth;
+		gsm = a->group;
+	}
+
 	info->bigtkidx = gsm->GN_bigtk;
 	info->bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
 
@@ -3807,6 +4001,7 @@
 
 static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm)
 {
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
 	struct wpa_group *gsm = sm->group;
 	size_t gtk_len = gsm->GTK_len;
 	size_t igtk_len;
@@ -3825,10 +4020,15 @@
 		return kde_len;
 
 	/* MLO IGTK KDE for each link */
-	igtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	igtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	kde_len += n_links * (2 + RSN_SELECTOR_LEN + 2 + 6 + 1 + igtk_len);
 
-	if (!sm->wpa_auth->conf.beacon_prot)
+	if (wpa_auth->conf.tx_bss_auth) {
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
+		igtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
+	}
+
+	if (!wpa_auth->conf.beacon_prot)
 		return kde_len;
 
 	/* MLO BIGTK KDE for each link */
@@ -3865,7 +4065,8 @@
 
 	/* Add MLO GTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].gtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO GTK: link=%u", link_id);
@@ -3897,7 +4098,8 @@
 
 	/* Add MLO IGTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].igtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO IGTK: link=%u", link_id);
@@ -3936,7 +4138,9 @@
 
 	/* Add MLO BIGTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].bigtk ||
+		    !ml_key_info.links[i].igtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO BIGTK: link=%u", link_id);
@@ -4233,6 +4437,11 @@
 
 	kde_len += wpa_auth_ml_kdes_len(sm);
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m3_elements)
+		kde_len += wpabuf_len(conf->eapol_m3_elements);
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	kde = os_malloc(kde_len);
 	if (!kde)
 		goto done;
@@ -4248,7 +4457,7 @@
 		size_t elen;
 
 		elen = pos - kde;
-		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name);
+		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true);
 		if (res < 0) {
 			wpa_printf(MSG_ERROR,
 				   "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data");
@@ -4347,6 +4556,17 @@
 
 	pos = wpa_auth_ml_kdes(sm, pos);
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m3_elements) {
+		os_memcpy(pos, wpabuf_head(conf->eapol_m3_elements),
+			  wpabuf_len(conf->eapol_m3_elements));
+		pos += wpabuf_len(conf->eapol_m3_elements);
+	}
+
+	if (conf->eapol_m3_no_encrypt)
+		encr = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	wpa_send_eapol(sm->wpa_auth, sm,
 		       (secure ? WPA_KEY_INFO_SECURE : 0) |
 		       (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ?
@@ -4398,7 +4618,7 @@
 
 	/* MLD MAC address must be the same */
 	if (!kde.mac_addr ||
-	    os_memcmp(kde.mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(kde.mac_addr, sm->peer_mld_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Mismatching or missing MLD address in EAPOL-Key msg 4/4");
 		return -1;
@@ -4732,7 +4952,8 @@
 				return;
 
 			kde = pos = kde_buf;
-			wpa_auth_ml_group_kdes(sm, pos);
+			pos = wpa_auth_ml_group_kdes(sm, pos);
+			kde_len = pos - kde_buf;
 		}
 #endif /* CONFIG_IEEE80211BE */
 	} else {
@@ -4912,19 +5133,30 @@
 				group->IGTK[group->GN_igtk - 4], len);
 	}
 
-	if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION &&
-	    conf->beacon_prot) {
-		len = wpa_cipher_key_len(conf->group_mgmt_cipher);
-		os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN);
-		inc_byte_array(group->Counter, WPA_NONCE_LEN);
-		if (wpa_gmk_to_gtk(group->GMK, "BIGTK key expansion",
-				   wpa_auth->addr, group->GNonce,
-				   group->BIGTK[group->GN_bigtk - 6], len) < 0)
-			ret = -1;
-		wpa_hexdump_key(MSG_DEBUG, "BIGTK",
-				group->BIGTK[group->GN_bigtk - 6], len);
+	if (!wpa_auth->non_tx_beacon_prot &&
+	    conf->ieee80211w == NO_MGMT_FRAME_PROTECTION)
+		return ret;
+	if (!conf->beacon_prot)
+		return ret;
+
+	if (wpa_auth->conf.tx_bss_auth) {
+		group = wpa_auth->conf.tx_bss_auth->group;
+		if (group->bigtk_set)
+			return ret;
+		wpa_printf(MSG_DEBUG, "Set up BIGTK for TX BSS");
 	}
 
+	len = wpa_cipher_key_len(conf->group_mgmt_cipher);
+	os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN);
+	inc_byte_array(group->Counter, WPA_NONCE_LEN);
+	if (wpa_gmk_to_gtk(group->GMK, "BIGTK key expansion",
+			   wpa_auth->addr, group->GNonce,
+			   group->BIGTK[group->GN_bigtk - 6], len) < 0)
+		return -1;
+	group->bigtk_set = true;
+	wpa_hexdump_key(MSG_DEBUG, "BIGTK",
+			group->BIGTK[group->GN_bigtk - 6], len);
+
 	return ret;
 }
 
@@ -5085,9 +5317,10 @@
 
 int wpa_wnmsleep_bigtk_subelem(struct wpa_state_machine *sm, u8 *pos)
 {
-	struct wpa_group *gsm = sm->group;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_group *gsm = wpa_auth->group;
 	u8 *start = pos;
-	size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	size_t len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 
 	/*
 	 * BIGTK subelement:
@@ -5097,7 +5330,7 @@
 	*pos++ = 2 + 6 + len;
 	WPA_PUT_LE16(pos, gsm->GN_bigtk);
 	pos += 2;
-	if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos) != 0)
+	if (wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos) != 0)
 		return 0;
 	pos += 6;
 
@@ -5187,12 +5420,21 @@
 				     KEY_FLAG_GROUP_TX_DEFAULT) < 0)
 			ret = -1;
 
-		if (ret == 0 && conf->beacon_prot &&
-		    wpa_auth_set_key(wpa_auth, group->vlan_id, alg,
+		if (ret || !conf->beacon_prot)
+			return ret;
+		if (wpa_auth->conf.tx_bss_auth) {
+			wpa_auth = wpa_auth->conf.tx_bss_auth;
+			group = wpa_auth->group;
+			if (!group->bigtk_set || group->bigtk_configured)
+				return ret;
+		}
+		if (wpa_auth_set_key(wpa_auth, group->vlan_id, alg,
 				     broadcast_ether_addr, group->GN_bigtk,
 				     group->BIGTK[group->GN_bigtk - 6], len,
 				     KEY_FLAG_GROUP_TX_DEFAULT) < 0)
 			ret = -1;
+		else
+			group->bigtk_configured = true;
 	}
 
 	return ret;
@@ -5337,9 +5579,11 @@
 		tmp = group->GM_igtk;
 		group->GM_igtk = group->GN_igtk;
 		group->GN_igtk = tmp;
-		tmp = group->GM_bigtk;
-		group->GM_bigtk = group->GN_bigtk;
-		group->GN_bigtk = tmp;
+		if (!wpa_auth->conf.tx_bss_auth) {
+			tmp = group->GM_bigtk;
+			group->GM_bigtk = group->GN_bigtk;
+			group->GN_bigtk = tmp;
+		}
 		wpa_gtk_update(wpa_auth, group);
 		wpa_group_config_group_keys(wpa_auth, group);
 	}
@@ -5689,28 +5933,11 @@
 
 int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr,
 			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
-			int session_timeout, int akmp)
-{
-	if (!wpa_auth || wpa_auth->conf.disable_pmksa_caching)
-		return -1;
-
-	wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK (2)", pmk, PMK_LEN);
-	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, pmk_len, pmkid,
-				 NULL, 0, wpa_auth->addr, addr, session_timeout,
-				 NULL, akmp))
-		return 0;
-
-	return -1;
-}
-
-
-int wpa_auth_pmksa_add3(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 			int session_timeout, int akmp, const u8 *dpp_pkhash)
 {
 	struct rsn_pmksa_cache_entry *entry;
 
-	if (wpa_auth->conf.disable_pmksa_caching)
+	if (!wpa_auth || wpa_auth->conf.disable_pmksa_caching)
 		return -1;
 
 	wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK (3)", pmk, PMK_LEN);
@@ -5834,13 +6061,14 @@
 void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa,
 			      struct wpa_state_machine *sm,
 			      struct wpa_authenticator *wpa_auth,
-			      u8 *pmkid, u8 *pmk)
+			      u8 *pmkid, u8 *pmk, size_t *pmk_len)
 {
 	if (!sm)
 		return;
 
 	sm->pmksa = pmksa;
-	os_memcpy(pmk, pmksa->pmk, PMK_LEN);
+	os_memcpy(pmk, pmksa->pmk, pmksa->pmk_len);
+	*pmk_len = pmksa->pmk_len;
 	os_memcpy(pmkid, pmksa->pmkid, PMKID_LEN);
 	os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmksa->pmkid, PMKID_LEN);
 }
@@ -6358,7 +6586,7 @@
 		size_t elen;
 
 		elen = pos - kde;
-		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name);
+		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true);
 		if (res < 0) {
 			wpa_printf(MSG_ERROR,
 				   "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data");
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 57fda8a..4f6bb76 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -244,6 +244,9 @@
 	unsigned int skip_send_eapol:1;
 	unsigned int enable_eapol_large_timeout:1;
 	bool delay_eapol_tx;
+	struct wpabuf *eapol_m1_elements;
+	struct wpabuf *eapol_m3_elements;
+	bool eapol_m3_no_encrypt;
 #endif /* CONFIG_TESTING_OPTIONS */
 	unsigned int oci_freq_override_eapol_m3;
 	unsigned int oci_freq_override_eapol_g1;
@@ -279,6 +282,11 @@
 	bool force_kdk_derivation;
 
 	bool radius_psk;
+
+	/* Pointer to Multi-BSSID transmitted BSS authenticator instance.
+	 * Set only in nontransmitted BSSs, i.e., is NULL for transmitted BSS
+	 * and in BSSs that are not part of a Multi-BSSID set. */
+	struct wpa_authenticator *tx_bss_auth;
 };
 
 typedef enum {
@@ -479,9 +487,6 @@
 void wpa_auth_add_sae_pmkid(struct wpa_state_machine *sm, const u8 *pmkid);
 int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr,
 			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
-			int session_timeout, int akmp);
-int wpa_auth_pmksa_add3(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 			int session_timeout, int akmp, const u8 *dpp_pkhash);
 void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth,
 			   const u8 *sta_addr);
@@ -507,7 +512,7 @@
 void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa,
 			      struct wpa_state_machine *sm,
 			      struct wpa_authenticator *wpa_auth,
-			      u8 *pmkid, u8 *pmk);
+			      u8 *pmkid, u8 *pmk, size_t *pmk_len);
 int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id);
 void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth,
 				  struct wpa_state_machine *sm, int ack);
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 4b16f62..7744ed6 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -1427,7 +1427,7 @@
 
 	os_get_reltime(&now);
 	dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
-		if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(r0->spa, spa) &&
 		    os_memcmp_const(r0->pmk_r0_name, pmk_r0_name,
 				    WPA_PMK_NAME_LEN) == 0) {
 			*r0_out = r0;
@@ -1522,7 +1522,7 @@
 	os_get_reltime(&now);
 
 	dl_list_for_each(r1, &cache->pmk_r1, struct wpa_ft_pmk_r1_sa, list) {
-		if (os_memcmp(r1->spa, spa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(r1->spa, spa) &&
 		    os_memcmp_const(r1->pmk_r1_name, pmk_r1_name,
 				    WPA_PMK_NAME_LEN) == 0) {
 			os_memcpy(pmk_r1, r1->pmk_r1, r1->pmk_r1_len);
@@ -2024,7 +2024,7 @@
 			    sm->r0kh_id, sm->r0kh_id_len);
 		return -1;
 	}
-	if (os_memcmp(r0kh->addr, sm->wpa_auth->addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(r0kh->addr, sm->wpa_auth->addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: R0KH-ID points to self - no matching key available");
 		return -1;
@@ -2366,7 +2366,8 @@
 static u8 * wpa_ft_bigtk_subelem(struct wpa_state_machine *sm, size_t *len)
 {
 	u8 *subelem, *pos;
-	struct wpa_group *gsm = sm->group;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_group *gsm = wpa_auth->group;
 	size_t subelem_len;
 	const u8 *kek, *bigtk;
 	size_t kek_len;
@@ -2381,7 +2382,7 @@
 		kek_len = sm->PTK.kek_len;
 	}
 
-	bigtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	bigtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 
 	/* Sub-elem ID[1] | Length[1] | KeyID[2] | BIPN[6] | Key Length[1] |
 	 * Key[16+8] */
@@ -2395,7 +2396,7 @@
 	*pos++ = subelem_len - 2;
 	WPA_PUT_LE16(pos, gsm->GN_bigtk);
 	pos += 2;
-	wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos);
+	wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos);
 	pos += 6;
 	*pos++ = bigtk_len;
 	bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
@@ -3765,7 +3766,7 @@
 		   " Target AP=" MACSTR " Action=%d)",
 		   MAC2STR(sta_addr), MAC2STR(target_ap), action);
 
-	if (os_memcmp(sta_addr, sm->addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sta_addr, sm->addr)) {
 		wpa_printf(MSG_DEBUG, "FT: Mismatch in FT Action STA address: "
 			   "STA=" MACSTR " STA-Address=" MACSTR,
 			   MAC2STR(sm->addr), MAC2STR(sta_addr));
@@ -3778,7 +3779,7 @@
 	 * APs in the MD (if such a list were configured).
 	 */
 	if ((target_ap[0] & 0x01) ||
-	    os_memcmp(target_ap, sm->wpa_auth->addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(target_ap, sm->wpa_auth->addr)) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid Target AP in FT Action "
 			   "frame");
 		return -1;
@@ -4036,7 +4037,7 @@
 		seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len,
 					     auth, auth_len, msgtype, no_defer);
 	if (!no_defer && r1kh_wildcard &&
-	    (!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
+	    (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) {
 		/* wildcard: r1kh-id unknown or changed addr -> do a seq req */
 		seq_ret = FT_RRB_SEQ_DEFER;
 	}
@@ -4203,7 +4204,7 @@
 					     cb ? 0 : 1);
 	}
 	if (cb && r0kh_wildcard &&
-	    (!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
+	    (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) {
 		/* wildcard: r0kh-id unknown or changed addr -> do a seq req */
 		seq_ret = FT_RRB_SEQ_DEFER;
 	}
@@ -4357,7 +4358,7 @@
 	struct ft_get_sta_ctx *info = ctx;
 
 	if ((info->s1kh_id &&
-	     os_memcmp(info->s1kh_id, sm->addr, ETH_ALEN) != 0) ||
+	     !ether_addr_equal(info->s1kh_id, sm->addr)) ||
 	    os_memcmp(info->nonce, sm->ft_pending_pull_nonce,
 		      FT_RRB_NONCE_LEN) != 0 ||
 	    sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL)
@@ -4482,7 +4483,7 @@
 		wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
 				       &r0kh, &r0kh_wildcard);
 		if (!r0kh_wildcard &&
-		    (!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
+		    (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
 				    f_r0kh_id, f_r0kh_id_len);
 			goto out;
@@ -4500,7 +4501,7 @@
 		wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh,
 				       &r1kh_wildcard);
 		if (!r1kh_wildcard &&
-		    (!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
+		    (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID",
 				    f_r1kh_id, FT_R1KH_ID_LEN);
 			goto out;
@@ -4806,7 +4807,7 @@
 			return -1;
 		}
 
-		if (os_memcmp(target_ap_addr, wpa_auth->addr, ETH_ALEN) != 0) {
+		if (!ether_addr_equal(target_ap_addr, wpa_auth->addr)) {
 			wpa_printf(MSG_DEBUG, "FT: Target AP address in the "
 				   "RRB Request does not match with own "
 				   "address");
@@ -4969,7 +4970,7 @@
 		return;
 
 	dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
-		if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(r0->spa, addr)) {
 			r0found = r0;
 			break;
 		}
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 82d79f2..b286a77 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -183,8 +183,15 @@
 	wconf->oci_freq_override_ft_assoc = conf->oci_freq_override_ft_assoc;
 	wconf->oci_freq_override_fils_assoc =
 		conf->oci_freq_override_fils_assoc;
+
 	wconf->skip_send_eapol = iconf->skip_send_eapol;
 	wconf->enable_eapol_large_timeout = iconf->enable_eapol_large_timeout;
+
+	if (conf->eapol_m1_elements)
+		wconf->eapol_m1_elements = wpabuf_dup(conf->eapol_m1_elements);
+	if (conf->eapol_m3_elements)
+		wconf->eapol_m3_elements = wpabuf_dup(conf->eapol_m3_elements);
+	wconf->eapol_m3_no_encrypt = conf->eapol_m3_no_encrypt;
 #endif /* CONFIG_TESTING_OPTIONS */
 #ifdef CONFIG_P2P
 	os_memcpy(wconf->ip_addr_go, conf->ip_addr_go, 4);
@@ -556,7 +563,8 @@
 	if (sta) {
 		flags = hostapd_sta_flags_to_drv(sta->flags);
 #ifdef CONFIG_IEEE80211BE
-		if (sta->mld_info.mld_sta && (sta->flags & WLAN_STA_AUTHORIZED))
+		if (ap_sta_is_mld(hapd, sta) &&
+		    (sta->flags & WLAN_STA_AUTHORIZED))
 			link_id = -1;
 #endif /* CONFIG_IEEE80211BE */
 	}
@@ -669,7 +677,7 @@
 		hapd = iface->bss[j];
 		if (hapd == idata->src_hapd ||
 		    !hapd->wpa_auth ||
-		    os_memcmp(hapd->own_addr, idata->dst, ETH_ALEN) != 0)
+		    !ether_addr_equal(hapd->own_addr, idata->dst))
 			continue;
 
 		wpa_printf(MSG_DEBUG,
@@ -859,7 +867,7 @@
 			      MOBILITY_DOMAIN_ID_LEN) != 0)
 			continue; /* no matching FT SSID/mobility domain */
 		if (!is_multicast_ether_addr(idata->dst_addr) &&
-		    os_memcmp(hapd->own_addr, idata->dst_addr, ETH_ALEN) != 0)
+		    !ether_addr_equal(hapd->own_addr, idata->dst_addr))
 			continue; /* destination address does not match */
 
 		/* defer eth_p_oui_deliver until next eloop step as this is
@@ -1157,17 +1165,25 @@
 	if (!sta || !sta->wpa_sm)
 		return -1;
 
-	if (vlan->notempty &&
-	    !hostapd_vlan_valid(hapd->conf->vlan, vlan)) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_INFO,
-			       "Invalid VLAN %d%s received from FT",
-			       vlan->untagged, vlan->tagged[0] ? "+" : "");
-		return -1;
-	}
+	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) {
+		if (vlan->notempty &&
+		    !hostapd_vlan_valid(hapd->conf->vlan, vlan)) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO,
+				       "Invalid VLAN %d%s received from FT",
+				       vlan->untagged, vlan->tagged[0] ?
+				       "+" : "");
+			return -1;
+		}
 
-	if (ap_sta_set_vlan(hapd, sta, vlan) < 0)
-		return -1;
+		if (ap_sta_set_vlan(hapd, sta, vlan) < 0)
+			return -1;
+
+	} else {
+		if (vlan->notempty)
+			sta->vlan_id = vlan->untagged;
+	}
 	/* Configure wpa_group for GTK but ignore error due to driver not
 	 * knowing this STA. */
 	ap_sta_bind_vlan(hapd, sta);
@@ -1190,10 +1206,15 @@
 	if (!sta)
 		return -1;
 
-	if (sta->vlan_desc)
+	if (sta->vlan_desc) {
 		*vlan = *sta->vlan_desc;
-	else
+	} else if ((hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD) &&
+		   sta->vlan_id) {
+		vlan->notempty = 1;
+		vlan->untagged = sta->vlan_id;
+	} else {
 		os_memset(vlan, 0, sizeof(*vlan));
+	}
 
 	return 0;
 }
@@ -1396,7 +1417,7 @@
 	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
 		   MACSTR, MAC2STR(ethhdr->h_source), MAC2STR(ethhdr->h_dest));
 	if (!is_multicast_ether_addr(ethhdr->h_dest) &&
-	    os_memcmp(hapd->own_addr, ethhdr->h_dest, ETH_ALEN) != 0)
+	    !ether_addr_equal(hapd->own_addr, ethhdr->h_dest))
 		return;
 	wpa_ft_rrb_rx(hapd->wpa_auth, ethhdr->h_source, buf + sizeof(*ethhdr),
 		      len - sizeof(*ethhdr));
@@ -1412,7 +1433,7 @@
 	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
 		   MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr));
 	if (!is_multicast_ether_addr(dst_addr) &&
-	    os_memcmp(hapd->own_addr, dst_addr, ETH_ALEN) != 0)
+	    !ether_addr_equal(hapd->own_addr, dst_addr))
 		return;
 	wpa_ft_rrb_oui_rx(hapd->wpa_auth, src_addr, dst_addr, oui_suffix, buf,
 			  len);
@@ -1540,7 +1561,8 @@
 
 			if (!iface->bss[0]->conf->mld_ap ||
 			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
-			    link_id != iface->bss[0]->mld_link_id)
+			    link_id != iface->bss[0]->mld_link_id ||
+			    !iface->bss[0]->wpa_auth)
 				continue;
 
 			wpa_auth_ml_get_rsn_info(iface->bss[0]->wpa_auth,
@@ -1582,7 +1604,8 @@
 
 			if (!iface->bss[0]->conf->mld_ap ||
 			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
-			    link_id != iface->bss[0]->mld_link_id)
+			    link_id != iface->bss[0]->mld_link_id ||
+			    !iface->bss[0]->wpa_auth)
 				continue;
 
 			wpa_auth_ml_get_key_info(iface->bss[0]->wpa_auth,
@@ -1675,9 +1698,13 @@
 	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
+	struct hostapd_data *tx_bss;
 
 	hostapd_wpa_auth_conf(hapd->conf, hapd->iconf, &_conf);
 	_conf.msg_ctx = hapd->msg_ctx;
+	tx_bss = hostapd_mbssid_get_tx_bss(hapd);
+	if (tx_bss != hapd)
+		_conf.tx_bss_auth = tx_bss->wpa_auth;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EAPOL_TX_STATUS)
 		_conf.tx_status = 1;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_MLME)
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 74ae5ad..9ba8304 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -134,8 +134,6 @@
 					   * Request */
 	u8 r0kh_id[FT_R0KH_ID_MAX_LEN]; /* R0KH-ID from FT Auth Request */
 	size_t r0kh_id_len;
-	u8 sup_pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name from EAPOL-Key
-					       * message 2/4 */
 	u8 *assoc_resp_ftie;
 
 	void (*ft_pending_cb)(void *ctx, const u8 *dst, const u8 *bssid,
@@ -222,6 +220,8 @@
 	u8 BIGTK[2][WPA_IGTK_MAX_LEN];
 	int GN_igtk, GM_igtk;
 	int GN_bigtk, GM_bigtk;
+	bool bigtk_set;
+	bool bigtk_configured;
 	/* Number of references except those in struct wpa_group->next */
 	unsigned int references;
 	unsigned int num_setup_iface;
@@ -257,6 +257,8 @@
 	struct rsn_pmksa_cache *pmksa;
 	struct wpa_ft_pmk_cache *ft_pmk_cache;
 
+	bool non_tx_beacon_prot;
+
 #ifdef CONFIG_P2P
 	struct bitfield *ip_pool;
 #endif /* CONFIG_P2P */
diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c
index aacfa33..82d4d5f 100644
--- a/src/ap/wps_hostapd.c
+++ b/src/ap/wps_hostapd.c
@@ -288,7 +288,7 @@
 			any_psk = wpa_psk->psk;
 
 		if (mac_addr && !dev_psk &&
-		    os_memcmp(mac_addr, wpa_psk->addr, ETH_ALEN) == 0) {
+		    ether_addr_equal(mac_addr, wpa_psk->addr)) {
 			dev_psk = wpa_psk->psk;
 			break;
 		}