[wpa_supplicant] cumilative patch from commit 30748d2b3

Bug: 312315629
Test: Connect to open, WPA2, WPA3 and OWE
Test: Establish P2P connection
Test: Basic SoftAp tests
Test: Ran above tests on both Pixel 7a and Pixel6
Test: Regression test (312319701)

BYPASS_INCLUSIVE_LANGUAGE_REASON=Merged from open source
30748d2b3 SAE: Require PMKID match to PMKSA with SAE-EXT-KEY
nl80211: Use attribute NL80211_ATTR_BSSID to scan for specific BSSID
9b89df758 WNM: Do not start scan on disassociation imminent if BSSID is set
b08980309 hostapd: Add support for SAE offload for AP interface
d984c7b29 hostapd: Add support for OWE offload for STA/AP interface
da364180f hostapd: Support 4-way handshake offload for AP/P2P GO
77386f51a Adjust the RSSI and throughput estimate in roaming algorithm
790beb84a Adjust the SNR when comparing BSSes based on Tx power config
93a68a1fc OWE: Remove now unnecessary attempt to update transition mode BSS
00b312587 OWE: Do not update the BSS entry with zero length SSID for transition
9c9712657 OWE: Optimize transition mode scan to use known channels
5b12a0559 Use SSID from driver when finding the current BSS entry
a3020f852 MLD: Use BSS Parameters in TBTT Info to check SSID match
0635f83e4 MLD: Support multiple TBTT Information fields in RNR elements
c18aef624 MLD: Move TBTT Information field parsing into a helper function
99a8dd049 MLD: Support multiple RNR elements
be212bdb5 MLD: Move RNR element parsing into a helper function
84c33cc81 MBSSID: Use DTIM Count 0 in the Beacon template for nontransmitted BSSID
3e1fb2dec dragonfly: Fix legendre symbol calculation failure handling
76ae985b0 Remove QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID constraint
cc1867f5c MLD STA: Use MLD MAC address as destination for EAPOL-Key request
c92311fed MLD STA: Fix destination address for Group Key handshake msg 2/2
4f20dd52f wpa_cli/hostapd_cli: Add driver_flags2 command
c0da381a3 nl80211: Add capa.flags2 to STATUS-DRIVER
d193726aa nl80211: Dump driver_flags2 in debug prints
ed1ae82a3 Update the driver_flags2 to string conversion
bbc7ffe85 Rename driver capability for radar background detection
5025047ac Fix use after free warning introduced by gcc 12.1
236c0cfbc SAE: Pass SAE password on connect for SAE authentication offload support
6cc78b394 nl80211: Set NL80211_WPA_VERSION_2 vs. _3 based on AKM
c3b8452e0 nl80211: SAE authentication offload support
750403f3a mka: Fix re-establishment by resetting MI
61f0e19b8 mka: Fix unexpected cleanup on missing MKA_LIFE_TIME while installing SC/SA
c84388ee4 Compile-time config for dynamically loading libraries in wpa_supplicant
890953a32 wolfSSL: Old FIPS APIs have void return
ec7f064fa wolfSSL: Implement DPP backend functions
b37238d3a wolfSSL: Set up generator manually in FIPS build
8dabc1fed wolfSSL: Get EC generator for DPP
732ed5abe wolfSSL: Add crypto_ecdh_init2()
15a7c9b9e wolfSSL: Refactor crypto ECC section
41b5c9d8d wolfSSL: Use wc_ecc_get_curve_size_from_id()
378bef369 wolfSSL: Use wc_ecc_forcezero_point() in non-FIPS builds
de38571b8 wolfSSL: More complete crypto_ec_key_group()
d48f6b913 wolfSSL: EC group-to-id conversion into a helper function
a16916b74 wolfSSL: Improve logging
7ebb5469b wolfSSL: Improve error checking and logging in AES functions
10fd91d8f wolfSSL: Better error message in pbkdf2_sha1() for FIPS password failure
aa4c4d079 wolfSSL: Always clean up resources and log errors in wolfssl_hmac_vector()
644d87c34 wolfSSL: Improve error checking in vector hashing functions
5e20b924d wolfSSL: Add crypto logging macros
a0e8d9ae7 wolfSSL: Add FIPS warning
48a65d47c wolfSSL: Put wolfSSL headers in alphabetical order
a2eeb7f6d wolfSSL: Add more precise logging in wolfssl_handshake()
83f144bf6 wolfSSL: Debug print ciphersuites
568a5a815 EHT: Include crypto.h to avoid implicit function definition
0776c51ed DPP: Handle wpas_dpp_connected() processing in eloop callback
5c5f86900 DPP: Start next auth init from driver event to avoid race condition
f9965a650 Use os_reltime_initialized() for Michael MIC failure event
a8517c132 Add support for AKM suite 00-0F-AC:23
005b0ce36 defs: Enclose all structs between the pragmas
41a60f658 hostapd: Add support to send CW change notification
544801d74 wpa_supplicant: Add channel 140 to ht40plus allowed list for mesh/IBSS
75d33c988 OWE: Fix for entry->ssid possibly NULL dereference
e97d7c5a6 Only advertise MSCS and SCS in Association Request if supported by AP
a5d0bb42a Reduce delay between Association Request and Association Response
3f2c41e31 Check max number of TBTT info when adding Neighbor AP Information field
fc0b0cdcb hostapd: Avoid unnecessary Beacon frame update for co-location
8056b79ff Add DSSS Parameter Set element only for 2.4 GHz
056e68829 common: Fix ieee802_11_rsnx_capab()
ab3e679ae MBSSID: Check xrates_supported for all BSSs explicitly
4bfc007b6 MBSSID: Fix Non-Inheritance element encoding
42add3c27 Scan 6 GHz channels after change to 6 GHz-allowed regdom
0b8a67225 Parse 6 GHz capability from driver capabilities
41baf0159 nl80211: Fix uses_6ghz flag
17bdf34c4 Use default IEs in wpa_supplicant_trigger_scan()
aac288914 OKC with Suite B AKMPs in hostapd
0c9df339f OKC with Suite B AKMPs in wpa_supplicant
2bd8887e9 P2P: Pass the known BSSID to the driver to optimize scan time
bffd2b399 nl80211: Skip interface down/up when setting MAC address
9e426e068 Enable IPv6 in wpa_supplicant and eapol_test builds
3d8de6191 dbus: Use proper dbus_bool_t value TRUE instead of 1
03a9a57ac dbus: Add NonColoc6GHz and 6GHzOnly flags in wpa_supplicant scan
e5ea30fee SME: MLD: Handle reconfiguration Multi-Link element
7ea2798c2 Test command for sending ML probe request
de5e01010 wpa_supplicant: Support ML probe request
a12f39ad4 nl80211: Add support for minimal probe request content
c84709c59 hostapd: Output BSS Color (he_bss_color) when using STATUS
11a6ae242 More consistent use of mesh peer connected/disconnected notification
bd37f8615 Fix MESH-PEER-DISCONNECTED message logic on control iface
d986e8702 Respect disable_ht40/disable_vht/disable_he in AP/mesh mode
f7f8ea0aa nl80211: Change QoS Map configuration to be per bss, not radio
67bf89f55 WNM: Choose the best available BSS, not just the first one
fc7e74496 Sync with wireless-next.git include/uapi/linux/nl80211.h
5a96a516a dbus: Report guard interval and dual carrier modulation
3cb51378f Abort ongoing scan on DISCONNECT
5b21f4861 l2_packet_freebsd: Enable receiving priority tagged (VID=0) frames
0aa44ccf8 WNM: Lower rankings of current AP if disassociation imminent bit set
3242793cb P2P: Remove pending p2p-listen radio work on stopping listen
18330d1f6 hostapd: Update op_class after AP channel switching
2563edb8c Use 6 GHz default noise when estimating 6 GHz SNR
7a7339932 ACS: Fix typo in bw_40 frequency array
b99bb32f5 Don't disconnect on scan_freq update from control interface
cc5a00800 Ensure WDS is available on combined backhaul and fronthaul APs
1aeeebaa6 defconfig: Remove remaining reference to IEEE80211W symbol
8477fa7eb Check the need for SA Query earlier in association processing
a6440b57c Update correct VHT/HE/EHT mode in channel switch event
c86064716 Add NULL check for pmksa cache free_cb() callback
2f911fb15 SAE: Remove current PMKSA from driver after reauth threshold is passed
2d4be0019 Double the first group rekey timeout if over 100 associated stations
a89cf6ba4 Reserve QCA vendor sub command id 234
1dfcafff3 FILS: EHT additions
26f29ef46 FILS: Fix NSS calculation for HE mode
fcbb643ff FILS: Rename local variable to indicate HE mode
dcf66d2f4 FILS: Move maximum NSS determination to a new function
24e0938b3 FILS: Move phy index determination to new function
015af1bee DPP: Use CONFIG_SAE consistently to avoid a compiler warning
55ea12bb7 AP MLD: Add missing CONFIG_SAE checks
ef8d48c4c Update Wide Bandwidth Channel Switch element
c4c5c991d SAE: Do not reject reauth threshold passed PMKSA in association event
7a9587cee PASN: Copy PMK to PASN context on responder
e59d2a31c hostapd: Fix premature beacon set during association handling
ae928e67a Add channel 144 (5720 MHz) into operating class conversion tables
c80ded25c Refine roam stats frame subtypes in a QCA vendor attribute
ed89ab429 Update roam stats of AP BSSID to user space in a QCA vendor attribute
881cb4198 EAP-SIM/AKA peer: Simplify identity selection for MK derivation
ec6acdbb6 EAP-SIM/AKA server: Configurable limit to fast re-authentication
c6268e103 EAP-SIM/AKA server: Allow method specific identity exchange to be skipped
40af6560b EAP-SIM/AKA peer: Fix identity selection for MK derivation with AT_IDENTITY
bc9256980 Define a QCA vendor attribute to set traffic shaping policy
dec5ab645 Add _IS_ML flag attribute to the ADD_STA_NODE QCA vendor command
0d65e27fb Extend maximum allowed bandwidth update type QCA vendor interface
e510a3bad Add QCA vendor attributes to indicate MLO capabilities
e5ccbfc69 Split long comment lines in QCA vendor related definitions
4c9af238c Fix inconsistent whitespace use in QCA vendor related definitions
af6e0306b Fix typos in QCA vendor related definitions
dd25885a9 Remove space-before-tab in QCA vendor related definitions
f42906418 TDLS: Set EHT/MLO information for TDLS STA into the driver
940ef9a05 TDLS: Use link-specific BSSID instead of sm->bssid for MLO cases
5f30f62ee TDLS: Reply to Discovery Request on the link with matching BSSID
626501434 TDLS: Learn MLD link ID from TDLS Discovery Response
a41c8dbdd TDLS: Copy peer's EHT capabilities
c7561502f nl80211: Use a QCA vendor command to set the link for TDLS Discovery Response
e3a68081b driver: Add option for link ID to be specified for send_tdls_mgmt()
f85b2b2de Extend wpa_parse_kde_ies() to include EHT capabilities
3e7151693 Document per-ESS MAC address (mac_addr=3 and mac_value)
ba1579f3b Clear BIGTK values from wpa_supplicant state machine when not needed
377d617b5 Define new BSS command info mask for AP MLD address
f6eaa7b72 Add QCA vendor attribute for TTLM negotiation support type
12fabc476 Add QCA vendor attribute for configuring max A-MPDU aggregation count
b3d852560 Change QCA vendor configure attribution name of peer MAC address
123d16d86 Update hw_mode when CSA finishes
32dcec952 Send actual MFP configuration when driver takes care of BSS selection
edfca280c SCS: Add support for optional QoS Charateristics parameters
33da38655 SCS: Add support for QoS Characteristics in SCS request
c43766504 Add Non EHT SCS Capability in (Re)Association Request frames
12154861e Add support for conversion to little endian for 24 bits
609864d6a Add QCA vendor attribute to configure MLD ID in ML probe request
78b153f90 Calculate defragmented FTE length during IE parsing
aa08d9d76 Fix use of defragmented FTE information
ac9bf1cc2 Decrement hmac_sha*_vector() maximum num_elem value to 11
7381c60db FT: Make FTE MIC calculation more flexible
e6f64a8e1 FT: FTE MIC calculation for MLO Reassociation Request frame
4c079dcc6 Increment hmac_sha*_vector() maximum num_elem value to 25
338a78846 Add a QCA vendor sub command for transmit latency statistics
1085e3bdc Update iface->current_mode when fetching new hw_features
0a6842d50 nl80211: Fix beacon rate configuration for legacy rates 36, 48, 54 Mbps
dd1330b50 Fix hostapd interface cleanup with multiple interfaces
7637d0f25 P2P: Do not filter pref_freq_list if the driver does not provide one
47a65ccbf P2P: Clean wpa_s->last_ssid when removing a temporary group network
fe72afe71 Define QCA vendor attribute for high RSSI roam trigger threshold
e080930aa Define QCA vendor roam control RSSI attributes
585637355 Extend QCA vendor command to include more parameters for netdev events
6f293b321 QCA vendor attributes for updating roaming AP BSSID info
7e1f5c44c EHT: 320 MHz DFS support
a94ba5322 EHT: Support puncturing for 320 MHz channel bandwidth
bd209633e AP: Use is_zero_ether_addr() to check if BSSID is NULL
763a19286 AP: Add configuration option to specify the desired MLD address
2763d1d97 hostapd: Fix AID assignment in multiple BSSID
bc0636841 wpa_supplicant: Fix configuration parsing error for tx_queue_*
a685d8413 BSS coloring: Fix CCA with multiple BSS
b7db495ad AP: Fix ieee802_1x_ml_set_sta_authorized()
8f148d513 Fix a compiler warning on prototype mismatch
bf9cbb462 Fix writing of BIGTK in FT protocol
084745ffc Add QCA vendor attributes for NDP setup
3973300b8 FTE protected element check for MLO Reassociation Response frame
43b5f11d9 Defragmentation of FTE
053bd8af8 Recognize FTE MLO subelements
d320692d9 AP MLD: Handle new STA event when using SME offload to the driver
96deacf5d nl80211: Skip STA MLO link channel switch handling in AP mode
99a96b2f9 AP MLD: OWE when SME is offloaded to the driver
e53d44ac6 AP MLD: Use STA assoc link address in external auth status to the driver
4636476b7 Set RRM used config if the (Re)Association Request frame has RRM IE
a50d1ea6a Add QCA vendor attributes for user defined power save parameters
50ee26fc7 P2P: Check p2p_channel_select() return value
fb2b7858a FILS: Fix HE MCS field initialization
f80d83368 ACS: Remove invalid debug print
7a37a94ea Check whether element parsing has failed
a4c133ea7 WPS: Optimize attribute parsing workaround
518ae8c7c P2P: Do not print control characters in debug
de9a11f4d TTLS client: Support phase2_auth=2
8e6485a1b PEAP client: Update Phase 2 authentication requirements
30f5bdc34 Add support to configure per-MLO link maximum supported channel width
3d1ec9d0a Add QCA vendor interface to support per-MLO link configurations
f83cc05aa Reserve QCA vendor sub command id 232
82db29c37 QCA vendor test config attribute for MLO link powersave
f37a7dec3 Add vendor attributes for EPCS feature
a8c66bbb7 QCA vendor interface to control maximum allowed bandwidth update type
19e880d1f Add support to get the TDLS wider bandwidth capability
a8a112d4d Add documentation and nested attribute enums for existing QCA TDLS commands
91783b21b Define a QCA vendor attribute to configure UL MU transmission
05a2f4c4f EHT: Process puncturing bitmap from channel select driver event for ACS
9b233e9f0 nl80211: Always return NL_SKIP from survey dump handler
3a995cb5a Determine current hw mode before channel switch
a786c9b4a Enhance QCA_WLAN_VENDOR_ATTR_CONFIG_EHT_MLO_MAX_NUM_LINKS
aa4b8492e AP MLD: Provide Link ID when requesting current seqnum for a group key
5199cff4c AP/MLO: Forward received EAPOL frames to correct BSS
5c6cad01f AP/MLO: Forward Management frame TX status to correct BSS
996759ccf AP/MLO: Forward EAPOL TX status to correct BSS
8b5653669 AP: Use MLD address for traffic tests
6046aef73 AP: Don't process SAE/OWE association info on MLD links
90d819c24 AP: Use MLD address for SAE commit derivation
8b49853f4 AP: Specify the link ID for set_key() callback for group keys
8a8752876 MLO: Get the correct AA and SPA based on MLD operation for RSN authenticator
d5e93c804 MLO: Add MLO KDEs to EAPOL-Key msg 1/2 of the group handshake
79212e93f MLO: Validate MLO KDEs in EAPOL-Key msg 4/4
856d99410 MLO: Add MLO KDEs to EAPOL-Key msg 3/4
137b85509 MLO: Mechanism for fetching group key information for the links
eb28ee20e MLO: Validate MLO Link KDEs in EAPOL-Key msg 2/4
151ac359d MLO: Add MAC Address KDE to EAPOL-Key msg 1/4 for MLO association
3102d7676 MLO: Store MLO link information in RSN Authentication
cb130bbcb AP: MLO: Forward link specific events to the identified link
3613c8a96 nl80211: Use frequency to determine MLD link for MLME events
d3e20b211 AP/driver: Add link id to the set_tx_queue_params() callback
fbbca2bf1 AP: Provide the link ID for an MLD setting when setting VLAN
172b0a9a2 AP/driver: Add link ID to send EAPOL callbacks
c5271faf5 AP: Print MLD info in STATUS command
d75ebe23d AP: Handle Management frame TX status for AP MLD address
7a9ae9f43 AP: Do not prune station when adding a link station
5a61644ff driver: Specify link ID for 'send_mlme' and 'sta_deauth' callbacks
64d9ba3e6 Use a shared function for setting port authorization changes
edacd72d9 AP: MLO: Handle IEEE 802.1X port authorization
565020534 AP: MLO: Handle deauthentication/disassociation of MLD station
ced69780c AP: Cleanup coding style for deauth/disassoc handling
62fcfe8d2 AP: Move deauthentication/disassociation steps into helper functions
55038680a AP: MLO: Handle association callback
408b2a562 AP: MLO: Add Multi-Link element to (Re)Association Response frame
5f5db9366 AP: MLO: Process Multi-Link element from (Re)Association Request frame
d924be3bd AP: AID allocation for MLD
11a607d12 AP: Fill MLO information in struct hostapd_sta_add_params
bcbe80a66 AP: MLO: Handle Multi-Link element during authentication
f540d078c AP: Support building Basic Multi-Link element
79a9df6e8 AP: Match received Management frames against MLD address
a213fee11 AP: MLO: Make IEEE 802.1X SM, authserv, and RADIUS client singletons
7b45c2e6b nl80211: Select frame TX frequency according to the transmitting link
2b541601d AP: Include an RNR element in Beacon frames for AP MLD
0c6c94804 nl80211: Support setting up an AP on a specified link
df3fe12c9 nl80211: Move nl80211_put_freq_params()
e3605e809 driver: Allow to provide a link ID when setting a channel
be44a7afd driver: Add MLD link id to AP parameters
7fa99b324 AP: Allow starting multiple interfaces within single MLD
f2dd75093 AP: Add some basic MLD configuration options
1b14b38b1 nl80211: Fetch EML/MLD capabilities
8dffa0ccb AP: MLO: Retrieve EML and MLD capabilities from driver
4697887df nl80211: Rename the per iface-type capabilities struct
0837863fb AP: Handle 6 GHz AP state machine with NO_IR flags

Change-Id: I35521fd34f5769ba2323d49cbc8000a5565b8ee1
Signed-off-by: Sunil Ravi <sunilravi@google.com>
diff --git a/src/ap/acs.c b/src/ap/acs.c
index 1181c7d..e3cfe1d 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -256,7 +256,7 @@
 static const struct bw_item bw_40[] = {
 	{ 5180, 5200, 38 }, { 5220, 5240, 46 }, { 5260, 5280, 54 },
 	{ 5300, 5320, 62 }, { 5500, 5520, 102 }, { 5540, 5560, 110 },
-	{ 5580, 5600, 110 }, { 5620, 5640, 126}, { 5660, 5680, 134 },
+	{ 5580, 5600, 118 }, { 5620, 5640, 126 }, { 5660, 5680, 134 },
 	{ 5700, 5720, 142 }, { 5745, 5765, 151 }, { 5785, 5805, 159 },
 	{ 5825, 5845, 167 }, { 5865, 5885, 175 },
 	{ 5955, 5975, 3 }, { 5995, 6015, 11 }, { 6035, 6055, 19 },
@@ -1076,12 +1076,6 @@
 		return ideal_chan;
 	}
 
-#ifdef CONFIG_IEEE80211BE
-	if (iface->conf->punct_acs_threshold)
-		wpa_printf(MSG_DEBUG, "ACS: RU puncturing bitmap 0x%x",
-			   ideal_chan->punct_bitmap);
-#endif /* CONFIG_IEEE80211BE */
-
 	return rand_chan;
 }
 
@@ -1353,12 +1347,20 @@
 
 enum hostapd_chan_status acs_init(struct hostapd_iface *iface)
 {
+	int err;
+
 	wpa_printf(MSG_INFO, "ACS: Automatic channel selection started, this may take a bit");
 
 	if (iface->drv_flags & WPA_DRIVER_FLAGS_ACS_OFFLOAD) {
 		wpa_printf(MSG_INFO, "ACS: Offloading to driver");
-		if (hostapd_drv_do_acs(iface->bss[0]))
+
+		err = hostapd_drv_do_acs(iface->bss[0]);
+		if (err) {
+			if (err == 1)
+				return HOSTAPD_CHAN_INVALID_NO_IR;
 			return HOSTAPD_CHAN_INVALID;
+		}
+
 		return HOSTAPD_CHAN_ACS;
 	}
 
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index c3ee506..60d3566 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -90,6 +90,7 @@
 	bss->radius_server_auth_port = 1812;
 	bss->eap_sim_db_timeout = 1;
 	bss->eap_sim_id = 3;
+	bss->eap_sim_aka_fast_reauth_limit = 1000;
 	bss->ap_max_inactivity = AP_MAX_INACTIVITY;
 	bss->eapol_version = EAPOL_VERSION;
 
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index def5fd5..99a6d18 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -448,6 +448,7 @@
 	int eap_sim_aka_result_ind;
 	int eap_sim_id;
 	char *imsi_privacy_key;
+	int eap_sim_aka_fast_reauth_limit;
 	int tnc;
 	int fragment_size;
 	u16 pwd_group;
@@ -937,6 +938,17 @@
 	u8 rnr;
 	char *config_id;
 	bool xrates_supported;
+
+#ifdef CONFIG_IEEE80211BE
+	/* The AP is part of an AP MLD */
+	u8 mld_ap;
+
+	/* The MLD ID to which the AP MLD is affiliated with */
+	u8 mld_id;
+
+	/* The AP's MLD MAC address within the AP MLD */
+	u8 mld_addr[ETH_ALEN];
+#endif /* CONFIG_IEEE80211BE */
 };
 
 /**
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index aa4dbe9..8f9cc5b 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -430,7 +430,7 @@
 		    size_t eht_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
-		    int set)
+		    int set, const u8 *link_addr, bool mld_link_sta)
 {
 	struct hostapd_sta_add_params params;
 
@@ -460,6 +460,19 @@
 	params.support_p2p_ps = supp_p2p_ps;
 	params.set = set;
 	params.mld_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * An AP MLD needs to always specify to what link the station needs
+	 * to be added.
+	 */
+	if (hapd->conf->mld_ap) {
+		params.mld_link_id = hapd->mld_link_id;
+		params.mld_link_addr = link_addr;
+		params.mld_link_sta = mld_link_sta;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->sta_add(hapd->drv_priv, &params);
 }
 
@@ -540,12 +553,12 @@
 
 
 int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd,
-		       const u8 *addr, int idx, u8 *seq)
+		       const u8 *addr, int idx, int link_id, u8 *seq)
 {
 	if (hapd->driver == NULL || hapd->driver->get_seqnum == NULL)
 		return 0;
 	return hapd->driver->get_seqnum(ifname, hapd->drv_priv, addr, idx,
-					seq);
+					link_id, seq);
 }
 
 
@@ -584,6 +597,17 @@
 		return 0;
 	if (hapd->driver->set_freq == NULL)
 		return 0;
+
+	data.link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		data.link_id = hapd->mld_link_id;
+		wpa_printf(MSG_DEBUG,
+			   "hostapd_set_freq: link_id=%d", data.link_id);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_freq(hapd->drv_priv, &data);
 }
 
@@ -635,10 +659,19 @@
 int hostapd_set_tx_queue_params(struct hostapd_data *hapd, int queue, int aifs,
 				int cw_min, int cw_max, int burst_time)
 {
+	int link_id = -1;
+
 	if (hapd->driver == NULL || hapd->driver->set_tx_queue_params == NULL)
 		return 0;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_tx_queue_params(hapd->drv_priv, queue, aifs,
-						 cw_min, cw_max, burst_time);
+						 cw_min, cw_max, burst_time,
+						 link_id);
 }
 
 
@@ -646,8 +679,8 @@
 hostapd_get_hw_feature_data(struct hostapd_data *hapd, u16 *num_modes,
 			    u16 *flags, u8 *dfs_domain)
 {
-	if (hapd->driver == NULL ||
-	    hapd->driver->get_hw_feature_data == NULL)
+	if (!hapd->driver || !hapd->driver->get_hw_feature_data ||
+	    !hapd->drv_priv)
 		return NULL;
 	return hapd->driver->get_hw_feature_data(hapd->drv_priv, num_modes,
 						 flags, dfs_domain);
@@ -727,6 +760,11 @@
 	params.key_flag = key_flag;
 	params.link_id = -1;
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && !(key_flag & KEY_FLAG_PAIRWISE))
+		params.link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	return hapd->driver->set_key(hapd->drv_priv, &params);
 }
 
@@ -736,20 +774,35 @@
 			  const u16 *csa_offs, size_t csa_offs_len,
 			  int no_encrypt)
 {
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	if (!hapd->driver || !hapd->driver->send_mlme || !hapd->drv_priv)
 		return 0;
 	return hapd->driver->send_mlme(hapd->drv_priv, msg, len, noack, 0,
-				       csa_offs, csa_offs_len, no_encrypt, 0);
+				       csa_offs, csa_offs_len, no_encrypt, 0,
+				       link_id);
 }
 
 
 int hostapd_drv_sta_deauth(struct hostapd_data *hapd,
 			   const u8 *addr, int reason)
 {
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		link_id = hapd->mld_link_id;
+#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,
-					reason);
+					reason, link_id);
 }
 
 
@@ -889,6 +942,7 @@
 				      int **freq_list)
 {
 	int i;
+	bool is_no_ir = false;
 
 	for (i = 0; i < mode->num_channels; i++) {
 		struct hostapd_channel_data *chan = &mode->channels[i];
@@ -917,7 +971,12 @@
 		      (chan->flag & HOSTAPD_CHAN_RADAR)) &&
 		    !(chan->max_tx_power < hapd->iface->conf->min_tx_power))
 			int_array_add_unique(freq_list, chan->freq);
+		else if ((chan->flag & HOSTAPD_CHAN_NO_IR) &&
+			 is_6ghz_freq(chan->freq))
+			is_no_ir = true;
 	}
+
+	hapd->iface->is_no_ir = is_no_ir;
 }
 
 
@@ -935,6 +994,24 @@
 }
 
 
+void hostapd_get_mld_capa(struct hostapd_iface *iface)
+{
+	struct hostapd_data *hapd = iface->bss[0];
+
+	if (!hapd->driver || !hapd->driver->get_mld_capab)
+		return;
+
+	hapd->driver->get_mld_capab(hapd->drv_priv, WPA_IF_AP_BSS,
+				    &iface->mld_eml_capa,
+				    &iface->mld_mld_capa);
+}
+
+
+/**
+ * hostapd_drv_do_acs - Start automatic channel selection
+ * @hapd: BSS data for the device initiating ACS
+ * Returns: 0 on success, -1 on failure, 1 on failure due to NO_IR (AFC)
+ */
 int hostapd_drv_do_acs(struct hostapd_data *hapd)
 {
 	struct drv_acs_params params;
@@ -972,6 +1049,12 @@
 						 false, &freq_list);
 	}
 
+	if (!freq_list && hapd->iface->is_no_ir) {
+		wpa_printf(MSG_ERROR,
+			   "NO_IR: Interface freq_list is empty. Failing do_acs.");
+		return 1;
+	}
+
 	params.freq_list = freq_list;
 	params.edmg_enabled = hapd->iface->conf->enable_edmg;
 
diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h
index 023cbf1..331b0ea 100644
--- a/src/ap/ap_drv_ops.h
+++ b/src/ap/ap_drv_ops.h
@@ -47,7 +47,7 @@
 		    size_t eht_capab_len,
 		    const struct ieee80211_he_6ghz_band_cap *he_6ghz_capab,
 		    u32 flags, u8 qosinfo, u8 vht_opmode, int supp_p2p_ps,
-		    int set);
+		    int set, const u8 *link_addr, bool mld_link_sta);
 int hostapd_set_privacy(struct hostapd_data *hapd, int enabled);
 int hostapd_set_generic_elem(struct hostapd_data *hapd, const u8 *elem,
 			     size_t elem_len);
@@ -62,7 +62,7 @@
 int hostapd_set_ieee8021x(struct hostapd_data *hapd,
 			  struct wpa_bss_params *params);
 int hostapd_get_seqnum(const char *ifname, struct hostapd_data *hapd,
-		       const u8 *addr, int idx, u8 *seq);
+		       const u8 *addr, int idx, int link_id, u8 *seq);
 int hostapd_flush(struct hostapd_data *hapd);
 int hostapd_set_freq(struct hostapd_data *hapd, enum hostapd_hw_mode mode,
 		     int freq, int channel, int edmg, u8 edmg_channel,
@@ -155,6 +155,7 @@
 			    u8 qos_map_set_len);
 
 void hostapd_get_ext_capa(struct hostapd_iface *iface);
+void hostapd_get_mld_capa(struct hostapd_iface *iface);
 
 void hostapd_get_hw_mode_any_channels(struct hostapd_data *hapd,
 				      struct hostapd_hw_modes *mode,
@@ -172,12 +173,13 @@
 
 static inline int hostapd_drv_set_sta_vlan(const char *ifname,
 					   struct hostapd_data *hapd,
-					   const u8 *addr, int vlan_id)
+					   const u8 *addr, int vlan_id,
+					   int link_id)
 {
 	if (hapd->driver == NULL || hapd->driver->set_sta_vlan == NULL)
 		return 0;
 	return hapd->driver->set_sta_vlan(hapd->drv_priv, addr, ifname,
-					  vlan_id);
+					  vlan_id, link_id);
 }
 
 static inline int hostapd_drv_get_inact_sec(struct hostapd_data *hapd,
@@ -199,13 +201,13 @@
 static inline int hostapd_drv_hapd_send_eapol(struct hostapd_data *hapd,
 					      const u8 *addr, const u8 *data,
 					      size_t data_len, int encrypt,
-					      u32 flags)
+					      u32 flags, int link_id)
 {
 	if (hapd->driver == NULL || hapd->driver->hapd_send_eapol == NULL)
 		return 0;
 	return hapd->driver->hapd_send_eapol(hapd->drv_priv, addr, data,
 					     data_len, encrypt,
-					     hapd->own_addr, flags);
+					     hapd->own_addr, flags, link_id);
 }
 
 static inline int hostapd_drv_read_sta_data(
@@ -440,4 +442,16 @@
 }
 #endif /* CONFIG_TESTING_OPTIONS */
 
+#ifdef CONFIG_IEEE80211BE
+static inline int hostapd_drv_link_add(struct hostapd_data *hapd,
+				       u8 link_id, const u8 *addr)
+{
+	if (!hapd->driver || !hapd->drv_priv || !hapd->driver->link_add)
+		return -1;
+
+	return hapd->driver->link_add(hapd->drv_priv, link_id, addr);
+
+}
+#endif /* CONFIG_IEEE80211BE */
+
 #endif /* AP_DRV_OPS */
diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c
index 4ab2a4a..1488dcc 100644
--- a/src/ap/authsrv.c
+++ b/src/ap/authsrv.c
@@ -106,6 +106,15 @@
 {
 	struct radius_server_conf srv;
 	struct hostapd_bss_config *conf = hapd->conf;
+
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using RADIUS server of the first BSS");
+
+		hapd->radius_srv = hapd->mld_first_bss->radius_srv;
+		return 0;
+	}
+
 	os_memset(&srv, 0, sizeof(srv));
 	srv.client_file = conf->radius_server_clients;
 	srv.auth_port = conf->radius_server_auth_port;
@@ -215,6 +224,8 @@
 	cfg->eap_sim_aka_result_ind = hapd->conf->eap_sim_aka_result_ind;
 	cfg->eap_sim_id = hapd->conf->eap_sim_id;
 	cfg->imsi_privacy_key = hapd->imsi_privacy_key;
+	cfg->eap_sim_aka_fast_reauth_limit =
+		hapd->conf->eap_sim_aka_fast_reauth_limit;
 	cfg->tnc = hapd->conf->tnc;
 	cfg->wps = hapd->wps;
 	cfg->fragment_size = hapd->conf->fragment_size;
@@ -238,6 +249,19 @@
 
 int authsrv_init(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG, "MLD: Using auth_serv of the first BSS");
+
+#ifdef EAP_TLS_FUNCS
+		hapd->ssl_ctx = hapd->mld_first_bss->ssl_ctx;
+#endif /* EAP_TLS_FUNCS */
+		hapd->eap_cfg = hapd->mld_first_bss->eap_cfg;
+#ifdef EAP_SIM_DB
+		hapd->eap_sim_db_priv = hapd->mld_first_bss->eap_sim_db_priv;
+#endif /* EAP_SIM_DB */
+		return 0;
+	}
+
 #ifdef EAP_TLS_FUNCS
 	if (hapd->conf->eap_server &&
 	    (hapd->conf->ca_cert || hapd->conf->server_cert ||
@@ -352,6 +376,21 @@
 
 void authsrv_deinit(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Deinit auth_serv of a non-first BSS");
+
+		hapd->radius_srv = NULL;
+		hapd->eap_cfg = NULL;
+#ifdef EAP_SIM_DB
+		hapd->eap_sim_db_priv = NULL;
+#endif /* EAP_SIM_DB */
+#ifdef EAP_TLS_FUNCS
+		hapd->ssl_ctx = NULL;
+#endif /* EAP_TLS_FUNCS */
+		return;
+	}
+
 #ifdef RADIUS_SERVER
 	radius_server_deinit(hapd->radius_srv);
 	hapd->radius_srv = NULL;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index de944fe..1b5cea9 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -17,6 +17,7 @@
 #include "common/ieee802_11_common.h"
 #include "common/hw_features_common.h"
 #include "common/wpa_ctrl.h"
+#include "crypto/sha1.h"
 #include "wps/wps_defs.h"
 #include "p2p/p2p.h"
 #include "hostapd.h"
@@ -87,6 +88,12 @@
 
 static u8 * hostapd_eid_ds_params(struct hostapd_data *hapd, u8 *eid)
 {
+	enum hostapd_hw_mode hw_mode = hapd->iconf->hw_mode;
+
+	if (hw_mode != HOSTAPD_MODE_IEEE80211G &&
+	    hw_mode != HOSTAPD_MODE_IEEE80211B)
+		return eid;
+
 	*eid++ = WLAN_EID_DS_PARAMS;
 	*eid++ = 1;
 	*eid++ = hapd->iconf->channel;
@@ -471,6 +478,7 @@
 	size_t len, rnr_len = 0;
 	u8 elem_count = 0, *elem = NULL, **elem_offset = NULL, *end;
 	u8 rnr_elem_count = 0, *rnr_elem = NULL, **rnr_elem_offset = NULL;
+	size_t i;
 
 	if (!iface->mbssid_max_interfaces ||
 	    iface->num_bss > iface->mbssid_max_interfaces ||
@@ -478,6 +486,14 @@
 	     !iface->ema_max_periodicity))
 		goto fail;
 
+	/* Make sure bss->xrates_supported is set for all BSSs to know whether
+	 * it need to be non-inherited. */
+	for (i = 0; i < iface->num_bss; i++) {
+		u8 buf[100];
+
+		hostapd_eid_ext_supp_rates(iface->bss[i], buf);
+	}
+
 	tx_bss = hostapd_mbssid_get_tx_bss(hapd);
 	len = hostapd_eid_mbssid_len(tx_bss, WLAN_FC_STYPE_BEACON, &elem_count,
 				     NULL, 0, &rnr_len);
@@ -605,6 +621,14 @@
 		buflen += 3 + sizeof(struct ieee80211_eht_operation);
 		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;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -755,6 +779,8 @@
 
 #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);
 		pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP);
 		pos = hostapd_eid_eht_operation(hapd, pos);
 	}
@@ -1365,10 +1391,128 @@
 
 #ifdef CONFIG_FILS
 
+static u16 hostapd_gen_fils_discovery_phy_index(struct hostapd_data *hapd)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be)
+		return FD_CAP_PHY_INDEX_EHT;
+#endif /* CONFIG_IEEE80211BE */
+
+#ifdef CONFIG_IEEE80211AX
+	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax)
+		return FD_CAP_PHY_INDEX_HE;
+#endif /* CONFIG_IEEE80211AX */
+
+#ifdef CONFIG_IEEE80211AC
+	if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac)
+		return FD_CAP_PHY_INDEX_VHT;
+#endif /* CONFIG_IEEE80211AC */
+
+	if (hapd->iconf->ieee80211n && !hapd->conf->disable_11n)
+		return FD_CAP_PHY_INDEX_HT;
+
+	return 0;
+}
+
+
+static u16 hostapd_gen_fils_discovery_nss(struct hostapd_hw_modes *mode,
+					  u16 phy_index, u8 he_mcs_nss_size)
+{
+	u16 nss = 0;
+
+	if (!mode)
+		return 0;
+
+	if (phy_index == FD_CAP_PHY_INDEX_HE) {
+		const u8 *he_mcs = mode->he_capab[IEEE80211_MODE_AP].mcs;
+		int i;
+		u16 mcs[6];
+
+		os_memset(mcs, 0xff, 6 * sizeof(u16));
+
+		if (he_mcs_nss_size == 4) {
+			mcs[0] = WPA_GET_LE16(&he_mcs[0]);
+			mcs[1] = WPA_GET_LE16(&he_mcs[2]);
+		}
+
+		if (he_mcs_nss_size == 8) {
+			mcs[2] = WPA_GET_LE16(&he_mcs[4]);
+			mcs[3] = WPA_GET_LE16(&he_mcs[6]);
+		}
+
+		if (he_mcs_nss_size == 12) {
+			mcs[4] = WPA_GET_LE16(&he_mcs[8]);
+			mcs[5] = WPA_GET_LE16(&he_mcs[10]);
+		}
+
+		for (i = 0; i < HE_NSS_MAX_STREAMS; i++) {
+			u16 nss_mask = 0x3 << (i * 2);
+
+			/*
+			 * If Tx and/or Rx indicate support for a given NSS,
+			 * count it towards the maximum NSS.
+			 */
+			if (he_mcs_nss_size == 4 &&
+			    (((mcs[0] & nss_mask) != nss_mask) ||
+			     ((mcs[1] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+
+			if (he_mcs_nss_size == 8 &&
+			    (((mcs[2] & nss_mask) != nss_mask) ||
+			     ((mcs[3] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+
+			if (he_mcs_nss_size == 12 &&
+			    (((mcs[4] & nss_mask) != nss_mask) ||
+			     ((mcs[5] & nss_mask) != nss_mask))) {
+				nss++;
+				continue;
+			}
+		}
+	} else if (phy_index == FD_CAP_PHY_INDEX_EHT) {
+		u8 rx_nss, tx_nss, max_nss = 0, i;
+		u8 *mcs = mode->eht_capab[IEEE80211_MODE_AP].mcs;
+
+		/*
+		 * The Supported EHT-MCS And NSS Set field for the AP contains
+		 * one to three EHT-MCS Map fields based on the supported
+		 * bandwidth. Check the first byte (max NSS for Rx/Tx that
+		 * supports EHT-MCS 0-9) for each bandwidth (<= 80,
+		 * 160, 320) to find the maximum NSS. This assumes that
+		 * the lowest MCS rates support the largest number of spatial
+		 * streams. If values are different between Tx, Rx or the
+		 * bandwidths, choose the highest value.
+		 */
+		for (i = 0; i < 3; i++) {
+			rx_nss = mcs[3 * i] & 0x0F;
+			if (rx_nss > max_nss)
+				max_nss = rx_nss;
+
+			tx_nss = (mcs[3 * i] & 0xF0) >> 4;
+			if (tx_nss > max_nss)
+				max_nss = tx_nss;
+		}
+
+		nss = max_nss;
+	}
+
+	if (nss > 4)
+		return FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT;
+	if (nss)
+		return (nss - 1) << FD_CAP_NSS_SHIFT;
+
+	return 0;
+}
+
+
 static u16 hostapd_fils_discovery_cap(struct hostapd_data *hapd)
 {
-	u16 cap_info, phy_index = 0;
-	u8 chwidth = FD_CAP_BSS_CHWIDTH_20, mcs_nss_size = 4;
+	u16 cap_info, phy_index;
+	u8 chwidth = FD_CAP_BSS_CHWIDTH_20, he_mcs_nss_size = 4;
 	struct hostapd_hw_modes *mode = hapd->iface->current_mode;
 
 	cap_info = FD_CAP_ESS;
@@ -1376,17 +1520,15 @@
 		cap_info |= FD_CAP_PRIVACY;
 
 	if (is_6ghz_op_class(hapd->iconf->op_class)) {
-		phy_index = FD_CAP_PHY_INDEX_HE;
-
 		switch (hapd->iconf->op_class) {
 		case 137:
 			chwidth = FD_CAP_BSS_CHWIDTH_320;
 			break;
 		case 135:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			/* fallthrough */
 		case 134:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			chwidth = FD_CAP_BSS_CHWIDTH_160_80_80;
 			break;
 		case 133:
@@ -1399,10 +1541,10 @@
 	} else {
 		switch (hostapd_get_oper_chwidth(hapd->iconf)) {
 		case CONF_OPER_CHWIDTH_80P80MHZ:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			/* fallthrough */
 		case CONF_OPER_CHWIDTH_160MHZ:
-			mcs_nss_size += 4;
+			he_mcs_nss_size += 4;
 			chwidth = FD_CAP_BSS_CHWIDTH_160_80_80;
 			break;
 		case CONF_OPER_CHWIDTH_80MHZ:
@@ -1417,79 +1559,13 @@
 		default:
 			break;
 		}
-
-#ifdef CONFIG_IEEE80211AX
-		if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax)
-			phy_index = FD_CAP_PHY_INDEX_HE;
-#endif /* CONFIG_IEEE80211AX */
-#ifdef CONFIG_IEEE80211AC
-		if (!phy_index &&
-		    hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac)
-			phy_index = FD_CAP_PHY_INDEX_VHT;
-#endif /* CONFIG_IEEE80211AC */
-		if (!phy_index &&
-		    hapd->iconf->ieee80211n && !hapd->conf->disable_11n)
-			phy_index = FD_CAP_PHY_INDEX_HT;
 	}
 
+	phy_index = hostapd_gen_fils_discovery_phy_index(hapd);
 	cap_info |= phy_index << FD_CAP_PHY_INDEX_SHIFT;
 	cap_info |= chwidth << FD_CAP_BSS_CHWIDTH_SHIFT;
-
-	if (mode && phy_index == FD_CAP_PHY_INDEX_HE) {
-		const u8 *he_mcs = mode->he_capab[IEEE80211_MODE_AP].mcs;
-		int i;
-		u16 nss = 0, mcs[6];
-
-		os_memset(mcs, 0xffff, 6 * sizeof(u16));
-
-		if (mcs_nss_size == 4) {
-			mcs[0] = WPA_GET_LE16(&he_mcs[0]);
-			mcs[1] = WPA_GET_LE16(&he_mcs[2]);
-		}
-
-		if (mcs_nss_size == 8) {
-			mcs[2] = WPA_GET_LE16(&he_mcs[4]);
-			mcs[3] = WPA_GET_LE16(&he_mcs[6]);
-		}
-
-		if (mcs_nss_size == 12) {
-			mcs[4] = WPA_GET_LE16(&he_mcs[8]);
-			mcs[5] = WPA_GET_LE16(&he_mcs[10]);
-		}
-
-		for (i = 0; i < HE_NSS_MAX_STREAMS; i++) {
-			u16 nss_mask = 0x3 << (i * 2);
-
-			/*
-			 * If NSS values supported by RX and TX are different
-			 * then choose the smaller of the two as the maximum
-			 * supported NSS as that is the value supported by
-			 * both RX and TX.
-			 */
-			if (mcs_nss_size == 4 &&
-			    (((mcs[0] & nss_mask) == nss_mask) ||
-			     ((mcs[1] & nss_mask) == nss_mask)))
-				continue;
-
-			if (mcs_nss_size == 8 &&
-			    (((mcs[2] & nss_mask) == nss_mask) ||
-			     ((mcs[3] & nss_mask) == nss_mask)))
-				continue;
-
-			if (mcs_nss_size == 12 &&
-			    (((mcs[4] & nss_mask) == nss_mask) ||
-			     ((mcs[5] & nss_mask) == nss_mask)))
-				continue;
-
-			nss++;
-		}
-
-		if (nss > 4)
-			cap_info |= FD_CAP_NSS_5_8 << FD_CAP_NSS_SHIFT;
-		else if (nss)
-			cap_info |= (nss - 1) << FD_CAP_NSS_SHIFT;
-	}
-
+	cap_info |= hostapd_gen_fils_discovery_nss(mode, phy_index,
+						   he_mcs_nss_size);
 	return cap_info;
 }
 
@@ -1711,6 +1787,14 @@
 		tail_len += 3 + sizeof(struct ieee80211_eht_operation);
 		if (hapd->iconf->punct_bitmap)
 			tail_len += 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)
+			tail_len += 256;
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1881,6 +1965,9 @@
 
 #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_capab(hapd, tailpos,
 						IEEE80211_MODE_AP);
 		tailpos = hostapd_eid_eht_operation(hapd, tailpos);
@@ -1940,6 +2027,50 @@
 	resp = hostapd_probe_resp_offloads(hapd, &resp_len);
 #endif /* NEED_AP_MLME */
 
+	/* If key management offload is enabled, configure PSK to the driver. */
+	if (wpa_key_mgmt_wpa_psk_no_sae(hapd->conf->wpa_key_mgmt) &&
+	    (hapd->iface->drv_flags2 &
+	     WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) {
+		if (hapd->conf->ssid.wpa_psk && hapd->conf->ssid.wpa_psk_set) {
+			os_memcpy(params->psk, hapd->conf->ssid.wpa_psk->psk,
+				  PMK_LEN);
+			params->psk_len = PMK_LEN;
+		} else if (hapd->conf->ssid.wpa_passphrase &&
+			   pbkdf2_sha1(hapd->conf->ssid.wpa_passphrase,
+				       hapd->conf->ssid.ssid,
+				       hapd->conf->ssid.ssid_len, 4096,
+				       params->psk, PMK_LEN) == 0) {
+			params->psk_len = PMK_LEN;
+		}
+	}
+
+#ifdef CONFIG_SAE
+	/* If SAE offload is enabled, provide password to lower layer for
+	 * SAE authentication and PMK generation.
+	 */
+	if (wpa_key_mgmt_sae(hapd->conf->wpa_key_mgmt) &&
+	    (hapd->iface->drv_flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP)) {
+		if (hostapd_sae_pk_in_use(hapd->conf)) {
+			wpa_printf(MSG_ERROR,
+				   "SAE PK not supported with SAE offload");
+			return -1;
+		}
+
+		if (hostapd_sae_pw_id_in_use(hapd->conf)) {
+			wpa_printf(MSG_ERROR,
+				   "SAE Password Identifiers not supported with SAE offload");
+			return -1;
+		}
+
+		params->sae_password = sae_get_password(hapd, NULL, NULL, NULL,
+							NULL, NULL);
+		if (!params->sae_password) {
+			wpa_printf(MSG_ERROR, "SAE password not configured for offload");
+			return -1;
+		}
+	}
+#endif /* CONFIG_SAE */
+
 	params->head = (u8 *) head;
 	params->head_len = head_len;
 	params->tail = tail;
@@ -2030,6 +2161,14 @@
 		}
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && hapd->iconf->ieee80211be &&
+	    !hapd->conf->disable_11be) {
+		params->mld_ap = true;
+		params->mld_link_id = hapd->mld_link_id;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	return 0;
 }
 
@@ -2172,6 +2311,12 @@
 }
 
 
+void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd)
+{
+	__ieee802_11_set_beacon(hapd);
+}
+
+
 int ieee802_11_set_beacon(struct hostapd_data *hapd)
 {
 	struct hostapd_iface *iface = hapd->iface;
@@ -2186,21 +2331,29 @@
 	if (!iface->interfaces || iface->interfaces->count <= 1)
 		return 0;
 
-	/* Update Beacon frames in case of 6 GHz colocation */
+	/* Update Beacon frames in case of 6 GHz colocation or AP MLD */
 	is_6g = is_6ghz_op_class(iface->conf->op_class);
 	for (j = 0; j < iface->interfaces->count; j++) {
-		struct hostapd_iface *colocated;
+		struct hostapd_iface *other;
+		bool mld_ap = false;
 
-		colocated = iface->interfaces->iface[j];
-		if (colocated == iface || !colocated || !colocated->conf)
+		other = iface->interfaces->iface[j];
+		if (other == iface || !other || !other->conf)
 			continue;
 
-		if (is_6g == is_6ghz_op_class(colocated->conf->op_class))
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && other->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == other->bss[0]->conf->mld_id)
+			mld_ap = true;
+#endif /* CONFIG_IEEE80211BE */
+
+		if (is_6g == is_6ghz_op_class(other->conf->op_class) &&
+		    !mld_ap)
 			continue;
 
-		for (i = 0; i < colocated->num_bss; i++) {
-			if (colocated->bss[i] && colocated->bss[i]->started)
-				__ieee802_11_set_beacon(colocated->bss[i]);
+		for (i = 0; i < other->num_bss; i++) {
+			if (other->bss[i] && other->bss[i]->started)
+				__ieee802_11_set_beacon(other->bss[i]);
 		}
 	}
 
diff --git a/src/ap/beacon.h b/src/ap/beacon.h
index c320825..b32b2a7 100644
--- a/src/ap/beacon.h
+++ b/src/ap/beacon.h
@@ -15,6 +15,7 @@
 void handle_probe_req(struct hostapd_data *hapd,
 		      const struct ieee80211_mgmt *mgmt, size_t len,
 		      int ssi_signal);
+void ieee802_11_set_beacon_per_bss_only(struct hostapd_data *hapd);
 int ieee802_11_set_beacon(struct hostapd_data *hapd);
 int ieee802_11_set_beacons(struct hostapd_iface *iface);
 int ieee802_11_update_beacons(struct hostapd_iface *iface);
diff --git a/src/ap/bss_load.c b/src/ap/bss_load.c
index 725d3cd..e9baafc 100644
--- a/src/ap/bss_load.c
+++ b/src/ap/bss_load.c
@@ -55,7 +55,7 @@
 		return;
 	}
 
-	ieee802_11_set_beacon(hapd);
+	ieee802_11_set_beacon_per_bss_only(hapd);
 
 	if (get_bss_load_update_timeout(hapd, &sec, &usec) < 0)
 		return;
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index 6934a73..a6fcb7e 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -845,6 +845,16 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+		if (!iconf->he_op.he_bss_color_disabled &&
+		    iconf->he_op.he_bss_color) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "he_bss_color=%d\n",
+					  iconf->he_op.he_bss_color);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
 	}
 #endif /* CONFIG_IEEE80211AX */
 
@@ -938,6 +948,21 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+#ifdef CONFIG_IEEE80211BE
+		if (bss->conf->mld_ap) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "mld_addr[%d]=" MACSTR "\n"
+					  "mld_id[%d]=%d\n"
+					  "mld_link_id[%d]=%d\n",
+					  (int) i, MAC2STR(bss->mld_addr),
+					  (int) i, bss->conf->mld_id,
+					  (int) i, bss->mld_link_id);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
+#endif /* CONFIG_IEEE80211BE */
 	}
 
 	if (hapd->conf->chan_util_avg_period) {
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index e8c5ec9..9a5d3c8 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -34,7 +34,7 @@
 
 static bool dfs_use_radar_background(struct hostapd_iface *iface)
 {
-	return (iface->drv_flags2 & WPA_DRIVER_RADAR_BACKGROUND) &&
+	return (iface->drv_flags2 & WPA_DRIVER_FLAGS2_RADAR_BACKGROUND) &&
 		iface->conf->enable_background_radar;
 }
 
@@ -362,8 +362,9 @@
 	if (iface->conf->ieee80211n && iface->conf->secondary_channel == -1)
 		channel_no -= 4;
 
-	/* VHT/HE */
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	/* VHT/HE/EHT */
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		switch (hostapd_get_oper_chwidth(iface->conf)) {
 		case CONF_OPER_CHWIDTH_USE_HT:
 			break;
@@ -381,9 +382,13 @@
 			chan_seg1 = hostapd_get_oper_centr_freq_seg1_idx(
 				iface->conf) - 6;
 			break;
+		case CONF_OPER_CHWIDTH_320MHZ:
+			channel_no = hostapd_get_oper_centr_freq_seg0_idx(
+				iface->conf) - 30;
+			break;
 		default:
 			wpa_printf(MSG_INFO,
-				   "DFS only VHT20/40/80/160/80+80 is supported now");
+				   "DFS only EHT20/40/80/160/80+80/320 is supported now");
 			channel_no = -1;
 			break;
 		}
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 70dd18e..7a8ea4e 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -2406,7 +2406,9 @@
 	char ssid_hex[2 * SSID_MAX_LEN + 1], *pass_hex = NULL;
 	char cmd[300];
 	const char *password = NULL;
+#ifdef CONFIG_SAE
 	struct sae_password_entry *e;
+#endif /* CONFIG_SAE */
 	int conf_id = -1;
 	bool sae = false, psk = false;
 	size_t len;
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 510a06c..98794c2 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -59,9 +59,10 @@
 	if (!sta->fils_pending_assoc_req)
 		return;
 
-	ieee802_11_parse_elems(sta->fils_pending_assoc_req,
-			       sta->fils_pending_assoc_req_len, &elems, 0);
-	if (!elems.fils_session) {
+	if (ieee802_11_parse_elems(sta->fils_pending_assoc_req,
+				   sta->fils_pending_assoc_req_len, &elems,
+				   0) == ParseFailed ||
+	    !elems.fils_session) {
 		wpa_printf(MSG_DEBUG, "%s failed to find FILS Session element",
 			   __func__);
 		return;
@@ -131,8 +132,122 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static int hostapd_update_sta_links_status(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   const u8 *resp_ies,
+					   size_t resp_ies_len)
+{
+	struct mld_info *info = &sta->mld_info;
+	struct wpabuf *mlebuf;
+	const u8 *mle, *pos;
+	struct ieee802_11_elems elems;
+	size_t mle_len, rem_len;
+	int ret = 0;
+
+	if (!resp_ies) {
+		wpa_printf(MSG_DEBUG,
+			   "MLO: (Re)Association Response frame elements not available");
+		return -1;
+	}
+
+	if (ieee802_11_parse_elems(resp_ies, resp_ies_len, &elems, 0) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLO: Failed to parse (Re)Association Response frame elements");
+		return -1;
+	}
+
+	mlebuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlebuf) {
+		wpa_printf(MSG_ERROR,
+			   "MLO: Basic Multi-Link element not found in (Re)Association Response frame");
+		return -1;
+	}
+
+	mle = wpabuf_head(mlebuf);
+	mle_len = wpabuf_len(mlebuf);
+	if (mle_len < MULTI_LINK_CONTROL_LEN + 1 ||
+	    mle_len - MULTI_LINK_CONTROL_LEN < mle[MULTI_LINK_CONTROL_LEN]) {
+		wpa_printf(MSG_ERROR,
+			   "MLO: Invalid Multi-Link element in (Re)Association Response frame");
+		ret = -1;
+		goto out;
+	}
+
+	/* Skip Common Info */
+	pos = mle + MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN];
+	rem_len = mle_len -
+		(MULTI_LINK_CONTROL_LEN + mle[MULTI_LINK_CONTROL_LEN]);
+
+	/* Parse Subelements */
+	while (rem_len > 2) {
+		size_t ie_len = 2 + pos[1];
+
+		if (rem_len < ie_len)
+			break;
+
+		if (pos[0] == MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
+			u8 link_id;
+			const u8 *sta_profile;
+			size_t sta_profile_len;
+			u16 sta_ctrl;
+
+			if (pos[1] < BASIC_MLE_STA_CTRL_LEN + 1) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid per-STA profile IE");
+				goto next_subelem;
+			}
+
+			sta_profile_len = pos[1];
+			sta_profile = &pos[2];
+			sta_ctrl = WPA_GET_LE16(sta_profile);
+			link_id = sta_ctrl & BASIC_MLE_STA_CTRL_LINK_ID_MASK;
+			if (link_id >= MAX_NUM_MLD_LINKS) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid link ID in per-STA profile IE");
+				goto next_subelem;
+			}
+
+			/* Skip STA Control and STA Info */
+			if (sta_profile_len - BASIC_MLE_STA_CTRL_LEN <
+			    sta_profile[BASIC_MLE_STA_CTRL_LEN]) {
+				wpa_printf(MSG_DEBUG,
+					   "MLO: Invalid STA info in per-STA profile IE");
+				goto next_subelem;
+			}
+
+			sta_profile_len = sta_profile_len -
+				(BASIC_MLE_STA_CTRL_LEN +
+				 sta_profile[BASIC_MLE_STA_CTRL_LEN]);
+			sta_profile = sta_profile + BASIC_MLE_STA_CTRL_LEN +
+				sta_profile[BASIC_MLE_STA_CTRL_LEN];
+
+			/* Skip Capabilities Information field */
+			if (sta_profile_len < 2)
+				goto next_subelem;
+			sta_profile_len -= 2;
+			sta_profile += 2;
+
+			/* Get status of the link */
+			info->links[link_id].status = WPA_GET_LE16(sta_profile);
+		}
+next_subelem:
+		pos += ie_len;
+		rem_len -= ie_len;
+	}
+
+out:
+	wpabuf_free(mlebuf);
+	return ret;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
-			const u8 *req_ies, size_t req_ies_len, int reassoc)
+			const u8 *req_ies, size_t req_ies_len,
+			const u8 *resp_ies, size_t resp_ies_len,
+			const u8 *link_addr, int reassoc)
 {
 	struct sta_info *sta;
 	int new_assoc;
@@ -145,6 +260,9 @@
 	u16 reason = WLAN_REASON_UNSPECIFIED;
 	int status = WLAN_STATUS_SUCCESS;
 	const u8 *p2p_dev_addr = NULL;
+#ifdef CONFIG_OWE
+	struct hostapd_iface *iface = hapd->iface;
+#endif /* CONFIG_OWE */
 
 	if (addr == NULL) {
 		/*
@@ -176,7 +294,12 @@
 	hostapd_logger(hapd, addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_INFO, "associated");
 
-	ieee802_11_parse_elems(req_ies, req_ies_len, &elems, 0);
+	if (ieee802_11_parse_elems(req_ies, req_ies_len, &elems, 0) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG, "%s: Could not parse elements", __func__);
+		return -1;
+	}
+
 	if (elems.wps_ie) {
 		ie = elems.wps_ie - 2;
 		ielen = elems.wps_ie_len + 2;
@@ -220,6 +343,53 @@
 			return -1;
 		}
 	}
+
+	if (hapd->conf->wpa && check_sa_query_need(hapd, sta)) {
+		status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
+		p = hostapd_eid_assoc_comeback_time(hapd, sta, p);
+		hostapd_sta_assoc(hapd, addr, reassoc, status, buf, p - buf);
+
+		return 0;
+	}
+
+#ifdef CONFIG_IEEE80211BE
+	if (link_addr) {
+		struct mld_info *info = &sta->mld_info;
+		int i, num_valid_links = 0;
+		u8 link_id = hapd->mld_link_id;
+
+		info->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;
+		os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN);
+		os_memcpy(info->links[link_id].local_addr, hapd->own_addr,
+			  ETH_ALEN);
+
+		if (!elems.basic_mle ||
+		    hostapd_process_ml_assoc_req(hapd, &elems, sta) !=
+		    WLAN_STATUS_SUCCESS) {
+			reason = WLAN_REASON_UNSPECIFIED;
+			wpa_printf(MSG_DEBUG,
+				   "Failed to get STA non-assoc links info");
+			goto fail;
+		}
+
+		for (i = 0 ; i < MAX_NUM_MLD_LINKS; i++) {
+			if (info->links[i].valid)
+				num_valid_links++;
+		}
+		if (num_valid_links > 1 &&
+		    hostapd_update_sta_links_status(hapd, sta, resp_ies,
+						    resp_ies_len)) {
+			wpa_printf(MSG_DEBUG,
+				   "Failed to get STA non-assoc links status info");
+			reason = WLAN_REASON_UNSPECIFIED;
+			goto fail;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2);
 
 	/*
@@ -314,17 +484,6 @@
 		    os_memcmp(ie + 2, "\x00\x50\xf2\x04", 4) == 0) {
 			struct wpabuf *wps;
 
-			if (check_sa_query_need(hapd, sta)) {
-				status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
-				p = hostapd_eid_assoc_comeback_time(hapd, sta,
-								    p);
-
-				hostapd_sta_assoc(hapd, addr, reassoc, status,
-						  buf, p - buf);
-				return 0;
-			}
-
 			sta->flags |= WLAN_STA_WPS;
 			wps = ieee802_11_vendor_ie_concat(ie, ielen,
 							  WPS_IE_VENDOR_TYPE);
@@ -340,16 +499,6 @@
 		}
 #endif /* CONFIG_WPS */
 
-		if (check_sa_query_need(hapd, sta)) {
-			status = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
-			p = hostapd_eid_assoc_comeback_time(hapd, sta, p);
-
-			hostapd_sta_assoc(hapd, addr, reassoc, status, buf,
-					  p - buf);
-			return 0;
-		}
-
 		if (sta->wpa_sm == NULL)
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
@@ -359,6 +508,15 @@
 				   "Failed to initialize WPA state machine");
 			return -1;
 		}
+#ifdef CONFIG_IEEE80211BE
+		if (sta->mld_info.mld_sta) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Set ML info in RSN Authenticator");
+			wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+					     sta->mld_assoc_link_id,
+					     &sta->mld_info);
+		}
+#endif /* CONFIG_IEEE80211BE */
 		res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
 					  hapd->iface->freq,
 					  ie, ielen,
@@ -617,6 +775,7 @@
 
 #ifdef CONFIG_OWE
 	if ((hapd->conf->wpa_key_mgmt & WPA_KEY_MGMT_OWE) &&
+	    !(iface->drv_flags2 & WPA_DRIVER_FLAGS2_OWE_OFFLOAD_AP) &&
 	    wpa_auth_sta_key_mgmt(sta->wpa_sm) == WPA_KEY_MGMT_OWE &&
 	    elems.owe_dh) {
 		u8 *npos;
@@ -863,7 +1022,7 @@
 {
 #ifdef NEED_AP_MLME
 	int channel, chwidth, is_dfs0, is_dfs;
-	u8 seg0_idx = 0, seg1_idx = 0;
+	u8 seg0_idx = 0, seg1_idx = 0, op_class, chan_no;
 	size_t i;
 
 	hostapd_logger(hapd, NULL, HOSTAPD_MODULE_IEEE80211,
@@ -917,6 +1076,12 @@
 		break;
 	}
 
+	/* The operating channel changed when CSA finished, so need to update
+	 * hw_mode for all following operations to cover the cases where the
+	 * driver changed the operating band. */
+	if (finished && hostapd_csa_update_hwmode(hapd->iface))
+		return;
+
 	switch (hapd->iface->current_mode->mode) {
 	case HOSTAPD_MODE_IEEE80211A:
 		if (cf1 == 5935)
@@ -941,9 +1106,9 @@
 
 	hapd->iconf->channel = channel;
 	hapd->iconf->ieee80211n = ht;
-	if (!ht) {
+	if (!ht)
 		hapd->iconf->ieee80211ac = 0;
-	} else if (hapd->iconf->ch_switch_vht_config) {
+	if (hapd->iconf->ch_switch_vht_config) {
 		/* CHAN_SWITCH VHT config */
 		if (hapd->iconf->ch_switch_vht_config &
 		    CH_SWITCH_VHT_ENABLED)
@@ -951,28 +1116,35 @@
 		else if (hapd->iconf->ch_switch_vht_config &
 			 CH_SWITCH_VHT_DISABLED)
 			hapd->iconf->ieee80211ac = 0;
-	} else if (hapd->iconf->ch_switch_he_config) {
+	}
+	if (hapd->iconf->ch_switch_he_config) {
 		/* CHAN_SWITCH HE config */
 		if (hapd->iconf->ch_switch_he_config &
-		    CH_SWITCH_HE_ENABLED)
+		    CH_SWITCH_HE_ENABLED) {
 			hapd->iconf->ieee80211ax = 1;
+			if (hapd->iface->freq > 4000 &&
+			    hapd->iface->freq < 5895)
+				hapd->iconf->ieee80211ac = 1;
+		}
 		else if (hapd->iconf->ch_switch_he_config &
 			 CH_SWITCH_HE_DISABLED)
 			hapd->iconf->ieee80211ax = 0;
+	}
 #ifdef CONFIG_IEEE80211BE
-	} else if (hapd->iconf->ch_switch_eht_config) {
+	if (hapd->iconf->ch_switch_eht_config) {
 		/* CHAN_SWITCH EHT config */
 		if (hapd->iconf->ch_switch_eht_config &
 		    CH_SWITCH_EHT_ENABLED) {
 			hapd->iconf->ieee80211be = 1;
 			hapd->iconf->ieee80211ax = 1;
-			if (!is_6ghz_freq(hapd->iface->freq))
+			if (!is_6ghz_freq(hapd->iface->freq) &&
+			    hapd->iface->freq > 4000)
 				hapd->iconf->ieee80211ac = 1;
 		} else if (hapd->iconf->ch_switch_eht_config &
 			   CH_SWITCH_EHT_DISABLED)
 			hapd->iconf->ieee80211be = 0;
-#endif /* CONFIG_IEEE80211BE */
 	}
+#endif /* CONFIG_IEEE80211BE */
 	hapd->iconf->ch_switch_vht_config = 0;
 	hapd->iconf->ch_switch_he_config = 0;
 	hapd->iconf->ch_switch_eht_config = 0;
@@ -985,6 +1157,10 @@
 		hapd->iconf->ht_capab &= ~HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
 
 	hapd->iconf->secondary_channel = offset;
+	if (ieee80211_freq_to_channel_ext(freq, offset, chwidth,
+					  &op_class, &chan_no) !=
+	    NUM_HOSTAPD_MODES)
+		hapd->iconf->op_class = op_class;
 	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);
@@ -1202,6 +1378,9 @@
 			hapd->iconf, acs_res->vht_seg1_center_ch);
 		hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, 0);
 	}
+
+	if (hapd->iface->conf->ieee80211be && acs_res->puncture_bitmap)
+		hapd->iconf->punct_bitmap = acs_res->puncture_bitmap;
 #endif /* CONFIG_IEEE80211BE */
 
 out:
@@ -1418,6 +1597,23 @@
 
 #ifdef NEED_AP_MLME
 
+static struct hostapd_data *
+switch_link_hapd(struct hostapd_data *hapd, int link_id)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && link_id >= 0) {
+		struct hostapd_data *link_bss;
+
+		link_bss = hostapd_mld_get_link_bss(hapd, link_id);
+		if (link_bss)
+			return link_bss;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return hapd;
+}
+
+
 #define HAPD_BROADCAST ((struct hostapd_data *) -1)
 
 static struct hostapd_data * get_hapd_bssid(struct hostapd_iface *iface,
@@ -1454,11 +1650,15 @@
 
 static int hostapd_mgmt_rx(struct hostapd_data *hapd, struct rx_mgmt *rx_mgmt)
 {
-	struct hostapd_iface *iface = hapd->iface;
+	struct hostapd_iface *iface;
 	const struct ieee80211_hdr *hdr;
 	const u8 *bssid;
 	struct hostapd_frame_info fi;
 	int ret;
+	bool is_mld = false;
+
+	hapd = switch_link_hapd(hapd, rx_mgmt->link_id);
+	iface = hapd->iface;
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (hapd->ext_mgmt_frame_handling) {
@@ -1480,8 +1680,16 @@
 	if (bssid == NULL)
 		return 0;
 
-	hapd = get_hapd_bssid(iface, bssid);
-	if (hapd == NULL) {
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap &&
+	    os_memcmp(hapd->mld_addr, bssid, ETH_ALEN) == 0)
+		is_mld = true;
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!is_mld)
+		hapd = get_hapd_bssid(iface, bssid);
+
+	if (!hapd) {
 		u16 fc = le_to_host16(hdr->frame_control);
 
 		/*
@@ -1526,15 +1734,34 @@
 
 
 static void hostapd_mgmt_tx_cb(struct hostapd_data *hapd, const u8 *buf,
-			       size_t len, u16 stype, int ok)
+			       size_t len, u16 stype, int ok, int link_id)
 {
 	struct ieee80211_hdr *hdr;
-	struct hostapd_data *orig_hapd = hapd;
+	struct hostapd_data *orig_hapd, *tmp_hapd;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && link_id != -1) {
+		tmp_hapd = hostapd_mld_get_link_bss(hapd, link_id);
+		if (tmp_hapd)
+			hapd = tmp_hapd;
+	}
+#endif /* CONFIG_IEEE80211BE */
+	orig_hapd = hapd;
 
 	hdr = (struct ieee80211_hdr *) buf;
-	hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len));
-	if (!hapd)
+	tmp_hapd = get_hapd_bssid(hapd->iface, get_hdr_bssid(hdr, len));
+	if (tmp_hapd) {
+		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) {
+		/* AP MLD address match - use hapd pointer as-is */
+#endif /* CONFIG_IEEE80211BE */
+	} else {
 		return;
+	}
+
 	if (hapd == HAPD_BROADCAST) {
 		if (stype != WLAN_FC_STYPE_ACTION || len <= 25 ||
 		    buf[24] != WLAN_ACTION_PUBLIC)
@@ -1575,20 +1802,75 @@
 }
 
 
-static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
-				   const u8 *data, size_t data_len,
-				   enum frame_encryption encrypted)
+static struct hostapd_data * hostapd_find_by_sta(struct hostapd_iface *iface,
+						 const u8 *src)
 {
-	struct hostapd_iface *iface = hapd->iface;
 	struct sta_info *sta;
-	size_t j;
+	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) {
-			hapd = iface->bss[j];
-			break;
+		if (sta && sta->flags & WLAN_STA_ASSOC)
+			return iface->bss[j];
+	}
+
+	return NULL;
+}
+
+
+static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
+				   const u8 *data, size_t data_len,
+				   enum frame_encryption encrypted,
+				   int link_id)
+{
+	struct hostapd_data *orig_hapd = hapd;
+
+#ifdef CONFIG_IEEE80211BE
+	if (link_id != -1) {
+		struct hostapd_data *h_hapd;
+
+		hapd = switch_link_hapd(hapd, link_id);
+		h_hapd = hostapd_find_by_sta(hapd->iface, src);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src);
+		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;
+			}
 		}
+	} else {
+		hapd = hostapd_find_by_sta(hapd->iface, src);
+	}
+#else /* CONFIG_IEEE80211BE */
+	hapd = hostapd_find_by_sta(hapd->iface, src);
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!hapd) {
+		/* WLAN cases need to have an existing association, but non-WLAN
+		 * cases (mainly, wired IEEE 802.1X) need to be able to process
+		 * EAPOL frames from new devices that do not yet have a STA
+		 * entry and as such, do not get a match in
+		 * hostapd_find_by_sta(). */
+		wpa_printf(MSG_DEBUG,
+			   "No STA-specific hostapd instance for EAPOL RX found - fall back to initial context");
+		hapd = orig_hapd;
 	}
 
 	ieee802_1x_receive(hapd, src, data, data_len, encrypted);
@@ -1827,7 +2109,7 @@
 #ifdef CONFIG_OWE
 static int hostapd_notif_update_dh_ie(struct hostapd_data *hapd,
 				      const u8 *peer, const u8 *ie,
-				      size_t ie_len)
+				      size_t ie_len, const u8 *link_addr)
 {
 	u16 status;
 	struct sta_info *sta;
@@ -1877,15 +2159,31 @@
 	}
 	sta->flags &= ~(WLAN_STA_WPS | WLAN_STA_MAYBE_WPS | WLAN_STA_WPS2);
 
+#ifdef CONFIG_IEEE80211BE
+	if (link_addr) {
+		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;;
+		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,
+			  ETH_ALEN);
+		os_memcpy(info->links[link_id].peer_addr, link_addr, ETH_ALEN);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	status = owe_process_rsn_ie(hapd, sta, elems.rsn_ie,
 				    elems.rsn_ie_len, elems.owe_dh,
-				    elems.owe_dh_len);
+				    elems.owe_dh_len, link_addr);
 	if (status != WLAN_STATUS_SUCCESS)
 		ap_free_sta(hapd, sta);
 
 	return 0;
 err:
-	hostapd_drv_update_dh_ie(hapd, peer, status, NULL, 0);
+	hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : peer, status,
+				 NULL, 0);
 	return 0;
 }
 #endif /* CONFIG_OWE */
@@ -1895,6 +2193,7 @@
 			  union wpa_event_data *data)
 {
 	struct hostapd_data *hapd = ctx;
+	struct sta_info *sta;
 #ifndef CONFIG_NO_STDOUT_DEBUG
 	int level = MSG_DEBUG;
 
@@ -1935,7 +2234,8 @@
 			hostapd_mgmt_tx_cb(hapd, data->tx_status.data,
 					   data->tx_status.data_len,
 					   data->tx_status.stype,
-					   data->tx_status.ack);
+					   data->tx_status.ack,
+					   data->tx_status.link_id);
 			break;
 		case WLAN_FC_TYPE_DATA:
 			hostapd_tx_status(hapd, data->tx_status.dst,
@@ -1946,6 +2246,7 @@
 		}
 		break;
 	case EVENT_EAPOL_TX_STATUS:
+		hapd = switch_link_hapd(hapd, data->eapol_tx_status.link_id);
 		hostapd_eapol_tx_status(hapd, data->eapol_tx_status.dst,
 					data->eapol_tx_status.data,
 					data->eapol_tx_status.data_len,
@@ -1987,23 +2288,60 @@
 		hostapd_event_eapol_rx(hapd, data->eapol_rx.src,
 				       data->eapol_rx.data,
 				       data->eapol_rx.data_len,
-				       data->eapol_rx.encrypted);
+				       data->eapol_rx.encrypted,
+				       data->eapol_rx.link_id);
 		break;
 	case EVENT_ASSOC:
 		if (!data)
 			return;
+#ifdef CONFIG_IEEE80211BE
+		if (data->assoc_info.assoc_link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->assoc_info.assoc_link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for EVENT_ASSOC");
+				return;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_notif_assoc(hapd, data->assoc_info.addr,
 				    data->assoc_info.req_ies,
 				    data->assoc_info.req_ies_len,
+				    data->assoc_info.resp_ies,
+				    data->assoc_info.resp_ies_len,
+				    data->assoc_info.link_addr,
 				    data->assoc_info.reassoc);
 		break;
+	case EVENT_PORT_AUTHORIZED:
+		/* Port authorized event for an associated STA */
+		sta = ap_get_sta(hapd, data->port_authorized.sta_addr);
+		if (sta)
+			ap_sta_set_authorized(hapd, sta, 1);
+		else
+			wpa_printf(MSG_DEBUG,
+				   "No STA info matching port authorized event found");
+		break;
 #ifdef CONFIG_OWE
 	case EVENT_UPDATE_DH:
 		if (!data)
 			return;
+#ifdef CONFIG_IEEE80211BE
+		if (data->update_dh.assoc_link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->update_dh.assoc_link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for EVENT_UPDATE_DH assoc_link_id=%d",
+					   data->update_dh.assoc_link_id);
+				return;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_notif_update_dh_ie(hapd, data->update_dh.peer,
 					   data->update_dh.ie,
-					   data->update_dh.ie_len);
+					   data->update_dh.ie_len,
+					   data->update_dh.link_addr);
 		break;
 #endif /* CONFIG_OWE */
 	case EVENT_DISASSOC:
@@ -2154,7 +2492,8 @@
 	case EVENT_CCA_NOTIFY:
 		wpa_printf(MSG_DEBUG, "CCA finished on on %s",
 			   hapd->conf->iface);
-		hapd->iface->conf->he_op.he_bss_color = hapd->cca_color;
+		if (hapd->cca_color)
+			hapd->iface->conf->he_op.he_bss_color = hapd->cca_color;
 		hostapd_cleanup_cca_params(hapd);
 		break;
 #endif /* CONFIG_IEEE80211AX */
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 112e6fa..236381f 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -55,6 +55,7 @@
 #include "hs20.h"
 #include "airtime_policy.h"
 #include "wpa_auth_kay.h"
+#include "hw_features.h"
 
 
 static int hostapd_flush_old_stations(struct hostapd_data *hapd, u16 reason);
@@ -393,6 +394,25 @@
 #endif /* CONFIG_WEP */
 
 
+static void hostapd_clear_drv_priv(struct hostapd_data *hapd)
+{
+	unsigned int i;
+
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		struct hostapd_iface *iface = hapd->iface->interfaces->iface[i];
+
+		if (hapd->iface == iface || !iface)
+			continue;
+
+		if (iface->bss && iface->bss[0] &&
+		    iface->bss[0]->mld_first_bss == hapd)
+			iface->bss[0]->drv_priv = NULL;
+	}
+
+	hapd->drv_priv = NULL;
+}
+
+
 void hostapd_free_hapd_data(struct hostapd_data *hapd)
 {
 	os_free(hapd->probereq_cb);
@@ -420,9 +440,11 @@
 	vlan_deinit(hapd);
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
-	radius_client_deinit(hapd->radius);
+	if (!hapd->mld_first_bss) {
+		radius_client_deinit(hapd->radius);
+		radius_das_deinit(hapd->radius_das);
+	}
 	hapd->radius = NULL;
-	radius_das_deinit(hapd->radius_das);
 	hapd->radius_das = NULL;
 #endif /* CONFIG_NO_RADIUS */
 
@@ -449,7 +471,7 @@
 			 * driver wrapper may have removed its internal instance
 			 * and hapd->drv_priv is not valid anymore.
 			 */
-			hapd->drv_priv = NULL;
+			hostapd_clear_drv_priv(hapd);
 		}
 	}
 
@@ -1196,6 +1218,10 @@
 	u8 if_addr[ETH_ALEN];
 	int flush_old_stations = 1;
 
+	if (hapd->mld_first_bss)
+		wpa_printf(MSG_DEBUG,
+			   "MLD: %s: Setting non-first BSS", __func__);
+
 	wpa_printf(MSG_DEBUG, "%s(hapd=%p (%s), first=%d)",
 		   __func__, hapd, conf->iface, first);
 
@@ -1354,34 +1380,43 @@
 	}
 #endif /* CONFIG_SQLITE */
 
-	hapd->radius = radius_client_init(hapd, conf->radius);
-	if (hapd->radius == NULL) {
-		wpa_printf(MSG_ERROR, "RADIUS client initialization failed.");
-		return -1;
-	}
-
-	if (conf->radius_das_port) {
-		struct radius_das_conf das_conf;
-		os_memset(&das_conf, 0, sizeof(das_conf));
-		das_conf.port = conf->radius_das_port;
-		das_conf.shared_secret = conf->radius_das_shared_secret;
-		das_conf.shared_secret_len =
-			conf->radius_das_shared_secret_len;
-		das_conf.client_addr = &conf->radius_das_client_addr;
-		das_conf.time_window = conf->radius_das_time_window;
-		das_conf.require_event_timestamp =
-			conf->radius_das_require_event_timestamp;
-		das_conf.require_message_authenticator =
-			conf->radius_das_require_message_authenticator;
-		das_conf.ctx = hapd;
-		das_conf.disconnect = hostapd_das_disconnect;
-		das_conf.coa = hostapd_das_coa;
-		hapd->radius_das = radius_das_init(&das_conf);
-		if (hapd->radius_das == NULL) {
-			wpa_printf(MSG_ERROR, "RADIUS DAS initialization "
-				   "failed.");
+	if (!hapd->mld_first_bss) {
+		hapd->radius = radius_client_init(hapd, conf->radius);
+		if (!hapd->radius) {
+			wpa_printf(MSG_ERROR,
+				   "RADIUS client initialization failed.");
 			return -1;
 		}
+
+		if (conf->radius_das_port) {
+			struct radius_das_conf das_conf;
+
+			os_memset(&das_conf, 0, sizeof(das_conf));
+			das_conf.port = conf->radius_das_port;
+			das_conf.shared_secret = conf->radius_das_shared_secret;
+			das_conf.shared_secret_len =
+				conf->radius_das_shared_secret_len;
+			das_conf.client_addr = &conf->radius_das_client_addr;
+			das_conf.time_window = conf->radius_das_time_window;
+			das_conf.require_event_timestamp =
+				conf->radius_das_require_event_timestamp;
+			das_conf.require_message_authenticator =
+				conf->radius_das_require_message_authenticator;
+			das_conf.ctx = hapd;
+			das_conf.disconnect = hostapd_das_disconnect;
+			das_conf.coa = hostapd_das_coa;
+			hapd->radius_das = radius_das_init(&das_conf);
+			if (!hapd->radius_das) {
+				wpa_printf(MSG_ERROR,
+					   "RADIUS DAS initialization failed.");
+				return -1;
+			}
+		}
+	} else {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using RADIUS client of the first BSS");
+		hapd->radius = hapd->mld_first_bss->radius;
+		hapd->radius_das = hapd->mld_first_bss->radius_das;
 	}
 #endif /* CONFIG_NO_RADIUS */
 
@@ -1607,6 +1642,125 @@
 }
 
 
+/* When NO_IR flag is set and AP is stopped, clean up BSS parameters without
+ * deinitializing the driver and the control interfaces. A subsequent
+ * REG_CHANGE event can bring the AP back up.
+ */
+static void hostapd_no_ir_cleanup(struct hostapd_data *bss)
+{
+	hostapd_bss_deinit_no_free(bss);
+	hostapd_free_hapd_data(bss);
+	hostapd_cleanup_iface_partial(bss->iface);
+}
+
+
+static int hostapd_no_ir_channel_list_updated(struct hostapd_iface *iface,
+					      void *ctx)
+{
+	bool all_no_ir, is_6ghz;
+	int i, j;
+	struct hostapd_hw_modes *mode = NULL;
+
+	if (hostapd_get_hw_features(iface))
+		return 0;
+
+	all_no_ir = true;
+	is_6ghz = false;
+
+	for (i = 0; i < iface->num_hw_features; i++) {
+		mode = &iface->hw_features[i];
+
+		if (mode->mode == iface->conf->hw_mode) {
+			if (iface->freq > 0 &&
+			    !hw_mode_get_channel(mode, iface->freq, NULL)) {
+				mode = NULL;
+				continue;
+			}
+
+			for (j = 0; j < mode->num_channels; j++) {
+				if (!(mode->channels[j].flag &
+				      HOSTAPD_CHAN_NO_IR))
+					all_no_ir = false;
+
+				if (is_6ghz_freq(mode->channels[j].freq))
+					is_6ghz = true;
+			}
+			break;
+		}
+	}
+
+	if (!mode || !is_6ghz)
+		return 0;
+	iface->current_mode = mode;
+
+	if (iface->state == HAPD_IFACE_ENABLED) {
+		if (!all_no_ir) {
+			struct hostapd_channel_data *chan;
+
+			chan = hw_get_channel_freq(iface->current_mode->mode,
+						   iface->freq, NULL,
+						   iface->hw_features,
+						   iface->num_hw_features);
+
+			if (!chan) {
+				wpa_printf(MSG_ERROR,
+					   "NO_IR: Could not derive chan from freq");
+				return 0;
+			}
+
+			if (!(chan->flag & HOSTAPD_CHAN_NO_IR))
+				return 0;
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: The current channel has NO_IR flag now, stop AP.");
+		} else {
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: All chan in new chanlist are NO_IR, stop AP.");
+		}
+
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		iface->is_no_ir = true;
+		hostapd_drv_stop_ap(iface->bss[0]);
+		hostapd_no_ir_cleanup(iface->bss[0]);
+		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+	} else if (iface->state == HAPD_IFACE_NO_IR) {
+		if (all_no_ir) {
+			wpa_printf(MSG_DEBUG,
+				   "NO_IR: AP in NO_IR and all chan in the new chanlist are NO_IR. Ignore");
+			return 0;
+		}
+
+		if (!iface->conf->acs) {
+			struct hostapd_channel_data *chan;
+
+			chan = hw_get_channel_freq(iface->current_mode->mode,
+						   iface->freq, NULL,
+						   iface->hw_features,
+						   iface->num_hw_features);
+			if (!chan) {
+				wpa_printf(MSG_ERROR,
+					   "NO_IR: Could not derive chan from freq");
+				return 0;
+			}
+
+			/* If the last operating channel is NO_IR, trigger ACS.
+			 */
+			if (chan->flag & HOSTAPD_CHAN_NO_IR) {
+				iface->freq = 0;
+				iface->conf->channel = 0;
+				if (acs_init(iface) != HOSTAPD_CHAN_ACS)
+					wpa_printf(MSG_ERROR,
+						   "NO_IR: Could not start ACS");
+				return 0;
+			}
+		}
+
+		setup_interface2(iface);
+	}
+
+	return 0;
+}
+
+
 static void channel_list_update_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct hostapd_iface *iface = eloop_ctx;
@@ -1627,6 +1781,13 @@
 
 void hostapd_channel_list_updated(struct hostapd_iface *iface, int initiator)
 {
+	if (initiator == REGDOM_SET_BY_DRIVER) {
+		hostapd_for_each_interface(iface->interfaces,
+					   hostapd_no_ir_channel_list_updated,
+					   NULL);
+		return;
+	}
+
 	if (!iface->wait_channel_update || initiator != REGDOM_SET_BY_USER)
 		return;
 
@@ -1776,6 +1937,7 @@
 static int setup_interface2(struct hostapd_iface *iface)
 {
 	iface->wait_channel_update = 0;
+	iface->is_no_ir = false;
 
 	if (hostapd_get_hw_features(iface)) {
 		/* Not all drivers support this yet, so continue without hw
@@ -1831,6 +1993,14 @@
 	return hostapd_setup_interface_complete(iface, 0);
 
 fail:
+	if (iface->is_no_ir) {
+		/* If AP is in NO_IR state, it can be reenabled by the driver
+		 * regulatory update and EVENT_CHANNEL_LIST_CHANGED. */
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+		return 0;
+	}
+
 	hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 	wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 	if (iface->interfaces && iface->interfaces->terminate_on_error)
@@ -2317,10 +2487,20 @@
 	for (j = 0; j < iface->num_bss; j++)
 		hostapd_neighbor_set_own_report(iface->bss[j]);
 
+	if (iface->interfaces && iface->interfaces->count > 1)
+		ieee802_11_set_beacons(iface);
+
 	return 0;
 
 fail:
 	wpa_printf(MSG_ERROR, "Interface initialization failed");
+
+	if (iface->is_no_ir) {
+		hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+		return 0;
+	}
+
 	hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 	wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 #ifdef CONFIG_FST
@@ -2366,8 +2546,15 @@
 
 	if (err) {
 		wpa_printf(MSG_ERROR, "Interface initialization failed");
-		hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 		iface->need_to_start_in_sync = 0;
+
+		if (iface->is_no_ir) {
+			hostapd_set_state(iface, HAPD_IFACE_NO_IR);
+			wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_NO_IR);
+			return 0;
+		}
+
+		hostapd_set_state(iface, HAPD_IFACE_DISABLED);
 		wpa_msg(hapd->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 		if (interfaces && interfaces->terminate_on_error)
 			eloop_terminate();
@@ -2536,6 +2723,7 @@
 
 	eloop_cancel_timeout(channel_list_update_timeout, iface, NULL);
 	iface->wait_channel_update = 0;
+	iface->is_no_ir = false;
 
 #ifdef CONFIG_FST
 	if (iface->fst) {
@@ -2800,8 +2988,9 @@
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
 	if (driver && driver->hapd_deinit && drv_priv) {
-		driver->hapd_deinit(drv_priv);
-		iface->bss[0]->drv_priv = NULL;
+		if (!iface->bss[0]->mld_first_bss)
+			driver->hapd_deinit(drv_priv);
+		hostapd_clear_drv_priv(iface->bss[0]);
 	}
 	hostapd_interface_free(iface);
 }
@@ -2816,13 +3005,14 @@
 	wpa_printf(MSG_DEBUG, "%s: driver=%p drv_priv=%p -> hapd_deinit",
 		   __func__, driver, drv_priv);
 	if (driver && driver->hapd_deinit && drv_priv) {
-		driver->hapd_deinit(drv_priv);
+		if (!hapd_iface->bss[0]->mld_first_bss)
+			driver->hapd_deinit(drv_priv);
 		for (j = 0; j < hapd_iface->num_bss; j++) {
 			wpa_printf(MSG_DEBUG, "%s:bss[%d]->drv_priv=%p",
 				   __func__, (int) j,
 				   hapd_iface->bss[j]->drv_priv);
 			if (hapd_iface->bss[j]->drv_priv == drv_priv) {
-				hapd_iface->bss[j]->drv_priv = NULL;
+				hostapd_clear_drv_priv(hapd_iface->bss[j]);
 				hapd_iface->extended_capa = NULL;
 				hapd_iface->extended_capa_mask = NULL;
 				hapd_iface->extended_capa_len = 0;
@@ -3163,8 +3353,14 @@
 		conf_file = ptr + 7;
 
 	for (i = 0; i < interfaces->count; i++) {
+		bool mld_ap = false;
+
+#ifdef CONFIG_IEEE80211BE
+		mld_ap = interfaces->iface[i]->conf->bss[0]->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
+
 		if (!os_strcmp(interfaces->iface[i]->conf->bss[0]->iface,
-			       buf)) {
+			       buf) && !mld_ap) {
 			wpa_printf(MSG_INFO, "Cannot add interface - it "
 				   "already exists");
 			return -1;
@@ -3339,6 +3535,12 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	    sta->mld_assoc_link_id != hapd->mld_link_id)
+		return;
+#endif /* CONFIG_IEEE80211BE */
+
 	ap_sta_clear_disconnect_timeouts(hapd, sta);
 	sta->post_csa_sa_query = 0;
 
@@ -3371,8 +3573,12 @@
 		    sta->auth_alg != WLAN_AUTH_FILS_PK &&
 		    !(sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)))
 			wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH);
-	} else
+	} else if (!(hapd->iface->drv_flags2 &
+		     WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK)) {
+		/* The 4-way handshake offloaded case will have this handled
+		 * based on the port authorized event. */
 		wpa_auth_sta_associated(hapd->wpa_auth, sta->wpa_sm);
+	}
 
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_WIRED) {
 		if (eloop_cancel_timeout(ap_handle_timer, hapd, sta) > 0) {
@@ -3421,6 +3627,8 @@
 		return "DFS";
 	case HAPD_IFACE_ENABLED:
 		return "ENABLED";
+	case HAPD_IFACE_NO_IR:
+		return "NO_IR";
 	}
 
 	return "UNKNOWN";
@@ -3572,6 +3780,7 @@
 	if (!channel)
 		return -1;
 
+	hostapd_determine_mode(hapd->iface);
 	mode = hapd->iface->current_mode;
 
 	/* if a pointer to old_params is provided we save previous state */
@@ -4063,3 +4272,26 @@
 	}
 }
 #endif /* CONFIG_OCV */
+
+
+#ifdef CONFIG_IEEE80211BE
+struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
+					       u8 link_id)
+{
+	unsigned int i;
+
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		struct hostapd_iface *h = hapd->iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h->bss[0];
+		struct hostapd_bss_config *hconf = h_hapd->conf;
+
+		if (!hconf->mld_ap || hconf->mld_id != hapd->conf->mld_id)
+			continue;
+
+		if (h_hapd->mld_link_id == link_id)
+			return h_hapd;
+	}
+
+	return NULL;
+}
+#endif /* CONFIG_IEEE80211BE */
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index b9a67b9..7f703be 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -99,6 +99,7 @@
 	HOSTAPD_CHAN_VALID = 0, /* channel is ready */
 	HOSTAPD_CHAN_INVALID = 1, /* no usable channel found */
 	HOSTAPD_CHAN_ACS = 2, /* ACS work being performed */
+	HOSTAPD_CHAN_INVALID_NO_IR = 3, /* channel invalid due to AFC NO IR */
 };
 
 struct hostapd_probereq_cb {
@@ -174,6 +175,12 @@
 	unsigned int reenable_beacon:1;
 
 	u8 own_addr[ETH_ALEN];
+	u8 mld_addr[ETH_ALEN];
+	u8 mld_link_id;
+	/* Used for mld_link_id assignment - valid on the first MLD BSS only */
+	u8 mld_next_link_id;
+
+	struct hostapd_data *mld_first_bss;
 
 	int num_sta; /* number of entries in sta_list */
 	struct sta_info *sta_list; /* STA info list head */
@@ -482,6 +489,7 @@
 	HAPD_IFACE_ACS,
 	HAPD_IFACE_HT_SCAN,
 	HAPD_IFACE_DFS,
+	HAPD_IFACE_NO_IR,
 	HAPD_IFACE_ENABLED
 };
 
@@ -495,7 +503,8 @@
 	struct hostapd_config *conf;
 	char phy[16]; /* Name of the PHY (radio) */
 
-        enum hostapd_iface_state state;
+	enum hostapd_iface_state state;
+
 #ifdef CONFIG_MESH
 	struct mesh_conf *mconf;
 #endif /* CONFIG_MESH */
@@ -542,6 +551,8 @@
 	const u8 *extended_capa, *extended_capa_mask;
 	unsigned int extended_capa_len;
 
+	u16 mld_eml_capa, mld_mld_capa;
+
 	unsigned int drv_max_acl_mac_addrs;
 
 	struct hostapd_hw_modes *hw_features;
@@ -652,6 +663,9 @@
 
 	int (*enable_iface_cb)(struct hostapd_iface *iface);
 	int (*disable_iface_cb)(struct hostapd_iface *iface);
+
+	/* Configured freq of interface is NO_IR */
+	bool is_no_ir;
 };
 
 /* hostapd.c */
@@ -712,13 +726,15 @@
 					   const u8 *ie, size_t ie_len,
 					   int ssi_signal),
 				 void *ctx);
-void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr);
+void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr,
+				int mld_assoc_link_id);
 
 /* drv_callbacks.c (TODO: move to somewhere else?) */
 void hostapd_notify_assoc_fils_finish(struct hostapd_data *hapd,
 				      struct sta_info *sta);
 int hostapd_notif_assoc(struct hostapd_data *hapd, const u8 *addr,
-			const u8 *ie, size_t ielen, int reassoc);
+			const u8 *req_ie, size_t req_ielen, const u8 *resp_ie,
+			size_t resp_ielen, const u8 *link_addr, int reassoc);
 void hostapd_notif_disassoc(struct hostapd_data *hapd, const u8 *addr);
 void hostapd_event_sta_low_ack(struct hostapd_data *hapd, const u8 *addr);
 void hostapd_event_connect_failed_reason(struct hostapd_data *hapd,
@@ -753,5 +769,7 @@
 int hostapd_set_acl(struct hostapd_data *hapd);
 struct hostapd_data * hostapd_mbssid_get_tx_bss(struct hostapd_data *hapd);
 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);
 
 #endif /* HOSTAPD_H */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index f836be4..9edbb5a 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -79,6 +79,9 @@
 	u16 num_modes, flags;
 	struct hostapd_hw_modes *modes;
 	u8 dfs_domain;
+	enum hostapd_hw_mode mode = HOSTAPD_MODE_IEEE80211ANY;
+	bool is_6ghz = false;
+	bool orig_mode_valid = false;
 
 	if (hostapd_drv_none(hapd))
 		return -1;
@@ -95,6 +98,20 @@
 	iface->hw_flags = flags;
 	iface->dfs_domain = dfs_domain;
 
+	if (iface->current_mode) {
+		/*
+		 * Received driver event CHANNEL_LIST_CHANGED when the current
+		 * hw mode is valid. Clear iface->current_mode temporarily as
+		 * the mode instance will be replaced with a new instance and
+		 * the current pointer would be pointing to freed memory.
+		 */
+		orig_mode_valid = true;
+		mode = iface->current_mode->mode;
+		is_6ghz = mode == HOSTAPD_MODE_IEEE80211A &&
+			iface->current_mode->num_channels > 0 &&
+			is_6ghz_freq(iface->current_mode->channels[0].freq);
+		iface->current_mode = NULL;
+	}
 	hostapd_free_hw_features(iface->hw_features, iface->num_hw_features);
 	iface->hw_features = modes;
 	iface->num_hw_features = num_modes;
@@ -104,6 +121,12 @@
 		int dfs_enabled = hapd->iconf->ieee80211h &&
 			(iface->drv_flags & WPA_DRIVER_FLAGS_RADAR);
 
+		/* Restore orignal mode if possible */
+		if (orig_mode_valid && feature->mode == mode &&
+		    feature->num_channels > 0 &&
+		    is_6ghz == is_6ghz_freq(feature->channels[0].freq))
+			iface->current_mode = feature;
+
 		/* set flag for channels we can use in current regulatory
 		 * domain */
 		for (j = 0; j < feature->num_channels; j++) {
@@ -141,6 +164,12 @@
 		}
 	}
 
+	if (orig_mode_valid && !iface->current_mode) {
+		wpa_printf(MSG_ERROR,
+			   "%s: Could not update iface->current_mode",
+			   __func__);
+	}
+
 	return 0;
 }
 
@@ -794,6 +823,11 @@
 }
 
 
+/* Returns:
+ * 1 = usable
+ * 0 = not usable
+ * -1 = not currently usable due to 6 GHz NO-IR
+ */
 static int hostapd_is_usable_chan(struct hostapd_iface *iface,
 				  int frequency, int primary)
 {
@@ -817,6 +851,10 @@
 		   chan->flag,
 		   chan->flag & HOSTAPD_CHAN_NO_IR ? " NO-IR" : "",
 		   chan->flag & HOSTAPD_CHAN_RADAR ? " RADAR" : "");
+
+	if (is_6ghz_freq(chan->freq) && (chan->flag & HOSTAPD_CHAN_NO_IR))
+		return -1;
+
 	return 0;
 }
 
@@ -826,6 +864,7 @@
 	int i, contiguous = 0;
 	int num_of_enabled = 0;
 	int max_contiguous = 0;
+	int err;
 	struct ieee80211_edmg_config edmg;
 	struct hostapd_channel_data *pri_chan;
 
@@ -865,8 +904,9 @@
 		if (num_of_enabled > 4)
 			return 0;
 
-		if (!hostapd_is_usable_chan(iface, freq, 1))
-			return 0;
+		err = hostapd_is_usable_chan(iface, freq, 1);
+		if (err <= 0)
+			return err;
 
 		if (contiguous > max_contiguous)
 			max_contiguous = contiguous;
@@ -897,7 +937,8 @@
 {
 #ifdef CONFIG_IEEE80211BE
 	struct hostapd_config *conf = iface->conf;
-	u8 bw, start_chan;
+	u16 bw;
+	u8 start_chan;
 
 	if (!conf->punct_bitmap)
 		return true;
@@ -914,21 +955,30 @@
 		return false;
 	}
 
-	switch (conf->eht_oper_chwidth) {
-	case 0:
-		wpa_printf(MSG_ERROR,
-			   "RU puncturing is supported only in 80 MHz and 160 MHz");
-		return false;
-	case 1:
-		bw = 80;
-		start_chan = conf->eht_oper_centr_freq_seg0_idx - 6;
-		break;
-	case 2:
-		bw = 160;
-		start_chan = conf->eht_oper_centr_freq_seg0_idx - 14;
-		break;
-	default:
-		return false;
+	/*
+	 * In the 6 GHz band, eht_oper_chwidth is ignored. Use operating class
+	 * to determine channel width.
+	 */
+	if (conf->op_class == 137) {
+		bw = 320;
+		start_chan = conf->eht_oper_centr_freq_seg0_idx - 30;
+	} else {
+		switch (conf->eht_oper_chwidth) {
+		case 0:
+			wpa_printf(MSG_ERROR,
+				   "RU puncturing is supported only in 80 MHz and 160 MHz");
+			return false;
+		case 1:
+			bw = 80;
+			start_chan = conf->eht_oper_centr_freq_seg0_idx - 6;
+			break;
+		case 2:
+			bw = 160;
+			start_chan = conf->eht_oper_centr_freq_seg0_idx - 14;
+			break;
+		default:
+			return false;
+		}
 	}
 
 	if (!is_punct_bitmap_valid(bw, (conf->channel - start_chan) / 4,
@@ -942,10 +992,16 @@
 }
 
 
+/* Returns:
+ * 1 = usable
+ * 0 = not usable
+ * -1 = not currently usable due to 6 GHz NO-IR
+ */
 static int hostapd_is_usable_chans(struct hostapd_iface *iface)
 {
 	int secondary_freq;
 	struct hostapd_channel_data *pri_chan;
+	int err;
 
 	if (!iface->current_mode)
 		return 0;
@@ -957,12 +1013,15 @@
 		wpa_printf(MSG_ERROR, "Primary frequency not present");
 		return 0;
 	}
-	if (!hostapd_is_usable_chan(iface, pri_chan->freq, 1)) {
+
+	err = hostapd_is_usable_chan(iface, pri_chan->freq, 1);
+	if (err <= 0) {
 		wpa_printf(MSG_ERROR, "Primary frequency not allowed");
-		return 0;
+		return err;
 	}
-	if (!hostapd_is_usable_edmg(iface))
-		return 0;
+	err = hostapd_is_usable_edmg(iface);
+	if (err <= 0)
+		return err;
 
 	if (!hostapd_is_usable_punct_bitmap(iface))
 		return 0;
@@ -970,8 +1029,9 @@
 	if (!iface->conf->secondary_channel)
 		return 1;
 
-	if (hostapd_is_usable_chan(iface, iface->freq +
-				   iface->conf->secondary_channel * 20, 0)) {
+	err = hostapd_is_usable_chan(iface, iface->freq +
+				     iface->conf->secondary_channel * 20, 0);
+	if (err > 0) {
 		if (iface->conf->secondary_channel == 1 &&
 		    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P))
 			return 1;
@@ -980,24 +1040,24 @@
 			return 1;
 	}
 	if (!iface->conf->ht40_plus_minus_allowed)
-		return 0;
+		return err;
 
 	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
 	secondary_freq = iface->freq + 20;
-	if (hostapd_is_usable_chan(iface, secondary_freq, 0) &&
-	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
+	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
 		iface->conf->secondary_channel = 1;
 		return 1;
 	}
 
 	secondary_freq = iface->freq - 20;
-	if (hostapd_is_usable_chan(iface, secondary_freq, 0) &&
-	    (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
+	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
 		iface->conf->secondary_channel = -1;
 		return 1;
 	}
 
-	return 0;
+	return err;
 }
 
 
@@ -1019,14 +1079,14 @@
 }
 
 
-static void hostapd_determine_mode(struct hostapd_iface *iface)
+int hostapd_determine_mode(struct hostapd_iface *iface)
 {
 	int i;
 	enum hostapd_hw_mode target_mode;
 
 	if (iface->current_mode ||
 	    iface->conf->hw_mode != HOSTAPD_MODE_IEEE80211ANY)
-		return;
+		return 0;
 
 	if (iface->freq < 4000)
 		target_mode = HOSTAPD_MODE_IEEE80211G;
@@ -1049,8 +1109,11 @@
 		}
 	}
 
-	if (!iface->current_mode)
-		wpa_printf(MSG_ERROR, "ACS: Cannot decide mode");
+	if (!iface->current_mode) {
+		wpa_printf(MSG_ERROR, "ACS/CSA: Cannot decide mode");
+		return -1;
+	}
+	return 0;
 }
 
 
@@ -1058,11 +1121,17 @@
 hostapd_check_chans(struct hostapd_iface *iface)
 {
 	if (iface->freq) {
+		int err;
+
 		hostapd_determine_mode(iface);
-		if (hostapd_is_usable_chans(iface))
-			return HOSTAPD_CHAN_VALID;
-		else
-			return HOSTAPD_CHAN_INVALID;
+
+		err = hostapd_is_usable_chans(iface);
+		if (err <= 0) {
+			if (!err)
+				return HOSTAPD_CHAN_INVALID;
+			return HOSTAPD_CHAN_INVALID_NO_IR;
+		}
+		return HOSTAPD_CHAN_VALID;
 	}
 
 	/*
@@ -1073,6 +1142,8 @@
 	switch (acs_init(iface)) {
 	case HOSTAPD_CHAN_ACS:
 		return HOSTAPD_CHAN_ACS;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		return HOSTAPD_CHAN_INVALID_NO_IR;
 	case HOSTAPD_CHAN_VALID:
 	case HOSTAPD_CHAN_INVALID:
 	default:
@@ -1112,6 +1183,7 @@
 
 	switch (hostapd_check_chans(iface)) {
 	case HOSTAPD_CHAN_VALID:
+		iface->is_no_ir = false;
 		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO,
 			ACS_EVENT_COMPLETED "freq=%d channel=%d",
 			iface->freq, iface->conf->channel);
@@ -1121,6 +1193,9 @@
 		wpa_msg(iface->bss[0]->msg_ctx, MSG_INFO, ACS_EVENT_FAILED);
 		hostapd_notify_bad_chans(iface);
 		goto out;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		iface->is_no_ir = true;
+		/* fall through */
 	case HOSTAPD_CHAN_INVALID:
 	default:
 		wpa_printf(MSG_ERROR, "ACS picked unusable channels");
@@ -1144,6 +1219,25 @@
 
 
 /**
+ * hostapd_csa_update_hwmode - Update hardware mode
+ * @iface: Pointer to interface data.
+ * Returns: 0 on success, < 0 on failure
+ *
+ * Update hardware mode when the operating channel changed because of CSA.
+ */
+int hostapd_csa_update_hwmode(struct hostapd_iface *iface)
+{
+	if (!iface || !iface->conf)
+		return -1;
+
+	iface->current_mode = NULL;
+	iface->conf->hw_mode = HOSTAPD_MODE_IEEE80211ANY;
+
+	return hostapd_determine_mode(iface);
+}
+
+
+/**
  * hostapd_select_hw_mode - Select the hardware mode
  * @iface: Pointer to interface data.
  * Returns: 0 on success, < 0 on failure
@@ -1206,9 +1300,13 @@
 
 	switch (hostapd_check_chans(iface)) {
 	case HOSTAPD_CHAN_VALID:
+		iface->is_no_ir = false;
 		return 0;
 	case HOSTAPD_CHAN_ACS: /* ACS will run and later complete */
 		return 1;
+	case HOSTAPD_CHAN_INVALID_NO_IR:
+		iface->is_no_ir = true;
+		/* fall through */
 	case HOSTAPD_CHAN_INVALID:
 	default:
 		hostapd_notify_bad_chans(iface);
diff --git a/src/ap/hw_features.h b/src/ap/hw_features.h
index ad0ddf7..c682c6d 100644
--- a/src/ap/hw_features.h
+++ b/src/ap/hw_features.h
@@ -15,6 +15,7 @@
 void hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
 			      size_t num_hw_features);
 int hostapd_get_hw_features(struct hostapd_iface *iface);
+int hostapd_csa_update_hwmode(struct hostapd_iface *iface);
 int hostapd_acs_completed(struct hostapd_iface *iface, int err);
 int hostapd_select_hw_mode(struct hostapd_iface *iface);
 const char * hostapd_hw_mode_txt(int mode);
@@ -28,6 +29,7 @@
 void hostapd_stop_setup_timers(struct hostapd_iface *iface);
 int hostapd_hw_skip_mode(struct hostapd_iface *iface,
 			 struct hostapd_hw_modes *mode);
+int hostapd_determine_mode(struct hostapd_iface *iface);
 #else /* NEED_AP_MLME */
 static inline void
 hostapd_free_hw_features(struct hostapd_hw_modes *hw_features,
@@ -40,6 +42,11 @@
 	return -1;
 }
 
+static inline int hostapd_csa_update_hwmode(struct hostapd_iface *iface)
+{
+	return 0;
+}
+
 static inline int hostapd_acs_completed(struct hostapd_iface *iface, int err)
 {
 	return -1;
@@ -91,6 +98,11 @@
 	return 0;
 }
 
+static inline int hostapd_determine_mode(struct hostapd_iface *iface)
+{
+	return 0;
+}
+
 #endif /* NEED_AP_MLME */
 
 #endif /* HW_FEATURES_H */
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 93a6b4f..1f39107 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -83,6 +83,8 @@
 static void handle_auth(struct hostapd_data *hapd,
 			const struct ieee80211_mgmt *mgmt, size_t len,
 			int rssi, int from_queue);
+static int add_associated_sta(struct hostapd_data *hapd,
+			      struct sta_info *sta, int reassoc);
 
 
 u8 * hostapd_eid_multi_ap(struct hostapd_data *hapd, u8 *eid)
@@ -396,17 +398,38 @@
 	u8 *buf;
 	size_t rlen;
 	int reply_res = WLAN_STATUS_UNSPECIFIED_FAILURE;
+	const u8 *sa = hapd->own_addr;
+	struct wpabuf *ml_resp = NULL;
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Once a non-AP MLD is added to the driver, the addressing should use
+	 * 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) {
+		sa = hapd->mld_addr;
+
+		ml_resp = hostapd_ml_auth_resp(hapd);
+		if (!ml_resp)
+			return -1;
+	}
+#endif /* CONFIG_IEEE80211BE */
 
 	rlen = IEEE80211_HDRLEN + sizeof(reply->u.auth) + ies_len;
+	if (ml_resp)
+		rlen += wpabuf_len(ml_resp);
 	buf = os_zalloc(rlen);
-	if (buf == NULL)
+	if (!buf) {
+		wpabuf_free(ml_resp);
 		return -1;
+	}
 
 	reply = (struct ieee80211_mgmt *) buf;
 	reply->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					    WLAN_FC_STYPE_AUTH);
 	os_memcpy(reply->da, dst, ETH_ALEN);
-	os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN);
+	os_memcpy(reply->sa, sa, ETH_ALEN);
 	os_memcpy(reply->bssid, bssid, ETH_ALEN);
 
 	reply->u.auth.auth_alg = host_to_le16(auth_alg);
@@ -416,6 +439,14 @@
 	if (ies && ies_len)
 		os_memcpy(reply->u.auth.variable, ies, ies_len);
 
+#ifdef CONFIG_IEEE80211BE
+	if (ml_resp)
+		os_memcpy(reply->u.auth.variable + ies_len,
+			  wpabuf_head(ml_resp), wpabuf_len(ml_resp));
+
+	wpabuf_free(ml_resp);
+#endif /* CONFIG_IEEE80211BE */
+
 	wpa_printf(MSG_DEBUG, "authentication reply: STA=" MACSTR
 		   " auth_alg=%d auth_transaction=%d resp=%d (IE len=%lu) (dbg=%s)",
 		   MAC2STR(dst), auth_alg, auth_transaction,
@@ -509,12 +540,12 @@
 }
 
 
-static const char * sae_get_password(struct hostapd_data *hapd,
-				     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)
+const char * sae_get_password(struct hostapd_data *hapd,
+			      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)
 {
 	const char *password = NULL;
 	struct sae_password_entry *pw;
@@ -524,7 +555,8 @@
 
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
-		    os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0)
+		    (!sta ||
+		     os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0))
 			continue;
 		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
 			continue;
@@ -542,7 +574,7 @@
 		pt = hapd->conf->ssid.pt;
 	}
 
-	if (!password) {
+	if (!password && sta) {
 		for (psk = sta->psk; psk; psk = psk->next) {
 			if (psk->is_passphrase) {
 				password = psk->passphrase;
@@ -573,12 +605,18 @@
 	int use_pt = 0;
 	struct sae_pt *pt = NULL;
 	const struct sae_pk *pk = NULL;
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta)
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (sta->sae->tmp) {
 		rx_id = sta->sae->tmp->pw_id;
 		use_pt = sta->sae->h2e;
 #ifdef CONFIG_SAE_PK
-		os_memcpy(sta->sae->tmp->own_addr, hapd->own_addr, ETH_ALEN);
+		os_memcpy(sta->sae->tmp->own_addr, own_addr, ETH_ALEN);
 		os_memcpy(sta->sae->tmp->peer_addr, sta->addr, ETH_ALEN);
 #endif /* CONFIG_SAE_PK */
 	}
@@ -598,12 +636,12 @@
 	}
 
 	if (update && use_pt &&
-	    sae_prepare_commit_pt(sta->sae, pt, hapd->own_addr, sta->addr,
+	    sae_prepare_commit_pt(sta->sae, pt, own_addr, sta->addr,
 				  NULL, pk) < 0)
 		return NULL;
 
 	if (update && !use_pt &&
-	    sae_prepare_commit(hapd->own_addr, sta->addr,
+	    sae_prepare_commit(own_addr, sta->addr,
 			       (u8 *) password, os_strlen(password),
 			       sta->sae) < 0) {
 		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
@@ -837,7 +875,15 @@
 
 	os_memset(&params, 0, sizeof(params));
 	params.status = status;
-	params.bssid = sta->addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (sta->mld_info.mld_sta)
+		params.bssid =
+			sta->mld_info.links[sta->mld_assoc_link_id].peer_addr;
+#endif /* CONFIG_IEEE80211BE */
+	if (!params.bssid)
+		params.bssid = sta->addr;
+
 	if (status == WLAN_STATUS_SUCCESS && sta->sae &&
 	    !hapd->conf->disable_pmksa_caching)
 		params.pmkid = sta->sae->pmkid;
@@ -2748,6 +2794,8 @@
 	size_t resp_ies_len = 0;
 	u16 seq_ctrl;
 	struct radius_sta rad_info;
+	const u8 *dst, *sa, *bssid;
+	bool mld_sta = false;
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.auth)) {
 		wpa_printf(MSG_INFO, "handle_auth - too short payload (len=%lu)",
@@ -2765,6 +2813,20 @@
 	}
 #endif /* CONFIG_TESTING_OPTIONS */
 
+	sa = mgmt->sa;
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * Handle MLO authentication before the station is added to hostapd and
+	 * the driver so that the station MLD MAC address would be used in both
+	 * hostapd and the driver.
+	 */
+	sa = hostapd_process_ml_auth(hapd, mgmt, len);
+	if (sa)
+		mld_sta = true;
+	else
+		sa = mgmt->sa;
+#endif /* CONFIG_IEEE80211BE */
+
 	auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
 	auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
 	status_code = le_to_host16(mgmt->u.auth.status_code);
@@ -2780,7 +2842,7 @@
 	wpa_printf(MSG_DEBUG, "authentication: STA=" MACSTR " auth_alg=%d "
 		   "auth_transaction=%d status_code=%d wep=%d%s "
 		   "seq_ctrl=0x%x%s%s",
-		   MAC2STR(mgmt->sa), auth_alg, auth_transaction,
+		   MAC2STR(sa), auth_alg, auth_transaction,
 		   status_code, !!(fc & WLAN_FC_ISWEP),
 		   challenge ? " challenge" : "",
 		   seq_ctrl, (fc & WLAN_FC_RETRY) ? " retry" : "",
@@ -2846,7 +2908,17 @@
 
 	if (os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
 		wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate",
-			   MAC2STR(mgmt->sa));
+			   MAC2STR(sa));
+		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto fail;
+	}
+
+	if (mld_sta &&
+	    (os_memcmp(sa, hapd->own_addr, ETH_ALEN) == 0 ||
+	     os_memcmp(sa, hapd->mld_addr, ETH_ALEN) == 0)) {
+		wpa_printf(MSG_INFO,
+			   "Station " MACSTR " not allowed to authenticate",
+			   MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto fail;
 	}
@@ -2854,7 +2926,7 @@
 	if (hapd->conf->no_auth_if_seen_on) {
 		struct hostapd_data *other;
 
-		other = sta_track_seen_on(hapd->iface, mgmt->sa,
+		other = sta_track_seen_on(hapd->iface, sa,
 					  hapd->conf->no_auth_if_seen_on);
 		if (other) {
 			u8 *pos;
@@ -2863,7 +2935,7 @@
 
 			wpa_printf(MSG_DEBUG, "%s: Reject authentication from "
 				   MACSTR " since STA has been seen on %s",
-				   hapd->conf->iface, MAC2STR(mgmt->sa),
+				   hapd->conf->iface, MAC2STR(sa),
 				   hapd->conf->no_auth_if_seen_on);
 
 			resp = WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION;
@@ -2906,12 +2978,12 @@
 		}
 	}
 
-	res = ieee802_11_allowed_address(hapd, mgmt->sa, (const u8 *) mgmt, len,
+	res = ieee802_11_allowed_address(hapd, sa, (const u8 *) mgmt, len,
 					 &rad_info);
 	if (res == HOSTAPD_ACL_REJECT) {
 		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
 			"Ignore Authentication frame from " MACSTR
-			" due to ACL reject", MAC2STR(mgmt->sa));
+			" due to ACL reject", MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto fail;
 	}
@@ -2921,7 +2993,7 @@
 #ifdef CONFIG_SAE
 	if (auth_alg == WLAN_AUTH_SAE && !from_queue &&
 	    (auth_transaction == 1 ||
-	     (auth_transaction == 2 && auth_sae_queued_addr(hapd, mgmt->sa)))) {
+	     (auth_transaction == 2 && auth_sae_queued_addr(hapd, sa)))) {
 		/* Handle SAE Authentication commit message through a queue to
 		 * provide more control for postponing the needed heavy
 		 * processing under a possible DoS attack scenario. In addition,
@@ -2934,7 +3006,7 @@
 	}
 #endif /* CONFIG_SAE */
 
-	sta = ap_get_sta(hapd, mgmt->sa);
+	sta = ap_get_sta(hapd, sa);
 	if (sta) {
 		sta->flags &= ~WLAN_STA_PENDING_FILS_ERP;
 		sta->ft_over_ds = 0;
@@ -2954,7 +3026,7 @@
 		    sta->plink_state == PLINK_BLOCKED) {
 			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
 				   " is blocked - drop Authentication frame",
-				   MAC2STR(mgmt->sa));
+				   MAC2STR(sa));
 			return;
 		}
 #endif /* CONFIG_MESH */
@@ -2974,7 +3046,7 @@
 			 */
 			wpa_printf(MSG_DEBUG, "Mesh peer " MACSTR
 				   " not yet known - drop Authentication frame",
-				   MAC2STR(mgmt->sa));
+				   MAC2STR(sa));
 			/*
 			 * Save a copy of the frame so that it can be processed
 			 * if a new peer entry is added shortly after this.
@@ -2986,13 +3058,38 @@
 		}
 #endif /* CONFIG_MESH */
 
-		sta = ap_sta_add(hapd, mgmt->sa);
+		sta = ap_sta_add(hapd, sa);
 		if (!sta) {
 			wpa_printf(MSG_DEBUG, "ap_sta_add() failed");
 			resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 			goto fail;
 		}
 	}
+
+#ifdef CONFIG_IEEE80211BE
+	if (auth_transaction == 1) {
+		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;
+			sta->mld_assoc_link_id = link_id;
+
+			/*
+			 * Set the MLD address as the station address and the
+			 * station addresses.
+			 */
+			os_memcpy(sta->mld_info.common_info.mld_addr, sa,
+				  ETH_ALEN);
+			os_memcpy(sta->mld_info.links[link_id].peer_addr,
+				  mgmt->sa, ETH_ALEN);
+			os_memcpy(sta->mld_info.links[link_id].local_addr,
+				  hapd->own_addr, ETH_ALEN);
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->last_seq_ctrl = seq_ctrl;
 	sta->last_subtype = WLAN_FC_STYPE_AUTH;
 #ifdef CONFIG_MBO
@@ -3130,7 +3227,22 @@
 	}
 
  fail:
-	reply_res = send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, auth_alg,
+	dst = mgmt->sa;
+	bssid = mgmt->bssid;
+
+#ifdef CONFIG_IEEE80211BE
+	 /*
+	  * Once a non-AP MLD is added to the driver, the addressing should use
+	  * 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) {
+		dst = sta->addr;
+		bssid = hapd->mld_addr;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	reply_res = send_auth_reply(hapd, sta, dst, bssid, auth_alg,
 				    auth_alg == WLAN_AUTH_SAE ?
 				    auth_transaction : auth_transaction + 1,
 				    resp, resp_ies, resp_ies_len,
@@ -3161,10 +3273,51 @@
 }
 
 
+static u32 hostapd_get_aid_word(struct hostapd_data *hapd,
+				struct sta_info *sta, int i)
+{
+#ifdef CONFIG_IEEE80211BE
+	u32 aid_word = 0;
+
+	/* 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) {
+		int j;
+
+		for (j = 0; j < MAX_NUM_MLD_LINKS; j++) {
+			struct hostapd_data *link_bss;
+
+			if (!sta->mld_info.links[j].valid)
+				continue;
+
+			link_bss = hostapd_mld_get_link_bss(hapd, j);
+			if (!link_bss) {
+				/* This shouldn't happen, just skip */
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link BSS for AID");
+				continue;
+			}
+
+			aid_word |= link_bss->sta_aid[i];
+		}
+
+		return aid_word;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return hapd->sta_aid[i];
+}
+
+
 int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	int i, j = 32, aid;
 
+	/* Transmitted and non-transmitted BSSIDs share the same AID pool, so
+	 * use the shared storage in the transmitted BSS to find the next
+	 * available value. */
+	hapd = hostapd_mbssid_get_tx_bss(hapd);
+
 	/* get a unique AID */
 	if (sta->aid > 0) {
 		wpa_printf(MSG_DEBUG, "  old AID %d", sta->aid);
@@ -3175,10 +3328,12 @@
 		return -1;
 
 	for (i = 0; i < AID_WORDS; i++) {
-		if (hapd->sta_aid[i] == (u32) -1)
+		u32 aid_word = hostapd_get_aid_word(hapd, sta, i);
+
+		if (aid_word == (u32) -1)
 			continue;
 		for (j = 0; j < 32; j++) {
-			if (!(hapd->sta_aid[i] & BIT(j)))
+			if (!(aid_word & BIT(j)))
 				break;
 		}
 		if (j < 32)
@@ -3548,7 +3703,8 @@
 u16 owe_process_rsn_ie(struct hostapd_data *hapd,
 		       struct sta_info *sta,
 		       const u8 *rsn_ie, size_t rsn_ie_len,
-		       const u8 *owe_dh, size_t owe_dh_len)
+		       const u8 *owe_dh, size_t owe_dh_len,
+		       const u8 *link_addr)
 {
 	u16 status;
 	u8 *owe_buf, ie[256 * 2];
@@ -3570,6 +3726,11 @@
 		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
 		goto end;
 	}
+#ifdef CONFIG_IEEE80211BE
+	if (sta->mld_info.mld_sta)
+		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
+				     sta->mld_assoc_link_id, &sta->mld_info);
+#endif /* CONFIG_IEEE80211BE */
 	rsn_ie -= 2;
 	rsn_ie_len += 2;
 	res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
@@ -3614,8 +3775,9 @@
 end:
 	wpa_printf(MSG_DEBUG, "OWE: Update status %d, ie len %d for peer "
 			      MACSTR, status, (unsigned int) ie_len,
-			      MAC2STR(sta->addr));
-	hostapd_drv_update_dh_ie(hapd, sta->addr, status,
+			      MAC2STR(link_addr ? link_addr : sta->addr));
+	hostapd_drv_update_dh_ie(hapd, link_addr ? link_addr : sta->addr,
+				 status,
 				 status == WLAN_STATUS_SUCCESS ? ie : NULL,
 				 ie_len);
 
@@ -3655,7 +3817,8 @@
 
 static int __check_assoc_ies(struct hostapd_data *hapd, struct sta_info *sta,
 			     const u8 *ies, size_t ies_len,
-			     struct ieee802_11_elems *elems, int reassoc)
+			     struct ieee802_11_elems *elems, int reassoc,
+			     bool link)
 {
 	int resp;
 	const u8 *wpa_ie;
@@ -3757,6 +3920,12 @@
 					  elems->eht_capabilities_len);
 		if (resp != WLAN_STATUS_SUCCESS)
 			return resp;
+
+		if (!link) {
+			resp = hostapd_process_ml_assoc_req(hapd, elems, sta);
+			if (resp != WLAN_STATUS_SUCCESS)
+				return resp;
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -3790,8 +3959,6 @@
 	if (hapd->conf->wps_state && elems->wps_ie && ies && ies_len) {
 		wpa_printf(MSG_DEBUG, "STA included WPS IE in (Re)Association "
 			   "Request - assume WPS is used");
-		if (check_sa_query(hapd, sta, reassoc))
-			return WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
 		sta->flags |= WLAN_STA_WPS;
 		wpabuf_free(sta->wps_ie);
 		sta->wps_ie = ieee802_11_vendor_ie_concat(ies, ies_len,
@@ -3823,20 +3990,36 @@
 	if (hapd->conf->wpa && wpa_ie) {
 		enum wpa_validate_result res;
 
-		if (check_sa_query(hapd, sta, reassoc))
-			return WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
-
 		wpa_ie -= 2;
 		wpa_ie_len += 2;
-		if (sta->wpa_sm == NULL)
+
+		if (!sta->wpa_sm) {
+#ifdef CONFIG_IEEE80211BE
+			struct mld_info *info = &sta->mld_info;
+#endif /* CONFIG_IEEE80211BE */
+
 			sta->wpa_sm = wpa_auth_sta_init(hapd->wpa_auth,
 							sta->addr,
 							p2p_dev_addr);
-		if (sta->wpa_sm == NULL) {
-			wpa_printf(MSG_WARNING, "Failed to initialize WPA "
-				   "state machine");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+
+			if (!sta->wpa_sm) {
+				wpa_printf(MSG_WARNING,
+					   "Failed to initialize RSN state machine");
+				return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			}
+
+#ifdef CONFIG_IEEE80211BE
+			if (info->mld_sta) {
+				wpa_printf(MSG_DEBUG,
+					   "MLD: Set ML info in RSN Authenticator");
+				wpa_auth_set_ml_info(sta->wpa_sm,
+						     hapd->mld_addr,
+						     sta->mld_assoc_link_id,
+						     info);
+			}
+#endif /* CONFIG_IEEE80211BE */
 		}
+
 		wpa_auth_set_auth_alg(sta->wpa_sm, sta->auth_alg);
 		res = wpa_validate_wpa_ie(hapd->wpa_auth, sta->wpa_sm,
 					  hapd->iface->freq,
@@ -3873,6 +4056,8 @@
 		}
 #endif /* CONFIG_IEEE80211R_AP */
 
+		if (link)
+			goto skip_sae_owe;
 #ifdef CONFIG_SAE
 		if (wpa_auth_uses_sae(sta->wpa_sm) && sta->sae &&
 		    sta->sae->state == SAE_ACCEPTED)
@@ -3922,6 +4107,7 @@
 				return resp;
 		}
 #endif /* CONFIG_OWE */
+	skip_sae_owe:
 
 #ifdef CONFIG_DPP2
 		dpp_pfs_free(sta->dpp_pfs);
@@ -4116,7 +4302,256 @@
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
 
-	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc);
+	return __check_assoc_ies(hapd, sta, ies, ies_len, &elems, reassoc,
+				 false);
+}
+
+
+#ifdef CONFIG_IEEE80211BE
+
+static size_t ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+					    u16 status_code,
+					    u8 *buf, size_t buflen)
+{
+	u8 *p = buf;
+
+	/* Capability Info */
+	WPA_PUT_LE16(p, hostapd_own_capab_info(hapd));
+	p += 2;
+
+	/* Status Code */
+	WPA_PUT_LE16(p, status_code);
+	p += 2;
+
+	if (status_code != WLAN_STATUS_SUCCESS)
+		return p - buf;
+
+	/* AID is not included */
+	p = hostapd_eid_supp_rates(hapd, p);
+	p = hostapd_eid_ext_supp_rates(hapd, p);
+	p = hostapd_eid_rm_enabled_capab(hapd, p, buf + buflen - p);
+	p = hostapd_eid_ht_capabilities(hapd, p);
+	p = hostapd_eid_ht_operation(hapd, p);
+
+	if (hapd->iconf->ieee80211ac && !hapd->conf->disable_11ac) {
+		p = hostapd_eid_vht_capabilities(hapd, p, 0);
+		p = hostapd_eid_vht_operation(hapd, p);
+	}
+
+	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
+		p = hostapd_eid_he_capab(hapd, p, IEEE80211_MODE_AP);
+		p = hostapd_eid_he_operation(hapd, p);
+		p = hostapd_eid_spatial_reuse(hapd, p);
+		p = hostapd_eid_he_mu_edca_parameter_set(hapd, p);
+		p = hostapd_eid_he_6ghz_band_cap(hapd, p);
+		if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
+			p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP);
+			p = hostapd_eid_eht_operation(hapd, p);
+		}
+	}
+
+	p = hostapd_eid_ext_capab(hapd, p, false);
+	p = hostapd_eid_mbo(hapd, p, buf + buflen - p);
+	p = hostapd_eid_wmm(hapd, p);
+
+	if (hapd->conf->assocresp_elements &&
+	    (size_t) (buf + buflen - p) >=
+	    wpabuf_len(hapd->conf->assocresp_elements)) {
+		os_memcpy(p, wpabuf_head(hapd->conf->assocresp_elements),
+			  wpabuf_len(hapd->conf->assocresp_elements));
+		p += wpabuf_len(hapd->conf->assocresp_elements);
+	}
+
+	return p - buf;
+}
+
+
+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)
+{
+	struct ieee802_11_elems elems;
+	struct wpabuf *mlbuf = NULL;
+	struct sta_info *sta = NULL;
+	u16 status = WLAN_STATUS_SUCCESS;
+
+	wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR,
+		   hapd->mld_link_id, MAC2STR(link->peer_addr));
+
+	if (ieee802_11_parse_elems(ies, ies_len, &elems, 1) == ParseFailed) {
+		wpa_printf(MSG_DEBUG, "MLD: link: Element parsing failed");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
+	}
+
+	sta = ap_get_sta(hapd, origin_sta->addr);
+	if (sta) {
+		wpa_printf(MSG_INFO, "MLD: link: Station already exists");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		sta = NULL;
+		goto out;
+	}
+
+	sta = ap_sta_add(hapd, origin_sta->addr);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "MLD: link: ap_sta_add() failed");
+		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
+		goto out;
+	}
+
+	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlbuf)
+		goto out;
+
+	if (ieee802_11_parse_link_assoc_req(ies, ies_len, &elems, mlbuf,
+					    hapd->mld_link_id, true) ==
+	    ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: link: Failed to parse association request Multi-Link element");
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
+	}
+
+	sta->flags |= origin_sta->flags | WLAN_STA_ASSOC_REQ_OK;
+	status = __check_assoc_ies(hapd, sta, NULL, 0, &elems, reassoc, true);
+	if (status != WLAN_STATUS_SUCCESS) {
+		wpa_printf(MSG_DEBUG, "MLD: link: Element check failed");
+		goto out;
+	}
+
+	sta->mld_info.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));
+
+	/*
+	 * 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);
+	sta->wpa_sm = NULL;
+
+	/*
+	 * Do not initialize the EAPOL state machine.
+	 * TODO: Maybe it is needed?
+	 */
+	sta->eapol_sm = NULL;
+
+	wpa_printf(MSG_DEBUG, "MLD: link=%u, association OK (aid=%u)",
+		   hapd->mld_link_id, sta->aid);
+
+	/*
+	 * Get RSNE and RSNXE for the current BSS as they are required by the
+	 * Authenticator.
+	 */
+	link->rsne = hostapd_wpa_ie(hapd, WLAN_EID_RSN);
+	link->rsnxe = hostapd_wpa_ie(hapd, WLAN_EID_RSNX);
+
+	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC_REQ_OK;
+
+	/* TODO: What other processing is required? */
+
+	if (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);
+
+	link->resp_sta_profile_len =
+		ieee80211_ml_build_assoc_resp(hapd, link->status,
+					      link->resp_sta_profile,
+					      sizeof(link->resp_sta_profile));
+}
+
+
+bool hostapd_is_mld_ap(struct hostapd_data *hapd)
+{
+	if (!hapd->conf->mld_ap)
+		return false;
+
+	if (!hapd->iface || !hapd->iface->interfaces ||
+	    hapd->iface->interfaces->count <= 1)
+		return false;
+
+	return true;
+}
+
+#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)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, j;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	/*
+	 * This is not really needed, but make the interaction with the RSN
+	 * Authenticator more consistent
+	 */
+	sta->mld_info.links[hapd->mld_link_id].rsne =
+		hostapd_wpa_ie(hapd, WLAN_EID_RSN);
+	sta->mld_info.links[hapd->mld_link_id].rsnxe =
+		hostapd_wpa_ie(hapd, WLAN_EID_RSNX);
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		struct hostapd_iface *iface = NULL;
+		struct mld_link_info *link = &sta->mld_info.links[i];
+
+		if (!link->valid)
+			continue;
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			iface = hapd->iface->interfaces->iface[j];
+
+			if (hapd->iface == iface)
+				continue;
+
+			if (iface->bss[0]->conf->mld_ap &&
+			    hapd->conf->mld_id == iface->bss[0]->conf->mld_id &&
+			    i == iface->bss[0]->mld_link_id)
+				break;
+		}
+
+		if (!iface || j == hapd->iface->interfaces->count) {
+			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));
+		} else {
+			ieee80211_ml_process_link(iface->bss[0], sta, link,
+						  ies, ies_len, reassoc);
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
 }
 
 
@@ -4150,6 +4585,20 @@
 	struct ieee80211_he_capabilities he_cap;
 	struct ieee80211_eht_capabilities eht_cap;
 	int set = 1;
+	const u8 *mld_link_addr = NULL;
+	bool mld_link_sta = false;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_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;
+
+		if (hapd->mld_link_id != sta->mld_assoc_link_id)
+			set = 0;
+	}
+#endif /* CONFIG_IEEE80211BE */
 
 	/*
 	 * Remove the STA entry to ensure the STA PS state gets cleared and
@@ -4178,7 +4627,7 @@
 		   wpa_auth_sta_ft_tk_already_set(sta->wpa_sm),
 		   wpa_auth_sta_fils_tk_already_set(sta->wpa_sm));
 
-	if (!sta->added_unassoc &&
+	if (!mld_link_sta && !sta->added_unassoc &&
 	    (!(sta->flags & WLAN_STA_AUTHORIZED) ||
 	     (reassoc && sta->ft_over_ds && sta->auth_alg == WLAN_AUTH_FT) ||
 	     (!wpa_auth_sta_ft_tk_already_set(sta->wpa_sm) &&
@@ -4228,7 +4677,7 @@
 			    sta->he_6ghz_capab,
 			    sta->flags | WLAN_STA_ASSOC, sta->qosinfo,
 			    sta->vht_opmode, sta->p2p_ie ? 1 : 0,
-			    set)) {
+			    set, mld_link_addr, mld_link_sta)) {
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211, HOSTAPD_LEVEL_NOTICE,
 			       "Could not %s STA to kernel driver",
@@ -4259,6 +4708,7 @@
 	struct ieee80211_mgmt *reply;
 	u8 *p;
 	u16 res = WLAN_STATUS_SUCCESS;
+	const u8 *sa = hapd->own_addr;
 
 	buflen = sizeof(struct ieee80211_mgmt) + 1024;
 #ifdef CONFIG_FILS
@@ -4294,9 +4744,19 @@
 		IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 			     (reassoc ? WLAN_FC_STYPE_REASSOC_RESP :
 			      WLAN_FC_STYPE_ASSOC_RESP));
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * 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)
+		sa = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	os_memcpy(reply->da, addr, ETH_ALEN);
-	os_memcpy(reply->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(reply->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(reply->sa, sa, ETH_ALEN);
+	os_memcpy(reply->bssid, sa, ETH_ALEN);
 
 	send_len = IEEE80211_HDRLEN;
 	send_len += sizeof(reply->u.assoc_resp);
@@ -4432,6 +4892,8 @@
 
 #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_capab(hapd, p, IEEE80211_MODE_AP);
 		p = hostapd_eid_eht_operation(hapd, p);
 	}
@@ -4704,6 +5166,7 @@
 	int delay_assoc = 0;
 #endif /* CONFIG_FILS */
 	int omit_rsnxe = 0;
+	bool set_beacon = false;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -4879,6 +5342,11 @@
 	}
 #endif /* CONFIG_MBO */
 
+	if (hapd->conf->wpa && check_sa_query(hapd, sta, reassoc)) {
+		resp = WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY;
+		goto fail;
+	}
+
 	/*
 	 * sta->capability is used in check_assoc_ies() for RRM enabled
 	 * capability element.
@@ -4940,7 +5408,7 @@
 		sta->nonerp_set = 1;
 		hapd->iface->num_sta_non_erp++;
 		if (hapd->iface->num_sta_non_erp == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
 	if (!(sta->capability & WLAN_CAPABILITY_SHORT_SLOT_TIME) &&
@@ -4951,7 +5419,7 @@
 		    hapd->iface->current_mode->mode ==
 		    HOSTAPD_MODE_IEEE80211G &&
 		    hapd->iface->num_sta_no_short_slot_time == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
 	if (sta->capability & WLAN_CAPABILITY_SHORT_PREAMBLE)
@@ -4966,10 +5434,11 @@
 		if (hapd->iface->current_mode &&
 		    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211G
 		    && hapd->iface->num_sta_no_short_preamble == 1)
-			ieee802_11_set_beacons(hapd->iface);
+			set_beacon = true;
 	}
 
-	update_ht_state(hapd, sta);
+	if (update_ht_state(hapd, sta) > 0)
+		set_beacon = true;
 
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 		       HOSTAPD_LEVEL_DEBUG,
@@ -5008,6 +5477,9 @@
 	}
 #endif /* CONFIG_FILS */
 
+	if (set_beacon)
+		ieee802_11_set_beacons(hapd->iface);
+
  fail:
 
 	/*
@@ -5028,6 +5500,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 (resp == WLAN_STATUS_SUCCESS && sta &&
 	    add_associated_sta(hapd, sta, reassoc))
 		resp = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
@@ -5087,27 +5562,37 @@
 }
 
 
-static void handle_disassoc(struct hostapd_data *hapd,
-			    const struct ieee80211_mgmt *mgmt, size_t len)
+static void hostapd_deauth_sta(struct hostapd_data *hapd,
+			       struct sta_info *sta,
+			       const struct ieee80211_mgmt *mgmt)
 {
-	struct sta_info *sta;
+	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+		"deauthentication: STA=" MACSTR " reason_code=%d",
+		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code));
 
-	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.disassoc)) {
-		wpa_printf(MSG_INFO, "handle_disassoc - too short payload (len=%lu)",
-			   (unsigned long) len);
-		return;
-	}
+	ap_sta_set_authorized(hapd, sta, 0);
+	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
+	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
+			WLAN_STA_ASSOC_REQ_OK);
+	hostapd_set_sta_flags(hapd, sta);
+	wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH);
+	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
+		       HOSTAPD_LEVEL_DEBUG, "deauthenticated");
+	mlme_deauthenticate_indication(
+		hapd, sta, le_to_host16(mgmt->u.deauth.reason_code));
+	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
+	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
+	ap_free_sta(hapd, sta);
+}
 
-	wpa_printf(MSG_DEBUG, "disassocation: STA=" MACSTR " reason_code=%d",
-		   MAC2STR(mgmt->sa),
-		   le_to_host16(mgmt->u.disassoc.reason_code));
 
-	sta = ap_get_sta(hapd, mgmt->sa);
-	if (sta == NULL) {
-		wpa_printf(MSG_INFO, "Station " MACSTR " trying to disassociate, but it is not associated",
-			   MAC2STR(mgmt->sa));
-		return;
-	}
+static void hostapd_disassoc_sta(struct hostapd_data *hapd,
+				 struct sta_info *sta,
+				 const struct ieee80211_mgmt *mgmt)
+{
+	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+		"disassocation: STA=" MACSTR " reason_code=%d",
+		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.disassoc.reason_code));
 
 	ap_sta_set_authorized(hapd, sta, 0);
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
@@ -5152,45 +5637,173 @@
 }
 
 
+#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,
+					 bool disassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *assoc_hapd, *tmp_hapd;
+	struct sta_info *assoc_sta;
+	unsigned int i, link_id;
+
+	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);
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		for (i = 0; i < assoc_hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+
+			if (!assoc_sta->mld_info.links[link_id].valid)
+				continue;
+
+			tmp_hapd =
+				assoc_hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				/*
+				 * Remove the station on which the association
+				 * was done only after all other link stations
+				 * are removed. Since there is only a single
+				 * station per struct hostapd_hapd with the
+				 * same association link simply break out from
+				 * the loop.
+				 */
+				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)
+					hostapd_deauth_sta(tmp_hapd, tmp_sta,
+							   mgmt);
+				else
+					hostapd_disassoc_sta(tmp_hapd, tmp_sta,
+							     mgmt);
+				break;
+			}
+		}
+	}
+
+	/* Remove the station on which the association was performed. */
+	if (!disassoc)
+		hostapd_deauth_sta(assoc_hapd, assoc_sta, mgmt);
+	else
+		hostapd_disassoc_sta(assoc_hapd, assoc_sta, mgmt);
+
+	return true;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+static void handle_disassoc(struct hostapd_data *hapd,
+			    const struct ieee80211_mgmt *mgmt, size_t len)
+{
+	struct sta_info *sta;
+
+	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.disassoc)) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			   "handle_disassoc - too short payload (len=%lu)",
+			   (unsigned long) len);
+		return;
+	}
+
+	sta = ap_get_sta(hapd, mgmt->sa);
+	if (!sta) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR
+			" trying to disassociate, but it is not associated",
+			MAC2STR(mgmt->sa));
+		return;
+	}
+
+	if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, true))
+		return;
+
+	hostapd_disassoc_sta(hapd, sta, mgmt);
+}
+
+
 static void handle_deauth(struct hostapd_data *hapd,
 			  const struct ieee80211_mgmt *mgmt, size_t len)
 {
 	struct sta_info *sta;
 
 	if (len < IEEE80211_HDRLEN + sizeof(mgmt->u.deauth)) {
-		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "handle_deauth - too short "
-			"payload (len=%lu)", (unsigned long) len);
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
+			"handle_deauth - too short payload (len=%lu)",
+			(unsigned long) len);
 		return;
 	}
 
-	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "deauthentication: STA=" MACSTR
-		" reason_code=%d",
-		MAC2STR(mgmt->sa), le_to_host16(mgmt->u.deauth.reason_code));
-
 	/* Clear the PTKSA cache entries for PASN */
 	ptksa_cache_flush(hapd->ptksa, mgmt->sa, WPA_CIPHER_NONE);
 
 	sta = ap_get_sta(hapd, mgmt->sa);
-	if (sta == NULL) {
-		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR " trying "
-			"to deauthenticate, but it is not authenticated",
+	if (!sta) {
+		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "Station " MACSTR
+			" trying to deauthenticate, but it is not authenticated",
 			MAC2STR(mgmt->sa));
 		return;
 	}
 
-	ap_sta_set_authorized(hapd, sta, 0);
-	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
-	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
-			WLAN_STA_ASSOC_REQ_OK);
-	hostapd_set_sta_flags(hapd, sta);
-	wpa_auth_sm_event(sta->wpa_sm, WPA_DEAUTH);
-	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-		       HOSTAPD_LEVEL_DEBUG, "deauthenticated");
-	mlme_deauthenticate_indication(
-		hapd, sta, le_to_host16(mgmt->u.deauth.reason_code));
-	sta->acct_terminate_cause = RADIUS_ACCT_TERMINATE_CAUSE_USER_REQUEST;
-	ieee802_1x_notify_port_enabled(sta->eapol_sm, 0);
-	ap_free_sta(hapd, sta);
+	if (hostapd_ml_handle_disconnect(hapd, sta, mgmt, false))
+		return;
+
+	hostapd_deauth_sta(hapd, sta, mgmt);
 }
 
 
@@ -5495,6 +6108,10 @@
 #ifdef CONFIG_MESH
 	    !(hapd->conf->mesh & MESH_ENABLED) &&
 #endif /* CONFIG_MESH */
+#ifdef CONFIG_IEEE80211BE
+	    !(hapd->conf->mld_ap &&
+	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+#endif /* CONFIG_IEEE80211BE */
 	    os_memcmp(mgmt->bssid, hapd->own_addr, ETH_ALEN) != 0) {
 		wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address",
 			   MAC2STR(mgmt->bssid));
@@ -5514,6 +6131,10 @@
 
 	if ((!is_broadcast_ether_addr(mgmt->da) ||
 	     stype != WLAN_FC_STYPE_ACTION) &&
+#ifdef CONFIG_IEEE80211BE
+	    !(hapd->conf->mld_ap &&
+	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+#endif /* CONFIG_IEEE80211BE */
 	    os_memcmp(mgmt->da, hapd->own_addr, ETH_ALEN) != 0) {
 		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -5658,6 +6279,90 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static void ieee80211_ml_link_sta_assoc_cb(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct mld_link_info *link,
+					   bool ok)
+{
+	if (!ok) {
+		hostapd_logger(hapd, link->peer_addr, HOSTAPD_MODULE_IEEE80211,
+			       HOSTAPD_LEVEL_DEBUG,
+			       "did not acknowledge association response");
+		sta->flags &= ~WLAN_STA_ASSOC_REQ_OK;
+
+		/* The STA is added only in case of SUCCESS */
+		if (link->status == WLAN_STATUS_SUCCESS)
+			hostapd_drv_sta_remove(hapd, sta->addr);
+
+		return;
+	}
+
+	if (link->status != WLAN_STATUS_SUCCESS)
+		return;
+
+	sta->flags |= WLAN_STA_ASSOC;
+	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
+
+	if (!hapd->conf->ieee802_1x && !hapd->conf->wpa)
+		ap_sta_set_authorized(hapd, sta, 1);
+
+	hostapd_set_sta_flags(hapd, sta);
+
+	/*
+	 * TODOs:
+	 * - IEEE 802.1X port enablement is not needed as done on the station
+	 *     doing the connection.
+	 * - Not handling accounting
+	 * - Need to handle VLAN configuration
+	 */
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
+static void hostapd_ml_handle_assoc_cb(struct hostapd_data *hapd,
+				       struct sta_info *sta, bool ok)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, link_id;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &sta->mld_info.links[link_id];
+
+		if (!link->valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+			struct hostapd_data *tmp_hapd =
+				hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (tmp_hapd->conf->mld_ap ||
+			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				if (tmp_sta == sta ||
+				    tmp_sta->mld_assoc_link_id !=
+				    sta->mld_assoc_link_id ||
+				    tmp_sta->aid != sta->aid)
+					continue;
+
+				ieee80211_ml_link_sta_assoc_cb(tmp_hapd,
+							       tmp_sta, link,
+							       ok);
+				break;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
 static void handle_assoc_cb(struct hostapd_data *hapd,
 			    const struct ieee80211_mgmt *mgmt,
 			    size_t len, int reassoc, int ok)
@@ -5673,6 +6378,17 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_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,
+			   "%s: MLD: ignore on link station (%d != %d)",
+			   __func__, hapd->mld_link_id, sta->mld_assoc_link_id);
+		return;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_resp) :
 				      sizeof(mgmt->u.assoc_resp))) {
 		wpa_printf(MSG_INFO,
@@ -5696,11 +6412,11 @@
 		if (status == WLAN_STATUS_SUCCESS)
 			hostapd_drv_sta_remove(hapd, sta->addr);
 
-		return;
+		goto handle_ml;
 	}
 
 	if (status != WLAN_STATUS_SUCCESS)
-		return;
+		goto handle_ml;
 
 	/* Stop previous accounting session, if one is started, and allocate
 	 * new session id for the new session. */
@@ -5742,11 +6458,11 @@
 		 * interface selection is not going to change anymore.
 		 */
 		if (ap_sta_bind_vlan(hapd, sta) < 0)
-			return;
+			goto handle_ml;
 	} else if (sta->vlan_id) {
 		/* VLAN ID already set (e.g., by PMKSA caching), so bind STA */
 		if (ap_sta_bind_vlan(hapd, sta) < 0)
-			return;
+			goto handle_ml;
 	}
 
 	hostapd_set_sta_flags(hapd, sta);
@@ -5762,7 +6478,7 @@
 	/* WPS not supported on backhaul BSS. Disable 4addr mode on fronthaul */
 	if ((sta->flags & WLAN_STA_WDS) ||
 	    (sta->flags & WLAN_STA_MULTI_AP &&
-	     !(hapd->conf->multi_ap & FRONTHAUL_BSS) &&
+	     (hapd->conf->multi_ap & BACKHAUL_BSS) &&
 	     !(sta->flags & WLAN_STA_WPS))) {
 		int ret;
 		char ifname_wds[IFNAMSIZ + 1];
@@ -5814,6 +6530,9 @@
 		os_free(sta->pending_eapol_rx);
 		sta->pending_eapol_rx = NULL;
 	}
+
+handle_ml:
+	hostapd_ml_handle_assoc_cb(hapd, sta, ok);
 }
 
 
@@ -6343,7 +7062,7 @@
 
 u8 * hostapd_eid_wb_chsw_wrapper(struct hostapd_data *hapd, u8 *eid)
 {
-	u8 bw, chan1, chan2 = 0;
+	u8 bw, chan1 = 0, chan2 = 0;
 	int freq1;
 
 	if (!hapd->cs_freq_params.channel ||
@@ -6352,20 +7071,17 @@
 	     !hapd->cs_freq_params.eht_enabled))
 		return eid;
 
-	/* bandwidth: 0: 40, 1: 80, 2: 160, 3: 80+80, 4: 320 */
+	/* bandwidth: 0: 40, 1: 80, 160, 80+80, 4: 320 as per
+	 * IEEE P802.11-REVme/D4.0, 9.4.2.159 and Table 9-314. */
 	switch (hapd->cs_freq_params.bandwidth) {
 	case 40:
 		bw = 0;
 		break;
 	case 80:
-		/* check if it's 80+80 */
-		if (!hapd->cs_freq_params.center_freq2)
-			bw = 1;
-		else
-			bw = 3;
+		bw = 1;
 		break;
 	case 160:
-		bw = 2;
+		bw = 1;
 		break;
 	case 320:
 		bw = 4;
@@ -6392,6 +7108,21 @@
 	*eid++ = WLAN_EID_WIDE_BW_CHSWITCH;
 	*eid++ = 3; /* Length of Wide Bandwidth Channel Switch element */
 	*eid++ = bw; /* New Channel Width */
+	if (hapd->cs_freq_params.bandwidth == 160) {
+		/* Update the CCFS0 and CCFS1 values in the element based on
+		 * IEEE P802.11-REVme/D4.0, Table 9-314 */
+
+		/* CCFS1 - The channel center frequency index of the 160 MHz
+		 * channel. */
+		chan2 = chan1;
+
+		/* CCFS0 - The channel center frequency index of the 80 MHz
+		 * channel segment that contains the primary channel. */
+		if (hapd->cs_freq_params.channel < chan1)
+			chan1 -= 8;
+		else
+			chan1 += 8;
+	}
 	*eid++ = chan1; /* New Channel Center Frequency Segment 0 */
 	*eid++ = chan2; /* New Channel Center Frequency Segment 1 */
 
@@ -6443,12 +7174,19 @@
 	size_t total_len = 0, len = *current_len;
 	int tbtt_count = 0;
 	size_t i, start = 0;
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	while (start < hapd->iface->num_bss) {
 		if (!len ||
-		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) {
+		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255 ||
+		    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) {
 			len = RNR_HEADER_LEN;
 			total_len += RNR_HEADER_LEN;
+			tbtt_count = 0;
 		}
 
 		len += RNR_TBTT_HEADER_LEN;
@@ -6472,8 +7210,13 @@
 			    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
 				break;
 
-			len += RNR_TBTT_INFO_LEN;
-			total_len += RNR_TBTT_INFO_LEN;
+			if (!ap_mld) {
+				len += RNR_TBTT_INFO_LEN;
+				total_len += RNR_TBTT_INFO_LEN;
+			} else {
+				len += RNR_TBTT_INFO_MLD_LEN;
+				total_len += RNR_TBTT_INFO_MLD_LEN;
+			}
 			tbtt_count++;
 		}
 		start = i;
@@ -6528,8 +7271,8 @@
 }
 
 
-static size_t hostapd_eid_rnr_colocation_len(struct hostapd_data *hapd,
-					     size_t *current_len)
+static size_t hostapd_eid_rnr_multi_iface_len(struct hostapd_data *hapd,
+					      size_t *current_len)
 {
 	struct hostapd_iface *iface;
 	size_t len = 0;
@@ -6540,9 +7283,16 @@
 
 	for (i = 0; i < hapd->iface->interfaces->count; i++) {
 		iface = hapd->iface->interfaces->iface[i];
+		bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+			ap_mld = true;
+#endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
-		    !is_6ghz_op_class(iface->conf->op_class))
+		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
 		len += hostapd_eid_rnr_iface_len(iface->bss[0], hapd,
@@ -6557,6 +7307,11 @@
 {
 	size_t total_len = 0, current_len = 0;
 	enum colocation_mode mode = get_colocation_mode(hapd);
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	switch (type) {
 	case WLAN_FC_STYPE_BEACON:
@@ -6565,9 +7320,10 @@
 		/* fallthrough */
 
 	case WLAN_FC_STYPE_PROBE_RESP:
-		if (mode == COLOCATED_LOWER_BAND)
-			total_len += hostapd_eid_rnr_colocation_len(
-				hapd, &current_len);
+		if (mode == COLOCATED_LOWER_BAND || ap_mld)
+			total_len +=
+				hostapd_eid_rnr_multi_iface_len(hapd,
+								&current_len);
 
 		if (hapd->conf->rnr && hapd->iface->num_bss > 1 &&
 		    !hapd->iconf->mbssid)
@@ -6657,6 +7413,11 @@
 	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;
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (!(iface->drv_flags & WPA_DRIVER_FLAGS_AP_CSA) || !iface->freq)
 		return eid;
@@ -6670,7 +7431,8 @@
 
 	while (start < iface->num_bss) {
 		if (!len ||
-		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255) {
+		    len + RNR_TBTT_HEADER_LEN + RNR_TBTT_INFO_LEN > 255 ||
+		    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX) {
 			eid_start = eid;
 			*eid++ = WLAN_EID_REDUCED_NEIGHBOR_REPORT;
 			size_offset = eid++;
@@ -6679,7 +7441,7 @@
 		}
 
 		tbtt_count_pos = eid++;
-		*eid++ = RNR_TBTT_INFO_LEN;
+		*eid++ = ap_mld ? RNR_TBTT_INFO_MLD_LEN : RNR_TBTT_INFO_LEN;
 		*eid++ = op_class;
 		*eid++ = hapd->iconf->channel;
 		len += RNR_TBTT_HEADER_LEN;
@@ -6728,7 +7490,18 @@
 
 			*eid++ = bss_param;
 			*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
-			len += RNR_TBTT_INFO_LEN;
+
+			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;
 		}
 
@@ -6745,8 +7518,8 @@
 }
 
 
-static u8 * hostapd_eid_rnr_colocation(struct hostapd_data *hapd, u8 *eid,
-				       size_t *current_len)
+static u8 * hostapd_eid_rnr_multi_iface(struct hostapd_data *hapd, u8 *eid,
+					size_t *current_len)
 {
 	struct hostapd_iface *iface;
 	size_t i;
@@ -6756,9 +7529,16 @@
 
 	for (i = 0; i < hapd->iface->interfaces->count; i++) {
 		iface = hapd->iface->interfaces->iface[i];
+		bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && iface->bss[0]->conf->mld_ap &&
+		    hapd->conf->mld_id == iface->bss[0]->conf->mld_id)
+			ap_mld = true;
+#endif /* CONFIG_IEEE80211BE */
 
 		if (iface == hapd->iface ||
-		    !is_6ghz_op_class(iface->conf->op_class))
+		    !(is_6ghz_op_class(iface->conf->op_class) || ap_mld))
 			continue;
 
 		eid = hostapd_eid_rnr_iface(iface->bss[0], hapd, eid,
@@ -6774,6 +7554,11 @@
 	u8 *eid_start = eid;
 	size_t current_len = 0;
 	enum colocation_mode mode = get_colocation_mode(hapd);
+	bool ap_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
 
 	switch (type) {
 	case WLAN_FC_STYPE_BEACON:
@@ -6782,9 +7567,9 @@
 		/* fallthrough */
 
 	case WLAN_FC_STYPE_PROBE_RESP:
-		if (mode == COLOCATED_LOWER_BAND)
-			eid = hostapd_eid_rnr_colocation(hapd, eid,
-							 &current_len);
+		if (mode == COLOCATED_LOWER_BAND || ap_mld)
+			eid = hostapd_eid_rnr_multi_iface(hapd, eid,
+							  &current_len);
 
 		if (hapd->conf->rnr && hapd->iface->num_bss > 1 &&
 		    !hapd->iconf->mbssid)
@@ -6979,7 +7764,13 @@
 			    (conf->dtim_period % elem_count))
 				conf->dtim_period = elem_count;
 			*eid++ = conf->dtim_period;
-			*eid++ = 0xFF; /* DTIM Count */
+			/* The driver is expected to update the DTIM Count
+			 * field for each BSS that corresponds to a
+			 * nontransmitted BSSID. The value is initialized to
+			 * 0 here so that the DTIM count would be somewhat
+			 * functional even if the driver were not to update
+			 * this. */
+			*eid++ = 0; /* DTIM Count */
 		} else {
 			/* Probe Request frame does not include DTIM Period and
 			 * DTIM Count fields. */
@@ -7010,11 +7801,12 @@
 			non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES;
 		if (ie_count) {
 			*eid++ = WLAN_EID_EXTENSION;
-			*eid++ = 2 + ie_count;
+			*eid++ = 2 + ie_count + 1;
 			*eid++ = WLAN_EID_EXT_NON_INHERITANCE;
 			*eid++ = ie_count;
 			os_memcpy(eid, non_inherit_ie, ie_count);
 			eid += ie_count;
+			*eid++ = 0; /* No Element ID Extension List */
 		}
 
 		*eid_len_pos = (eid - eid_len_pos) - 1;
@@ -7099,8 +7891,8 @@
 		if (hapd->conf->rnr)
 			rnr_eid = hostapd_eid_nr_db(hapd, rnr_eid, &cur_len);
 		if (get_colocation_mode(hapd) == COLOCATED_LOWER_BAND)
-			rnr_eid = hostapd_eid_rnr_colocation(hapd, rnr_eid,
-							     &cur_len);
+			rnr_eid = hostapd_eid_rnr_multi_iface(hapd, rnr_eid,
+							      &cur_len);
 	}
 
 	return eid;
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 1190a5e..4b58fee 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -19,6 +19,10 @@
 struct radius_sta;
 enum ieee80211_op_mode;
 enum oper_chan_width;
+struct ieee802_11_elems;
+struct sae_pk;
+struct sae_pt;
+struct sae_password_entry;
 
 int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len,
 		    struct hostapd_frame_info *fi);
@@ -84,13 +88,22 @@
 			   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);
+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,
+				   size_t len);
+u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
+				 struct ieee802_11_elems *elems,
+				 struct sta_info *sta);
 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);
 u16 copy_sta_vendor_vht(struct hostapd_data *hapd, struct sta_info *sta,
 			const u8 *ie, size_t len);
 
-void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta);
+int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta);
 void ht40_intolerant_add(struct hostapd_iface *iface, struct sta_info *sta);
 void ht40_intolerant_remove(struct hostapd_iface *iface, struct sta_info *sta);
 u16 copy_sta_vht_capab(struct hostapd_data *hapd, struct sta_info *sta,
@@ -177,7 +190,8 @@
 			   u8 *owe_buf, size_t owe_buf_len, u16 *status);
 u16 owe_process_rsn_ie(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *rsn_ie, size_t rsn_ie_len,
-		       const u8 *owe_dh, size_t owe_dh_len);
+		       const u8 *owe_dh, size_t owe_dh_len,
+		       const u8 *link_addr);
 u16 owe_validate_request(struct hostapd_data *hapd, const u8 *peer,
 			 const u8 *rsn_ie, size_t rsn_ie_len,
 			 const u8 *owe_dh, size_t owe_dh_len);
@@ -226,5 +240,10 @@
 			u8 *rnr_count, u8 **rnr_offset, size_t rnr_len);
 void punct_update_legacy_bw(u16 bitmap, u8 pri_chan,
 			    enum oper_chan_width *width, u8 *seg0, u8 *seg1);
+bool hostapd_is_mld_ap(struct hostapd_data *hapd);
+const char * sae_get_password(struct hostapd_data *hapd,
+			      struct sta_info *sta, const char *rx_id,
+			      struct sae_password_entry **pw_entry,
+			      struct sae_pt **s_pt, const struct sae_pk **s_pk);
 
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index 6ebe0f9..1d17518 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -8,6 +8,8 @@
 
 #include "utils/includes.h"
 #include "utils/common.h"
+#include "crypto/crypto.h"
+#include "crypto/dh_groups.h"
 #include "hostapd.h"
 #include "sta_info.h"
 #include "ieee802_11.h"
@@ -417,3 +419,732 @@
 	os_memset(dest, 0, sizeof(*dest));
 	os_memcpy(dest, src, len);
 }
+
+
+u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
+			      struct sta_info *info, bool include_mld_id)
+{
+	struct wpabuf *buf;
+	u16 control;
+	u8 *pos = eid;
+	const u8 *ptr;
+	size_t len, slice_len;
+	u8 link_id;
+	u8 common_info_len;
+
+	/*
+	 * As the Multi-Link element can exceed the size of 255 bytes need to
+	 * first build it and then handle fragmentation.
+	 */
+	buf = wpabuf_alloc(1024);
+	if (!buf)
+		return pos;
+
+	/* Multi-Link Control field */
+	control = MULTI_LINK_CONTROL_TYPE_BASIC |
+		BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+		BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT |
+		BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA |
+		BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA;
+
+	/*
+	 * Set the basic Multi-Link common information. Hard code the common
+	 * info length to 13 based on the length of the present fields:
+	 * Length (1) + MLD address (6) + Link ID (1) +
+	 * BSS Parameters Change Count (1) + EML Capabilities (2) +
+	 * MLD Capabilities and Operations (2)
+	 */
+	common_info_len = 13;
+
+	if (include_mld_id) {
+		/* AP MLD ID */
+		control |= BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID;
+		common_info_len++;
+	}
+
+	wpabuf_put_le16(buf, control);
+
+	wpabuf_put_u8(buf, common_info_len);
+
+	/* Own MLD MAC Address */
+	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+
+	/* Own Link ID */
+	wpabuf_put_u8(buf, hapd->mld_link_id);
+
+	/* Currently hard code the BSS Parameters Change Count to 0x1 */
+	wpabuf_put_u8(buf, 0x1);
+
+	wpa_printf(MSG_DEBUG, "MLD: EML Capabilities=0x%x",
+		   hapd->iface->mld_eml_capa);
+	wpabuf_put_le16(buf, hapd->iface->mld_eml_capa);
+
+	wpa_printf(MSG_DEBUG, "MLD: MLD Capabilities and Operations=0x%x",
+		   hapd->iface->mld_mld_capa);
+	wpabuf_put_le16(buf, hapd->iface->mld_mld_capa);
+
+	if (include_mld_id) {
+		wpa_printf(MSG_DEBUG, "MLD: AP MLD ID=0x%x",
+			   hapd->conf->mld_id);
+		wpabuf_put_u8(buf, hapd->conf->mld_id);
+	}
+
+	if (!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 hostapd_data *link_bss;
+
+		/*
+		 * control (2) + station info length (1) + MAC address (6) +
+		 * 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;
+
+		/* Skip the local one */
+		if (link_id == hapd->mld_link_id || !link->valid)
+			continue;
+
+		link_bss = hostapd_mld_get_link_bss(hapd, link_id);
+		if (!link_bss) {
+			wpa_printf(MSG_ERROR,
+				   "MLD: Couldn't find link BSS - skip it");
+			continue;
+		}
+
+		/* Per-STA Profile subelement */
+		wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_PER_STA_PROFILE);
+
+		if (total_len <= 255)
+			wpabuf_put_u8(buf, total_len);
+		else
+			wpabuf_put_u8(buf, 255);
+
+		/* STA Control */
+		control = (link_id & 0xf) |
+			EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK |
+			EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK |
+			EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK |
+			EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK |
+			EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK |
+			EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK;
+		wpabuf_put_le16(buf, control);
+
+		/* STA Info */
+
+		/* STA Info Length */
+		wpabuf_put_u8(buf, fixed_len - 2);
+		wpabuf_put_data(buf, link->local_addr, ETH_ALEN);
+		wpabuf_put_le16(buf, link_bss->iconf->beacon_int);
+
+		/* TSF Offset */
+		/*
+		 * TODO: Currently setting TSF offset to zero. However, this
+		 * information needs to come from the driver.
+		 */
+		wpabuf_put_le64(buf, 0);
+
+		/* DTIM Info */
+		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);
+
+		/* Fragment the sub element if needed */
+		if (total_len <= 255) {
+			wpabuf_put_data(buf, link->resp_sta_profile,
+					link->resp_sta_profile_len);
+		} else {
+			ptr = link->resp_sta_profile;
+			len = link->resp_sta_profile_len;
+
+			slice_len = 255 - fixed_len;
+
+			wpabuf_put_data(buf, ptr, slice_len);
+			len -= slice_len;
+			ptr += slice_len;
+
+			while (len) {
+				if (len <= 255)
+					slice_len = len;
+				else
+					slice_len = 255;
+
+				wpabuf_put_u8(buf, EHT_ML_SUB_ELEM_FRAGMENT);
+				wpabuf_put_u8(buf, slice_len);
+				wpabuf_put_data(buf, ptr, slice_len);
+
+				len -= slice_len;
+				ptr += slice_len;
+			}
+		}
+	}
+
+out:
+	/* Fragment the Multi-Link element, if needed */
+	len = wpabuf_len(buf);
+	ptr = wpabuf_head(buf);
+
+	if (len <= 254)
+		slice_len = len;
+	else
+		slice_len = 254;
+
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = slice_len + 1;
+	*pos++ = WLAN_EID_EXT_MULTI_LINK;
+	os_memcpy(pos, ptr, slice_len);
+
+	ptr += slice_len;
+	pos += slice_len;
+	len -= slice_len;
+
+	while (len) {
+		if (len <= 255)
+			slice_len = len;
+		else
+			slice_len = 255;
+
+		*pos++ = WLAN_EID_FRAGMENT;
+		*pos++ = slice_len;
+		os_memcpy(pos, ptr, slice_len);
+
+		ptr += slice_len;
+		pos += slice_len;
+		len -= slice_len;
+	}
+
+	wpabuf_free(buf);
+	return pos;
+}
+
+
+struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd)
+{
+	struct wpabuf *buf = wpabuf_alloc(12);
+
+	if (!buf)
+		return NULL;
+
+	wpabuf_put_u8(buf, WLAN_EID_EXTENSION);
+	wpabuf_put_u8(buf, 10);
+	wpabuf_put_u8(buf, WLAN_EID_EXT_MULTI_LINK);
+	wpabuf_put_le16(buf, MULTI_LINK_CONTROL_TYPE_BASIC);
+	wpabuf_put_u8(buf, ETH_ALEN + 1);
+	wpabuf_put_data(buf, hapd->mld_addr, ETH_ALEN);
+
+	return buf;
+}
+
+
+#ifdef CONFIG_SAE
+
+static const u8 *
+sae_commit_skip_fixed_fields(const struct ieee80211_mgmt *mgmt, size_t len,
+			     const u8 *pos, u16 status_code)
+{
+	u16 group;
+	size_t prime_len;
+	struct crypto_ec *ec;
+
+	if (status_code != WLAN_STATUS_SAE_HASH_TO_ELEMENT)
+		return pos;
+
+	/* SAE H2E commit message (group, scalar, FFE) */
+	if (len < 2) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: SAE Group is not present");
+		return NULL;
+	}
+
+	group = WPA_GET_LE16(pos);
+	pos += 2;
+
+	/* TODO: How to parse when the group is unknown? */
+	ec = crypto_ec_init(group);
+	if (!ec) {
+		const struct dh_group *dh = dh_groups_get(group);
+
+		if (!dh) {
+			wpa_printf(MSG_DEBUG, "EHT: Unknown SAE group %u",
+				   group);
+			return NULL;
+		}
+
+		prime_len = dh->prime_len;
+	} else {
+		prime_len = crypto_ec_prime_len(ec);
+	}
+
+	wpa_printf(MSG_DEBUG, "EHT: SAE scalar length is %zu", prime_len);
+
+	/* scalar */
+	pos += prime_len;
+
+	if (ec) {
+		pos += prime_len * 2;
+		crypto_ec_deinit(ec);
+	} else {
+		pos += prime_len;
+	}
+
+	if (pos - mgmt->u.auth.variable > (int) len) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: Too short SAE commit Authentication frame");
+		return NULL;
+	}
+
+	wpa_hexdump(MSG_DEBUG, "EHT: SAE: Authentication frame elements",
+		    pos, (int) len - (pos - mgmt->u.auth.variable));
+
+	return pos;
+}
+
+
+static const u8 *
+sae_confirm_skip_fixed_fields(struct hostapd_data *hapd,
+			      const struct ieee80211_mgmt *mgmt, size_t len,
+			      const u8 *pos, u16 status_code)
+{
+	struct sta_info *sta;
+
+	if (status_code == WLAN_STATUS_REJECTED_WITH_SUGGESTED_BSS_TRANSITION)
+		return pos;
+
+	/* send confirm integer */
+	pos += 2;
+
+	/*
+	 * At this stage we should already have an MLD station and actually SA
+	 * will be replaced with the MLD MAC address by the driver.
+	 */
+	sta = ap_get_sta(hapd, mgmt->sa);
+	if (!sta) {
+		wpa_printf(MSG_DEBUG, "SAE: No MLD STA for SAE confirm");
+		return NULL;
+	}
+
+	if (!sta->sae || sta->sae->state < SAE_COMMITTED || !sta->sae->tmp) {
+		if (sta->sae)
+			wpa_printf(MSG_DEBUG, "SAE: Invalid state=%u",
+				   sta->sae->state);
+		else
+			wpa_printf(MSG_DEBUG, "SAE: No SAE context");
+		return NULL;
+	}
+
+	wpa_printf(MSG_DEBUG, "SAE: confirm: kck_len=%zu",
+		   sta->sae->tmp->kck_len);
+
+	pos += sta->sae->tmp->kck_len;
+
+	if (pos - mgmt->u.auth.variable > (int) len) {
+		wpa_printf(MSG_DEBUG,
+			   "EHT: Too short SAE confirm Authentication frame");
+		return NULL;
+	}
+
+	return pos;
+}
+
+#endif /* CONFIG_SAE */
+
+
+static const u8 * auth_skip_fixed_fields(struct hostapd_data *hapd,
+					 const struct ieee80211_mgmt *mgmt,
+					 size_t len)
+{
+	u16 auth_alg = le_to_host16(mgmt->u.auth.auth_alg);
+#ifdef CONFIG_SAE
+	u16 auth_transaction = le_to_host16(mgmt->u.auth.auth_transaction);
+	u16 status_code = le_to_host16(mgmt->u.auth.status_code);
+#endif /* CONFIG_SAE */
+	const u8 *pos = mgmt->u.auth.variable;
+
+	/* Skip fixed fields as based on IEE P802.11-REVme/D3.0, Table 9-69
+	 * (Presence of fields and elements in Authentications frames) */
+	switch (auth_alg) {
+	case WLAN_AUTH_OPEN:
+		return pos;
+#ifdef CONFIG_SAE
+	case WLAN_AUTH_SAE:
+		if (auth_transaction == 1) {
+			if (status_code == WLAN_STATUS_SUCCESS) {
+				wpa_printf(MSG_DEBUG,
+					   "EHT: SAE H2E is mandatory for MLD");
+				goto out;
+			}
+
+			return sae_commit_skip_fixed_fields(mgmt, len, pos,
+							    status_code);
+		} else if (auth_transaction == 2) {
+			return sae_confirm_skip_fixed_fields(hapd, mgmt, len,
+							     pos, status_code);
+		}
+
+		return pos;
+#endif /* CONFIG_SAE */
+	/* TODO: Support additional algorithms that can be used for MLO */
+	case WLAN_AUTH_FT:
+	case WLAN_AUTH_FILS_SK:
+	case WLAN_AUTH_FILS_SK_PFS:
+	case WLAN_AUTH_FILS_PK:
+	case WLAN_AUTH_PASN:
+	default:
+		break;
+	}
+
+#ifdef CONFIG_SAE
+out:
+#endif /* CONFIG_SAE */
+	wpa_printf(MSG_DEBUG,
+		   "TODO: Authentication algorithm %u not supported with MLD",
+		   auth_alg);
+	return NULL;
+}
+
+
+const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
+				   const struct ieee80211_mgmt *mgmt,
+				   size_t len)
+{
+	struct ieee802_11_elems elems;
+	const u8 *pos;
+
+	if (!hapd->conf->mld_ap)
+		return NULL;
+
+	len -= offsetof(struct ieee80211_mgmt, u.auth.variable);
+
+	pos = auth_skip_fixed_fields(hapd, mgmt, len);
+	if (!pos)
+		return NULL;
+
+	if (ieee802_11_parse_elems(pos,
+				   (int)len - (pos - mgmt->u.auth.variable),
+				   &elems, 0) == ParseFailed) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Failed parsing Authentication frame");
+	}
+
+	if (!elems.basic_mle || !elems.basic_mle_len)
+		return NULL;
+
+	return get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len);
+}
+
+
+static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd,
+					   struct mld_info *info)
+{
+	u8 i, link_id;
+
+	if (!info->mld_sta) {
+		wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD");
+		return 0;
+	}
+
+	/*
+	 * Iterate over the links negotiated in the (Re)Association Request
+	 * frame and validate that they are indeed valid links in the local AP
+	 * MLD.
+	 *
+	 * While at it, also update the local address for the links in the
+	 * mld_info, so it could be easily available for later flows, e.g., for
+	 * the RSN Authenticator, etc.
+	 */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct hostapd_data *other_hapd;
+
+		if (!info->links[link_id].valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (hapd == other_hapd)
+				continue;
+
+			if (other_hapd->conf->mld_ap &&
+			    other_hapd->conf->mld_id == hapd->conf->mld_id &&
+			    link_id == other_hapd->mld_link_id)
+				break;
+		}
+
+		if (i == hapd->iface->interfaces->count &&
+		    link_id != hapd->mld_link_id) {
+			wpa_printf(MSG_DEBUG, "MLD: Invalid link ID=%u",
+				   link_id);
+			return -1;
+		}
+
+		if (i < hapd->iface->interfaces->count)
+			os_memcpy(info->links[link_id].local_addr,
+				  other_hapd->own_addr,
+				  ETH_ALEN);
+	}
+
+	return 0;
+}
+
+
+u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
+				 struct ieee802_11_elems *elems,
+				 struct sta_info *sta)
+{
+	struct wpabuf *mlbuf;
+	const struct ieee80211_eht_ml *ml;
+	const struct eht_ml_basic_common_info *common_info;
+	size_t ml_len, common_info_len;
+	struct mld_link_info *link_info;
+	struct mld_info *info = &sta->mld_info;
+	const u8 *pos;
+	int ret = -1;
+	u16 ml_control;
+
+	mlbuf = ieee802_11_defrag_mle(elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	if (!mlbuf)
+		return WLAN_STATUS_SUCCESS;
+
+	ml = wpabuf_head(mlbuf);
+	ml_len = wpabuf_len(mlbuf);
+
+	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 params change not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) {
+		wpa_printf(MSG_DEBUG, "MLD: Sync delay not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		common_info_len += 2;
+	} else {
+		wpa_printf(MSG_DEBUG, "MLD: EML capabilities not present");
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) {
+		common_info_len += 2;
+
+	} else {
+		wpa_printf(MSG_DEBUG, "MLD: MLD capabilities not present");
+		goto out;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: expected_common_info_len=%lu",
+		   common_info_len);
+
+	if (sizeof(*ml) + common_info_len > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info");
+		goto out;
+	}
+
+	common_info = (const 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 (expected %zu)",
+			   common_info->len, common_info_len);
+		goto out;
+	}
+
+	pos = common_info->variable;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		info->common_info.eml_capa = WPA_GET_LE16(pos);
+		pos += 2;
+	} else {
+		info->common_info.eml_capa = 0;
+	}
+
+	info->common_info.mld_capa = WPA_GET_LE16(pos);
+	pos += 2;
+
+	wpa_printf(MSG_DEBUG, "MLD: addr=" MACSTR ", eml=0x%x, mld=0x%x",
+		   MAC2STR(info->common_info.mld_addr),
+		   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) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: MLD address mismatch between authentication ("
+			   MACSTR ") and association (" MACSTR ")",
+			   MAC2STR(info->common_info.mld_addr),
+			   MAC2STR(common_info->mld_addr));
+		goto out;
+	}
+
+	info->links[hapd->mld_link_id].valid = true;
+
+	/* Parse the link info field */
+	ml_len -= sizeof(*ml) + common_info_len;
+
+	while (ml_len > 2) {
+		size_t sub_elem_len = *(pos + 1);
+		size_t sta_info_len;
+		u16 control;
+
+		wpa_printf(MSG_DEBUG, "MLD: sub element len=%zu",
+			   sub_elem_len);
+
+		if (2 + sub_elem_len > ml_len) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Invalid link info len: %zu %zu",
+				   2 + sub_elem_len, ml_len);
+			goto out;
+		}
+
+		if (*pos == MULTI_LINK_SUB_ELEM_ID_VENDOR) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Skip vendor specific subelement");
+
+			pos += 2 + sub_elem_len;
+			ml_len -= 2 + sub_elem_len;
+			continue;
+		}
+
+		if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Unexpected Multi-Link element subelement ID=%u",
+				   *pos);
+			goto out;
+		}
+
+		/* Skip the subelement ID and the length */
+		pos += 2;
+		ml_len -= 2;
+
+		/* Get the station control field */
+		if (sub_elem_len < 2) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Too short Per-STA Profile subelement");
+			goto out;
+		}
+		control = WPA_GET_LE16(pos);
+		link_info = &info->links[control &
+					 EHT_PER_STA_CTRL_LINK_ID_MSK];
+		pos += 2;
+		ml_len -= 2;
+		sub_elem_len -= 2;
+
+		if (!(control & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per-STA complete profile expected");
+			goto out;
+		}
+
+		if (!(control & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per-STA MAC address not present");
+			goto out;
+		}
+
+		if ((control & (EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK |
+				EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK))) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Beacon/DTIM interval not expected");
+			goto out;
+		}
+
+		/* The length octet and the MAC address must be present */
+		sta_info_len = 1 + ETH_ALEN;
+
+		if (control & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) {
+			if (control & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK)
+				link_info->nstr_bitmap_len = 2;
+			else
+				link_info->nstr_bitmap_len = 1;
+		}
+
+		sta_info_len += link_info->nstr_bitmap_len;
+
+		if (sta_info_len > ml_len || sta_info_len != *pos ||
+		    sta_info_len > sub_elem_len) {
+			wpa_printf(MSG_DEBUG, "MLD: Invalid STA Info length");
+			goto out;
+		}
+
+		/* skip the length */
+		pos++;
+		ml_len--;
+
+		/* get the link address */
+		os_memcpy(link_info->peer_addr, pos, ETH_ALEN);
+		wpa_printf(MSG_DEBUG,
+			   "MLD: assoc: link id=%u, addr=" MACSTR,
+			   control & EHT_PER_STA_CTRL_LINK_ID_MSK,
+			   MAC2STR(link_info->peer_addr));
+
+		pos += ETH_ALEN;
+		ml_len -= ETH_ALEN;
+
+		/* Get the NSTR bitmap */
+		if (link_info->nstr_bitmap_len) {
+			os_memcpy(link_info->nstr_bitmap, pos,
+				  link_info->nstr_bitmap_len);
+			pos += link_info->nstr_bitmap_len;
+			ml_len -= link_info->nstr_bitmap_len;
+		}
+
+		sub_elem_len -= sta_info_len;
+
+		wpa_printf(MSG_DEBUG, "MLD: STA Profile len=%zu", sub_elem_len);
+		if (sub_elem_len > ml_len)
+			goto out;
+
+		if (sub_elem_len > 2)
+			link_info->capability = WPA_GET_LE16(pos);
+
+		pos += sub_elem_len;
+		ml_len -= sub_elem_len;
+
+		wpa_printf(MSG_DEBUG, "MLD: link ctrl=0x%x, " MACSTR
+			   ", nstr bitmap len=%lu",
+			   control, MAC2STR(link_info->peer_addr),
+			   link_info->nstr_bitmap_len);
+
+		link_info->valid = true;
+	}
+
+	if (ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: %zu bytes left after parsing. fail",
+			   ml_len);
+		goto out;
+	}
+
+	ret = hostapd_mld_validate_assoc_info(hapd, info);
+out:
+	wpabuf_free(mlbuf);
+	if (ret) {
+		os_memset(info, 0, sizeof(*info));
+		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+	}
+
+	return WLAN_STATUS_SUCCESS;
+}
diff --git a/src/ap/ieee802_11_ht.c b/src/ap/ieee802_11_ht.c
index 59ecbdc..f90f125 100644
--- a/src/ap/ieee802_11_ht.c
+++ b/src/ap/ieee802_11_ht.c
@@ -479,15 +479,14 @@
 }
 
 
-void update_ht_state(struct hostapd_data *hapd, struct sta_info *sta)
+int update_ht_state(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	if ((sta->flags & WLAN_STA_HT) && sta->ht_capabilities)
 		update_sta_ht(hapd, sta);
 	else
 		update_sta_no_ht(hapd, sta);
 
-	if (hostapd_ht_operation_update(hapd->iface) > 0)
-		ieee802_11_set_beacons(hapd->iface);
+	return hostapd_ht_operation_update(hapd->iface);
 }
 
 
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 8b67669..052231e 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -95,39 +95,43 @@
 	if (sta->flags & WLAN_STA_PREAUTH) {
 		rsn_preauth_send(hapd, sta, buf, len);
 	} else {
+		int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+		link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1;
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_drv_hapd_send_eapol(
 			hapd, sta->addr, buf, len,
-			encrypt, hostapd_sta_flags_to_drv(sta->flags));
+			encrypt, hostapd_sta_flags_to_drv(sta->flags), link_id);
 	}
 
 	os_free(buf);
 }
 
 
-void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd,
-				   struct sta_info *sta, int authorized)
+static void ieee802_1x_set_authorized(struct hostapd_data *hapd,
+				      struct sta_info *sta,
+				      bool authorized, bool mld)
 {
 	int res;
 
 	if (sta->flags & WLAN_STA_PREAUTH)
 		return;
 
-	if (authorized) {
-		ap_sta_set_authorized(hapd, sta, 1);
-		res = hostapd_set_authorized(hapd, sta, 1);
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
-			       HOSTAPD_LEVEL_DEBUG, "authorizing port");
-	} else {
-		ap_sta_set_authorized(hapd, sta, 0);
-		res = hostapd_set_authorized(hapd, sta, 0);
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
-			       HOSTAPD_LEVEL_DEBUG, "unauthorizing port");
-	}
+	ap_sta_set_authorized(hapd, sta, authorized);
+	res = hostapd_set_authorized(hapd, sta, authorized);
+	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
+		       HOSTAPD_LEVEL_DEBUG, "%sauthorizing port",
+		       authorized ? "" : "un");
 
-	if (res && errno != ENOENT) {
+	if (!mld && res && errno != ENOENT) {
 		wpa_printf(MSG_DEBUG, "Could not set station " MACSTR
 			   " flags for kernel driver (errno=%d).",
 			   MAC2STR(sta->addr), errno);
+	} else if (mld && res) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Could not set station " MACSTR " flags",
+			   MAC2STR(sta->addr));
 	}
 
 	if (authorized) {
@@ -137,6 +141,65 @@
 }
 
 
+static void ieee802_1x_ml_set_sta_authorized(struct hostapd_data *hapd,
+					     struct sta_info *sta,
+					     bool authorized)
+{
+#ifdef CONFIG_IEEE80211BE
+	unsigned int i, link_id;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return;
+
+	/*
+	 * Authorizing the station should be done only in the station
+	 * performing the association
+	 */
+	if (authorized && hapd->mld_link_id != sta->mld_assoc_link_id)
+		return;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &sta->mld_info.links[link_id];
+
+		if (!link->valid)
+			continue;
+
+		for (i = 0; i < hapd->iface->interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+			struct hostapd_data *tmp_hapd =
+				hapd->iface->interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				if (tmp_sta == sta ||
+				    tmp_sta->mld_assoc_link_id !=
+				    sta->mld_assoc_link_id ||
+				    tmp_sta->aid != sta->aid)
+					continue;
+
+				ieee802_1x_set_authorized(tmp_hapd, tmp_sta,
+							  authorized, true);
+				break;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+
+void ieee802_1x_set_sta_authorized(struct hostapd_data *hapd,
+				   struct sta_info *sta, int authorized)
+{
+	ieee802_1x_set_authorized(hapd, sta, authorized, false);
+	ieee802_1x_ml_set_sta_authorized(hapd, sta, !!authorized);
+}
+
+
 #ifdef CONFIG_WEP
 #ifndef CONFIG_FIPS
 #ifndef CONFIG_NO_RC4
@@ -2474,6 +2537,14 @@
 	struct eapol_auth_config conf;
 	struct eapol_auth_cb cb;
 
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Using IEEE 802.1X state machine of the first BSS");
+
+		hapd->eapol_auth = hapd->mld_first_bss->eapol_auth;
+		return 0;
+	}
+
 	dl_list_init(&hapd->erp_keys);
 
 	os_memset(&conf, 0, sizeof(conf));
@@ -2558,6 +2629,14 @@
 
 void ieee802_1x_deinit(struct hostapd_data *hapd)
 {
+	if (hapd->mld_first_bss) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Deinit IEEE 802.1X state machine of a non-first BSS");
+
+		hapd->eapol_auth = NULL;
+		return;
+	}
+
 #ifdef CONFIG_WEP
 	eloop_cancel_timeout(ieee802_1x_rekey, hapd, NULL);
 #endif /* CONFIG_WEP */
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index 32d291d..ee4232f 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -56,7 +56,9 @@
 	unsigned int hash;
 
 	pmksa->pmksa_count--;
-	pmksa->free_cb(entry, pmksa->ctx);
+
+	if (pmksa->free_cb)
+		pmksa->free_cb(entry, pmksa->ctx);
 
 	/* unlink from hash list */
 	hash = PMKID_HASH(entry->pmkid);
@@ -332,6 +334,10 @@
 		return NULL;
 	os_memcpy(entry->pmk, pmk, pmk_len);
 	entry->pmk_len = pmk_len;
+	if (kck && kck_len && kck_len < WPA_KCK_MAX_LEN) {
+		os_memcpy(entry->kck, kck, kck_len);
+		entry->kck_len = kck_len;
+	}
 	if (pmkid)
 		os_memcpy(entry->pmkid, pmkid, PMKID_LEN);
 	else if (akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192)
@@ -523,8 +529,17 @@
 				return entry;
 			continue;
 		}
-		rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa, new_pmkid,
-			  entry->akmp);
+		if (entry->akmp == WPA_KEY_MGMT_IEEE8021X_SUITE_B_192 &&
+		    entry->kck_len > 0)
+			rsn_pmkid_suite_b_192(entry->kck, entry->kck_len,
+					      aa, spa, new_pmkid);
+		else if (wpa_key_mgmt_suite_b(entry->akmp) &&
+			 entry->kck_len > 0)
+		rsn_pmkid_suite_b(entry->kck, entry->kck_len, aa, spa,
+				  new_pmkid);
+		else
+			rsn_pmkid(entry->pmk, entry->pmk_len, aa, spa,
+				  new_pmkid, entry->akmp);
 		if (os_memcmp(new_pmkid, pmkid, PMKID_LEN) == 0)
 			return entry;
 	}
diff --git a/src/ap/pmksa_cache_auth.h b/src/ap/pmksa_cache_auth.h
index e3cee4a..e38e7ec 100644
--- a/src/ap/pmksa_cache_auth.h
+++ b/src/ap/pmksa_cache_auth.h
@@ -19,6 +19,8 @@
 	u8 pmkid[PMKID_LEN];
 	u8 pmk[PMK_LEN_MAX];
 	size_t pmk_len;
+	u8 kck[WPA_KCK_MAX_LEN];
+	size_t kck_len;
 	os_time_t expiration;
 	int akmp; /* WPA_KEY_MGMT_* */
 	u8 spa[ETH_ALEN];
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index 07100f2..a00f896 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -199,7 +199,7 @@
 
 	if ((sta->flags & WLAN_STA_WDS) ||
 	    (sta->flags & WLAN_STA_MULTI_AP &&
-	     !(hapd->conf->multi_ap & FRONTHAUL_BSS) &&
+	     (hapd->conf->multi_ap & BACKHAUL_BSS) &&
 	     !(sta->flags & WLAN_STA_WPS)))
 		hostapd_set_wds_sta(hapd, NULL, sta->addr, sta->aid, 0);
 
@@ -290,7 +290,7 @@
 #endif /* CONFIG_MESH */
 
 	if (set_beacon)
-		ieee802_11_set_beacons(hapd->iface);
+		ieee802_11_update_beacons(hapd->iface);
 
 	wpa_printf(MSG_DEBUG, "%s: cancel ap_handle_timer for " MACSTR,
 		   __func__, MAC2STR(sta->addr));
@@ -301,7 +301,15 @@
 	sae_clear_retransmit_timer(hapd, sta);
 
 	ieee802_1x_free_station(hapd, sta);
+
+#ifdef CONFIG_IEEE80211BE
+	if (!hapd->conf->mld_ap || !sta->mld_info.mld_sta ||
+	    hapd->mld_link_id == sta->mld_assoc_link_id)
+		wpa_auth_sta_deinit(sta->wpa_sm);
+#else
 	wpa_auth_sta_deinit(sta->wpa_sm);
+#endif /* CONFIG_IEEE80211BE */
+
 	rsn_preauth_free_station(hapd, sta);
 #ifndef CONFIG_NO_RADIUS
 	if (hapd->radius)
@@ -866,7 +874,14 @@
 			       ap_handle_timer, hapd, sta);
 	accounting_sta_stop(hapd, sta);
 	ieee802_1x_free_station(hapd, sta);
+#ifdef CONFIG_IEEE80211BE
+	if (!hapd->conf->mld_ap ||
+	    hapd->mld_link_id == sta->mld_assoc_link_id)
+		wpa_auth_sta_deinit(sta->wpa_sm);
+#else
 	wpa_auth_sta_deinit(sta->wpa_sm);
+#endif /* CONFIG_IEEE80211BE */
+
 	sta->wpa_sm = NULL;
 
 	sta->disassoc_reason = reason;
@@ -1071,6 +1086,12 @@
 	struct hostapd_vlan *vlan = NULL;
 	int ret;
 	int old_vlanid = sta->vlan_id_bound;
+	int mld_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		mld_link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	if ((sta->flags & WLAN_STA_WDS) && sta->vlan_id == 0) {
 		wpa_printf(MSG_DEBUG,
@@ -1128,7 +1149,8 @@
 	if (wpa_auth_sta_set_vlan(sta->wpa_sm, sta->vlan_id) < 0)
 		wpa_printf(MSG_INFO, "Failed to update VLAN-ID for WPA");
 
-	ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id);
+	ret = hostapd_drv_set_sta_vlan(iface, hapd, sta->addr, sta->vlan_id,
+				       mld_link_id);
 	if (ret < 0) {
 		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG, "could not bind the STA "
@@ -1285,7 +1307,19 @@
 		return;
 
 	if (authorized) {
-		hostapd_prune_associations(hapd, sta->addr);
+		int mld_assoc_link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+		if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+			if (sta->mld_assoc_link_id == hapd->mld_link_id)
+				mld_assoc_link_id = sta->mld_assoc_link_id;
+			else
+				mld_assoc_link_id = -2;
+		}
+#endif /* CONFIG_IEEE80211BE */
+		if (mld_assoc_link_id != -2)
+			hostapd_prune_associations(hapd, sta->addr,
+						   mld_assoc_link_id);
 		sta->flags |= WLAN_STA_AUTHORIZED;
 	} else {
 		sta->flags &= ~WLAN_STA_AUTHORIZED;
@@ -1572,6 +1606,9 @@
 
 int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
 {
+	const u8 *mld_link_addr = NULL;
+	bool mld_link_sta = false;
+
 	/*
 	 * If a station that is already associated to the AP, is trying to
 	 * authenticate again, remove the STA entry, in order to make sure the
@@ -1579,6 +1616,16 @@
 	 * this, station's added_unassoc flag is cleared once the station has
 	 * completed association.
 	 */
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && sta->mld_info.mld_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;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	ap_sta_set_authorized(hapd, sta, 0);
 	hostapd_drv_sta_remove(hapd, sta->addr);
 	sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_AUTH | WLAN_STA_AUTHORIZED);
@@ -1587,7 +1634,8 @@
 			    sta->supported_rates,
 			    sta->supported_rates_len,
 			    0, NULL, NULL, NULL, 0, NULL, 0, NULL,
-			    sta->flags, 0, 0, 0, 0)) {
+			    sta->flags, 0, 0, 0, 0,
+			    mld_link_addr, mld_link_sta)) {
 		hostapd_logger(hapd, sta->addr,
 			       HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_NOTICE,
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index 8433ff8..e2b9dde 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -69,6 +69,35 @@
 	enum frame_encryption encrypted;
 };
 
+#define EHT_ML_MAX_STA_PROF_LEN 1024
+struct mld_info {
+	bool mld_sta;
+
+	struct ml_common_info {
+		u8 mld_addr[ETH_ALEN];
+		u16 medium_sync_delay;
+		u16 eml_capa;
+		u16 mld_capa;
+	} common_info;
+
+	struct mld_link_info {
+		u8 valid;
+		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];
+
+		const u8 *rsne, *rsnxe;
+	} links[MAX_NUM_MLD_LINKS];
+};
+
 struct sta_info {
 	struct sta_info *next; /* next entry in sta list */
 	struct sta_info *hnext; /* next entry in hash table list */
@@ -299,6 +328,11 @@
 #ifdef CONFIG_PASN
 	struct pasn_data *pasn;
 #endif /* CONFIG_PASN */
+
+#ifdef CONFIG_IEEE80211BE
+	struct mld_info mld_info;
+	u8 mld_assoc_link_id;
+#endif /* CONFIG_IEEE80211BE */
 };
 
 
diff --git a/src/ap/utils.c b/src/ap/utils.c
index bedad6e..e93e531 100644
--- a/src/ap/utils.c
+++ b/src/ap/utils.c
@@ -43,6 +43,7 @@
 struct prune_data {
 	struct hostapd_data *hapd;
 	const u8 *addr;
+	int mld_assoc_link_id;
 };
 
 static int prune_associations(struct hostapd_iface *iface, void *ctx)
@@ -72,6 +73,12 @@
 		if (!osta)
 			continue;
 
+#ifdef CONFIG_IEEE80211BE
+		if (data->mld_assoc_link_id >= 0 &&
+		    osta->mld_assoc_link_id == data->mld_assoc_link_id)
+			continue;
+#endif /* CONFIG_IEEE80211BE */
+
 		wpa_printf(MSG_INFO, "%s: Prune association for " MACSTR,
 			   ohapd->conf->iface, MAC2STR(osta->addr));
 		ap_sta_disassociate(ohapd, osta, WLAN_REASON_UNSPECIFIED);
@@ -84,15 +91,20 @@
  * hostapd_prune_associations - Remove extraneous associations
  * @hapd: Pointer to BSS data for the most recent association
  * @addr: Associated STA address
+ * @mld_assoc_link_id: MLD link id used for association or -1 for non MLO
  *
  * This function looks through all radios and BSS's for previous
  * (stale) associations of STA. If any are found they are removed.
  */
-void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr)
+void hostapd_prune_associations(struct hostapd_data *hapd, const u8 *addr,
+				int mld_assoc_link_id)
 {
 	struct prune_data data;
+
 	data.hapd = hapd;
 	data.addr = addr;
+	data.mld_assoc_link_id = mld_assoc_link_id;
+
 	if (hapd->iface->interfaces &&
 	    hapd->iface->interfaces->for_each_interface)
 		hapd->iface->interfaces->for_each_interface(
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index 635a74a..a662201 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -29,6 +29,7 @@
 #include "drivers/driver.h"
 #include "ap_config.h"
 #include "ieee802_11.h"
+#include "sta_info.h"
 #include "wpa_auth.h"
 #include "pmksa_cache_auth.h"
 #include "wpa_auth_i.h"
@@ -84,12 +85,20 @@
 
 static const u8 * wpa_auth_get_aa(const struct wpa_state_machine *sm)
 {
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return sm->own_mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 	return sm->wpa_auth->addr;
 }
 
 
 static const u8 * wpa_auth_get_spa(const struct wpa_state_machine *sm)
 {
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return sm->peer_mld_addr;
+#endif /* CONFIG_IEEE80211BE */
 	return sm->addr;
 }
 
@@ -699,6 +708,9 @@
 	sm->wpa_auth = wpa_auth;
 	sm->group = wpa_auth->group;
 	wpa_group_get(sm->wpa_auth, sm->group);
+#ifdef CONFIG_IEEE80211BE
+	sm->mld_assoc_link_id = -1;
+#endif /* CONFIG_IEEE80211BE */
 
 	return sm;
 }
@@ -1077,9 +1089,15 @@
 	const u8 *key_data;
 	size_t keyhdrlen, mic_len;
 	u8 *mic;
+	bool is_mld = false;
 
 	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);
@@ -1149,6 +1167,11 @@
 		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.. */
 	if (key_info & WPA_KEY_INFO_REQUEST) {
 		msg = REQUEST;
 		msgtxt = "Request";
@@ -1157,7 +1180,9 @@
 		msgtxt = "2/2 Group";
 	} else if (key_data_length == 0 ||
 		   (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
-		    key_data_length == AES_BLOCK_SIZE)) {
+		    key_data_length == AES_BLOCK_SIZE) ||
+		   (is_mld && (key_info & WPA_KEY_INFO_SECURE) &&
+		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING)) {
 		msg = PAIRWISE_4;
 		msgtxt = "4/4 Pairwise";
 	} else {
@@ -1779,6 +1804,15 @@
 }
 
 
+static int wpa_auth_get_sta_count(struct wpa_authenticator *wpa_auth)
+{
+	if (!wpa_auth->cb->get_sta_count)
+		return -1;
+
+	return wpa_auth->cb->get_sta_count(wpa_auth->cb_ctx);
+}
+
+
 static void wpa_send_eapol(struct wpa_authenticator *wpa_auth,
 			   struct wpa_state_machine *sm, int key_info,
 			   const u8 *key_rsc, const u8 *nonce,
@@ -1811,11 +1845,16 @@
 skip_tx:
 #endif /* CONFIG_TESTING_OPTIONS */
 
-	if (ctr == 1 && wpa_auth->conf.tx_status)
-		timeout_ms = pairwise ? eapol_key_timeout_first :
-			eapol_key_timeout_first_group;
-	else
+	if (ctr == 1 && wpa_auth->conf.tx_status) {
+		if (pairwise)
+			timeout_ms = eapol_key_timeout_first;
+		else if (wpa_auth_get_sta_count(wpa_auth) > 100)
+			timeout_ms = eapol_key_timeout_first_group * 2;
+		else
+			timeout_ms = eapol_key_timeout_first_group;
+	} else {
 		timeout_ms = eapol_key_timeout_subseq;
+	}
 	if (wpa_auth->conf.wpa_disable_eapol_key_retries &&
 	    (!pairwise || (key_info & WPA_KEY_INFO_MIC)))
 		timeout_ms = eapol_key_timeout_no_retrans;
@@ -2293,8 +2332,9 @@
 
 SM_STATE(WPA_PTK, PTKSTART)
 {
-	u8 buf[2 + RSN_SELECTOR_LEN + PMKID_LEN], *pmkid = NULL;
-	size_t pmkid_len = 0;
+	u8 buf[2 * (2 + RSN_SELECTOR_LEN) + PMKID_LEN + ETH_ALEN];
+	u8 *pmkid = NULL;
+	size_t kde_len = 0;
 	u16 key_info;
 
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
@@ -2332,7 +2372,7 @@
 	     wpa_key_mgmt_sae(sm->wpa_key_mgmt)) &&
 	    sm->wpa_key_mgmt != WPA_KEY_MGMT_OSEN) {
 		pmkid = buf;
-		pmkid_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
+		kde_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
 		pmkid[0] = WLAN_EID_VENDOR_SPECIFIC;
 		pmkid[1] = RSN_SELECTOR_LEN + PMKID_LEN;
 		RSN_SELECTOR_PUT(&pmkid[2], RSN_KEY_DATA_PMKID);
@@ -2400,12 +2440,24 @@
 		}
 	}
 	if (!pmkid)
-		pmkid_len = 0;
+		kde_len = 0;
+
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLD: Add MAC Address KDE: kde_len=%zu",
+			   kde_len);
+		wpa_add_kde(buf + kde_len, RSN_KEY_DATA_MAC_ADDR,
+			    sm->own_mld_addr, ETH_ALEN, NULL, 0);
+		kde_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	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, pmkid, pmkid_len, 0, 0);
+		       sm->ANonce, kde_len ? buf : NULL, kde_len, 0, 0);
 }
 
 
@@ -3114,6 +3166,71 @@
 #endif /* CONFIG_OCV */
 
 
+static int wpa_auth_validate_ml_kdes_m2(struct wpa_state_machine *sm,
+					struct wpa_eapol_ie_parse *kde)
+{
+#ifdef CONFIG_IEEE80211BE
+	int i;
+	unsigned int n_links = 0;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/* MLD MAC address must be the same */
+	if (!kde->mac_addr ||
+	    os_memcmp(kde->mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG, "RSN: MLD: Invalid MLD address");
+		return -1;
+	}
+
+	/* Find matching link ID and the MAC address for each link */
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(kde->valid_mlo_links & BIT(i)))
+			continue;
+
+		/*
+		 * Each entry should contain the link information and the MAC
+		 * address.
+		 */
+		if (kde->mlo_link_len[i] != 1 + ETH_ALEN) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: Invalid MLO Link (ID %u) KDE len=%zu",
+				   i, kde->mlo_link_len[i]);
+			return -1;
+		}
+
+		if (!sm->mld_links[i].valid || i == sm->mld_assoc_link_id) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: Invalid link ID=%u", i);
+			return -1;
+		}
+
+		if (os_memcmp(sm->mld_links[i].peer_addr, kde->mlo_link[i] + 1,
+			      ETH_ALEN) != 0) {
+			wpa_printf(MSG_DEBUG,
+				   "RSN: MLD: invalid MAC address=" MACSTR
+				   " expected " MACSTR " (link ID %u)",
+				   MAC2STR(kde->mlo_link[i] + 1),
+				   MAC2STR(sm->mld_links[i].peer_addr), i);
+			return -1;
+		}
+
+		n_links++;
+	}
+
+	/* Must have the same number of MLO links (excluding the local one) */
+	if (n_links != sm->n_mld_affiliated_links) {
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLD: Expecting %u MLD links in msg 2, but got %u",
+			   sm->n_mld_affiliated_links, n_links);
+		return -1;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return 0;
+}
+
+
 SM_STATE(WPA_PTK, PTKCALCNEGOTIATING)
 {
 	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
@@ -3384,6 +3501,12 @@
 	}
 #endif /* CONFIG_DPP2 */
 
+	if (wpa_auth_validate_ml_kdes_m2(sm, &kde) < 0) {
+		wpa_sta_disconnect(wpa_auth, sm->addr,
+				   WLAN_REASON_PREV_AUTH_NOT_VALID);
+		return;
+	}
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
 		/*
@@ -3478,6 +3601,11 @@
 	if (!sm->mgmt_frame_prot)
 		return pos;
 
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		return pos; /* Use per-link MLO KDEs instead */
+#endif /* CONFIG_IEEE80211BE */
+
 	igtk.keyid[0] = gsm->GN_igtk;
 	igtk.keyid[1] = 0;
 	if (gsm->wpa_group_state != WPA_GROUP_SETKEYSDONE ||
@@ -3599,6 +3727,346 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 
+#ifdef CONFIG_IEEE80211BE
+
+void wpa_auth_ml_get_rsn_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_rsn_info *info)
+{
+	info->rsn_ies = a->wpa_ie;
+	info->rsn_ies_len = a->wpa_ie_len;
+
+	wpa_printf(MSG_DEBUG, "RSN: MLD: link_id=%u, rsn_ies_len=%zu",
+		   info->link_id, info->rsn_ies_len);
+}
+
+
+static void wpa_auth_get_ml_rsn_info(struct wpa_authenticator *wpa_auth,
+				     struct wpa_auth_ml_rsn_info *info)
+{
+	if (!wpa_auth->cb->get_ml_rsn_info)
+		return;
+
+	wpa_auth->cb->get_ml_rsn_info(wpa_auth->cb_ctx, info);
+}
+
+
+void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_key_info *info,
+			      bool mgmt_frame_prot, bool beacon_prot)
+{
+	struct wpa_group *gsm = a->group;
+	u8 rsc[WPA_KEY_RSC_LEN];
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Get group key info: link_id=%u, IGTK=%u, BIGTK=%u",
+		   info->link_id, mgmt_frame_prot, beacon_prot);
+
+	info->gtkidx = gsm->GN & 0x03;
+	info->gtk = gsm->GTK[gsm->GN - 1];
+	info->gtk_len = gsm->GTK_len;
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN, rsc) < 0)
+		os_memset(info->pn, 0, sizeof(info->pn));
+	else
+		os_memcpy(info->pn, rsc, sizeof(info->pn));
+
+	if (!mgmt_frame_prot)
+		return;
+
+	info->igtkidx = gsm->GN_igtk;
+	info->igtk = gsm->IGTK[gsm->GN_igtk - 4];
+	info->igtk_len = wpa_cipher_key_len(a->conf.group_mgmt_cipher);
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN_igtk, rsc) < 0)
+		os_memset(info->ipn, 0, sizeof(info->ipn));
+	else
+		os_memcpy(info->ipn, rsc, sizeof(info->ipn));
+
+	if (!beacon_prot)
+		return;
+
+	info->bigtkidx = gsm->GN_bigtk;
+	info->bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
+
+	if (wpa_auth_get_seqnum(a, NULL, gsm->GN_bigtk, rsc) < 0)
+		os_memset(info->bipn, 0, sizeof(info->bipn));
+	else
+		os_memcpy(info->bipn, rsc, sizeof(info->bipn));
+}
+
+
+static void wpa_auth_get_ml_key_info(struct wpa_authenticator *wpa_auth,
+				     struct wpa_auth_ml_key_info *info)
+{
+	if (!wpa_auth->cb->get_ml_key_info)
+		return;
+
+	wpa_auth->cb->get_ml_key_info(wpa_auth->cb_ctx, info);
+}
+
+
+static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm)
+{
+	struct wpa_group *gsm = sm->group;
+	size_t gtk_len = gsm->GTK_len;
+	size_t igtk_len;
+	size_t kde_len;
+	unsigned int n_links;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	n_links = sm->n_mld_affiliated_links + 1;
+
+	/* MLO GTK KDE for each link */
+	kde_len = n_links * (2 + RSN_SELECTOR_LEN + 1 + 6 + gtk_len);
+
+	if (!sm->mgmt_frame_prot)
+		return kde_len;
+
+	/* MLO IGTK KDE for each link */
+	igtk_len = wpa_cipher_key_len(sm->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)
+		return kde_len;
+
+	/* MLO BIGTK KDE for each link */
+	kde_len += n_links * (2 + RSN_SELECTOR_LEN + 2 + 6 + 1 + igtk_len);
+
+	return kde_len;
+}
+
+
+static u8 * wpa_auth_ml_group_kdes(struct wpa_state_machine *sm, u8 *pos)
+{
+	struct wpa_auth_ml_key_info ml_key_info;
+	unsigned int i, link_id;
+
+	/* First fetch the key information from all the authenticators */
+	os_memset(&ml_key_info, 0, sizeof(ml_key_info));
+	ml_key_info.n_mld_links = sm->n_mld_affiliated_links + 1;
+
+	/*
+	 * Assume that management frame protection and beacon protection are the
+	 * same on all links.
+	 */
+	ml_key_info.mgmt_frame_prot = sm->mgmt_frame_prot;
+	ml_key_info.beacon_prot = sm->wpa_auth->conf.beacon_prot;
+
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		ml_key_info.links[i++].link_id = link_id;
+	}
+
+	wpa_auth_get_ml_key_info(sm->wpa_auth, &ml_key_info);
+
+	/* 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)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO GTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO GTK",
+				ml_key_info.links[i].gtk,
+				ml_key_info.links[i].gtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 1 + 6 +
+			ml_key_info.links[i].gtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_GTK);
+		pos += RSN_SELECTOR_LEN;
+
+		*pos++ = (ml_key_info.links[i].gtkidx & 0x3) | (link_id << 4);
+
+		os_memcpy(pos, ml_key_info.links[i].pn, 6);
+		pos += 6;
+
+		os_memcpy(pos, ml_key_info.links[i].gtk,
+			  ml_key_info.links[i].gtk_len);
+		pos += ml_key_info.links[i].gtk_len;
+
+		i++;
+	}
+
+	if (!sm->mgmt_frame_prot)
+		return pos;
+
+	/* 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)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO IGTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO IGTK",
+				ml_key_info.links[i].igtk,
+				ml_key_info.links[i].igtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 2 + 1 +
+			sizeof(ml_key_info.links[i].ipn) +
+			ml_key_info.links[i].igtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_IGTK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Key ID */
+		*pos++ = ml_key_info.links[i].igtkidx;
+		*pos++ = 0;
+
+		/* Add the IPN */
+		os_memcpy(pos, ml_key_info.links[i].ipn,
+			  sizeof(ml_key_info.links[i].ipn));
+		pos += sizeof(ml_key_info.links[i].ipn);
+
+		*pos++ = ml_key_info.links[i].link_id << 4;
+
+		os_memcpy(pos, ml_key_info.links[i].igtk,
+			  ml_key_info.links[i].igtk_len);
+		pos += ml_key_info.links[i].igtk_len;
+
+		i++;
+	}
+
+	if (!sm->wpa_auth->conf.beacon_prot)
+		return pos;
+
+	/* 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)
+			continue;
+
+		wpa_printf(MSG_DEBUG, "RSN: MLO BIGTK: link=%u", link_id);
+		wpa_hexdump_key(MSG_DEBUG, "RSN: MLO BIGTK",
+				ml_key_info.links[i].bigtk,
+				ml_key_info.links[i].igtk_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 2 + 1 +
+			sizeof(ml_key_info.links[i].bipn) +
+			ml_key_info.links[i].igtk_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_BIGTK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Key ID */
+		*pos++ = ml_key_info.links[i].bigtkidx;
+		*pos++ = 0;
+
+		/* Add the BIPN */
+		os_memcpy(pos, ml_key_info.links[i].bipn,
+			  sizeof(ml_key_info.links[i].bipn));
+		pos += sizeof(ml_key_info.links[i].bipn);
+
+		*pos++ = ml_key_info.links[i].link_id << 4;
+
+		os_memcpy(pos, ml_key_info.links[i].bigtk,
+			  ml_key_info.links[i].igtk_len);
+		pos += ml_key_info.links[i].igtk_len;
+
+		i++;
+	}
+
+	return pos;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
+static size_t wpa_auth_ml_kdes_len(struct wpa_state_machine *sm)
+{
+	size_t kde_len = 0;
+
+#ifdef CONFIG_IEEE80211BE
+	unsigned int link_id;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/* For the MAC Address KDE */
+	kde_len = 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+
+	/* MLO Link KDE for each link */
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		kde_len += 2 + RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			sm->mld_links[link_id].rsne_len +
+			sm->mld_links[link_id].rsnxe_len;
+	}
+
+	kde_len += wpa_auth_ml_group_kdes_len(sm);
+#endif /* CONFIG_IEEE80211BE */
+
+	return kde_len;
+}
+
+
+static u8 * wpa_auth_ml_kdes(struct wpa_state_machine *sm, u8 *pos)
+{
+#ifdef CONFIG_IEEE80211BE
+	u8 link_id;
+
+	if (sm->mld_assoc_link_id < 0)
+		return pos;
+
+	wpa_printf(MSG_DEBUG, "RSN: MLD: Adding MAC Address KDE");
+	pos = wpa_add_kde(pos, RSN_KEY_DATA_MAC_ADDR,
+			  sm->own_mld_addr, ETH_ALEN, NULL, 0);
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		if (!sm->mld_links[link_id].valid)
+			continue;
+
+		wpa_printf(MSG_DEBUG,
+			   "RSN: MLO Link: link=%u, len=%zu", link_id,
+			   RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			   sm->mld_links[link_id].rsne_len +
+			   sm->mld_links[link_id].rsnxe_len);
+
+		*pos++ = WLAN_EID_VENDOR_SPECIFIC;
+		*pos++ = RSN_SELECTOR_LEN + 1 + ETH_ALEN +
+			sm->mld_links[link_id].rsne_len +
+			sm->mld_links[link_id].rsnxe_len;
+
+		RSN_SELECTOR_PUT(pos, RSN_KEY_DATA_MLO_LINK);
+		pos += RSN_SELECTOR_LEN;
+
+		/* Add the Link Information */
+		*pos = link_id;
+		if (sm->mld_links[link_id].rsne_len)
+			*pos |= RSN_MLO_LINK_KDE_LI_RSNE_INFO;
+		if (sm->mld_links[link_id].rsnxe_len)
+			*pos |= RSN_MLO_LINK_KDE_LI_RSNXE_INFO;
+
+		pos++;
+		os_memcpy(pos, sm->mld_links[link_id].own_addr, ETH_ALEN);
+		pos += ETH_ALEN;
+
+		if (sm->mld_links[link_id].rsne_len) {
+			os_memcpy(pos, sm->mld_links[link_id].rsne,
+				  sm->mld_links[link_id].rsne_len);
+			pos += sm->mld_links[link_id].rsne_len;
+		}
+
+		if (sm->mld_links[link_id].rsnxe_len) {
+			os_memcpy(pos, sm->mld_links[link_id].rsnxe,
+				  sm->mld_links[link_id].rsnxe_len);
+			pos += sm->mld_links[link_id].rsnxe_len;
+		}
+	}
+
+	pos = wpa_auth_ml_group_kdes(sm, pos);
+#endif /* CONFIG_IEEE80211BE */
+
+	return pos;
+}
+
+
 SM_STATE(WPA_PTK, PTKINITNEGOTIATING)
 {
 	u8 rsc[WPA_KEY_RSC_LEN], *_rsc, *gtk, *kde = NULL, *pos, stub_gtk[32];
@@ -3609,6 +4077,11 @@
 	u8 *wpa_ie_buf = NULL, *wpa_ie_buf2 = NULL;
 	u8 hdr[2];
 	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+#ifdef CONFIG_IEEE80211BE
+	bool is_mld = sm->mld_assoc_link_id >= 0;
+#else /* CONFIG_IEEE80211BE */
+	bool is_mld = false;
+#endif /* CONFIG_IEEE80211BE */
 
 	SM_ENTRY_MA(WPA_PTK, PTKINITNEGOTIATING, wpa_ptk);
 	sm->TimeoutEvt = false;
@@ -3714,6 +4187,7 @@
 		secure = 0;
 		gtk = NULL;
 		gtk_len = 0;
+		gtkidx = 0;
 		_rsc = NULL;
 		if (sm->rx_eapol_key_secure) {
 			/*
@@ -3757,13 +4231,17 @@
 		kde_len += 2 + RSN_SELECTOR_LEN + 2;
 #endif /* CONFIG_DPP2 */
 
+	kde_len += wpa_auth_ml_kdes_len(sm);
+
 	kde = os_malloc(kde_len);
 	if (!kde)
 		goto done;
 
 	pos = kde;
-	os_memcpy(pos, wpa_ie, wpa_ie_len);
-	pos += wpa_ie_len;
+	if (!is_mld) {
+		os_memcpy(pos, wpa_ie, wpa_ie_len);
+		pos += wpa_ie_len;
+	}
 #ifdef CONFIG_IEEE80211R_AP
 	if (wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
 		int res;
@@ -3787,7 +4265,7 @@
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_KEYID, hdr, 2, NULL, 0);
 	}
 
-	if (gtk) {
+	if (gtk && !is_mld) {
 		hdr[0] = gtkidx & 0x03;
 		pos = wpa_add_kde(pos, RSN_KEY_DATA_GROUPKEY, hdr, 2,
 				  gtk, gtk_len);
@@ -3867,6 +4345,8 @@
 	}
 #endif /* CONFIG_DPP2 */
 
+	pos = wpa_auth_ml_kdes(sm, pos);
+
 	wpa_send_eapol(sm->wpa_auth, sm,
 		       (secure ? WPA_KEY_INFO_SECURE : 0) |
 		       (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ?
@@ -3881,10 +4361,68 @@
 }
 
 
+static int wpa_auth_validate_ml_kdes_m4(struct wpa_state_machine *sm)
+{
+#ifdef CONFIG_IEEE80211BE
+	const struct ieee802_1x_hdr *hdr;
+	const struct wpa_eapol_key *key;
+	struct wpa_eapol_ie_parse kde;
+	const u8 *key_data, *mic;
+	u16 key_data_length;
+	size_t mic_len;
+
+	if (sm->mld_assoc_link_id < 0)
+		return 0;
+
+	/*
+	 * Note: last_rx_eapol_key length fields have already been validated in
+	 * wpa_receive().
+	 */
+	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
+
+	hdr = (const struct ieee802_1x_hdr *) sm->last_rx_eapol_key;
+	key = (const struct wpa_eapol_key *) (hdr + 1);
+	mic = (const u8 *) (key + 1);
+	key_data = mic + mic_len + 2;
+	key_data_length = WPA_GET_BE16(mic + mic_len);
+	if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) -
+	    sizeof(*key) - mic_len - 2)
+		return -1;
+
+	if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) {
+		wpa_auth_vlogger(sm->wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_INFO,
+				 "received EAPOL-Key msg 4/4 with invalid Key Data contents");
+		return -1;
+	}
+
+	/* MLD MAC address must be the same */
+	if (!kde.mac_addr ||
+	    os_memcmp(kde.mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Mismatching or missing MLD address in EAPOL-Key msg 4/4");
+		return -1;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: MLD address in EAPOL-Key msg 4/4: " MACSTR,
+		   MAC2STR(kde.mac_addr));
+#endif /* CONFIG_IEEE80211BE */
+
+	return 0;
+}
+
+
 SM_STATE(WPA_PTK, PTKINITDONE)
 {
 	SM_ENTRY_MA(WPA_PTK, PTKINITDONE, wpa_ptk);
 	sm->EAPOLKeyReceived = false;
+
+	if (wpa_auth_validate_ml_kdes_m4(sm) < 0) {
+		wpa_sta_disconnect(sm->wpa_auth, sm->addr,
+				   WLAN_REASON_PREV_AUTH_NOT_VALID);
+		return;
+	}
+
 	if (sm->Pair) {
 		enum wpa_alg alg = wpa_cipher_to_alg(sm->pairwise);
 		int klen = wpa_cipher_key_len(sm->pairwise);
@@ -4121,11 +4659,16 @@
 {
 	u8 rsc[WPA_KEY_RSC_LEN];
 	struct wpa_group *gsm = sm->group;
-	const u8 *kde;
+	const u8 *kde = NULL;
 	u8 *kde_buf = NULL, *pos, hdr[2];
 	size_t kde_len = 0;
 	u8 *gtk, stub_gtk[32];
 	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	bool is_mld = false;
+
+#ifdef CONFIG_IEEE80211BE
+	is_mld = sm->mld_assoc_link_id >= 0;
+#endif /* CONFIG_IEEE80211BE */
 
 	SM_ENTRY_MA(WPA_PTK_GROUP, REKEYNEGOTIATING, wpa_ptk_group);
 
@@ -4160,7 +4703,8 @@
 			return;
 		gtk = stub_gtk;
 	}
-	if (sm->wpa == WPA_VERSION_WPA2) {
+
+	if (sm->wpa == WPA_VERSION_WPA2 && !is_mld) {
 		kde_len = 2 + RSN_SELECTOR_LEN + 2 + gsm->GTK_len +
 			ieee80211w_kde_len(sm) + ocv_oci_len(sm);
 		kde_buf = os_malloc(kde_len);
@@ -4179,6 +4723,18 @@
 			return;
 		}
 		kde_len = pos - kde;
+#ifdef CONFIG_IEEE80211BE
+	} else if (sm->wpa == WPA_VERSION_WPA2 && is_mld) {
+		kde_len = wpa_auth_ml_group_kdes_len(sm);
+		if (kde_len) {
+			kde_buf = os_malloc(kde_len);
+			if (!kde_buf)
+				return;
+
+			kde = pos = kde_buf;
+			wpa_auth_ml_group_kdes(sm, pos);
+		}
+#endif /* CONFIG_IEEE80211BE */
 	} else {
 		kde = gtk;
 		kde_len = gsm->GTK_len;
@@ -6050,3 +6606,81 @@
 
 	eloop_register_timeout(0, 0, wpa_sm_call_step, sm, NULL);
 }
+
+
+void wpa_auth_set_ml_info(struct wpa_state_machine *sm, const u8 *mld_addr,
+			  u8 mld_assoc_link_id, struct mld_info *info)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct wpa_auth_ml_rsn_info ml_rsn_info;
+	unsigned int link_id, i;
+
+	if (!info)
+		return;
+
+	os_memset(sm->mld_links, 0, sizeof(sm->mld_links));
+
+	wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
+			"MLD: Initialization");
+
+	os_memcpy(sm->own_mld_addr, mld_addr, ETH_ALEN);
+	os_memcpy(sm->peer_mld_addr, info->common_info.mld_addr, ETH_ALEN);
+
+	sm->mld_assoc_link_id = mld_assoc_link_id;
+
+	os_memset(&ml_rsn_info, 0, sizeof(ml_rsn_info));
+
+	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		struct mld_link_info *link = &info->links[link_id];
+		struct mld_link *sm_link = &sm->mld_links[link_id];
+
+		sm_link->valid = link->valid;
+		if (!link->valid)
+			continue;
+
+		os_memcpy(sm_link->peer_addr, link->peer_addr, ETH_ALEN);
+		os_memcpy(sm_link->own_addr, link->local_addr, ETH_ALEN);
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: id=%u, addr=" MACSTR " peer=" MACSTR,
+			   link_id,
+			   MAC2STR(sm_link->own_addr),
+			   MAC2STR(sm_link->peer_addr));
+
+		if (link_id != mld_assoc_link_id)
+			sm->n_mld_affiliated_links++;
+
+		ml_rsn_info.links[i++].link_id = link_id;
+	}
+
+	ml_rsn_info.n_mld_links = i;
+
+	wpa_auth_get_ml_rsn_info(sm->wpa_auth, &ml_rsn_info);
+
+	for (i = 0; i < ml_rsn_info.n_mld_links; i++) {
+		struct mld_link *sm_link;
+		const u8 *rsn_ies;
+		u8 rsn_ies_len;
+
+		sm_link = &sm->mld_links[ml_rsn_info.links[i].link_id];
+		rsn_ies = ml_rsn_info.links[i].rsn_ies;
+		rsn_ies_len = ml_rsn_info.links[i].rsn_ies_len;
+
+		/* This should not really happen */
+		if (!rsn_ies || rsn_ies_len < 2 || rsn_ies[0] != WLAN_EID_RSN ||
+		    rsn_ies[1] + 2 > rsn_ies_len) {
+			wpa_printf(MSG_INFO, "WPA_AUTH: MLD: Invalid RSNE");
+			continue;
+		}
+
+		sm_link->rsne = rsn_ies;
+		sm_link->rsne_len = rsn_ies[1] + 2;
+
+		if (rsn_ies[1] + 2UL + 2UL < rsn_ies_len &&
+		    rsn_ies[rsn_ies[1] + 2] == WLAN_EID_RSNX) {
+			sm_link->rsnxe = rsn_ies + 2 + rsn_ies[1];
+			sm_link->rsnxe_len = sm_link->rsnxe[1] + 2;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 3b32fe3..57fda8a 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -15,6 +15,7 @@
 #include "common/ieee802_11_defs.h"
 
 struct vlan_description;
+struct mld_info;
 
 #define MAX_OWN_IE_OVERRIDE 256
 
@@ -290,6 +291,40 @@
 	WPA_EAPOL_keyDone, WPA_EAPOL_inc_EapolFramesTx
 } wpa_eapol_variable;
 
+struct wpa_auth_ml_rsn_info {
+	unsigned int n_mld_links;
+
+	struct wpa_auth_ml_link_rsn_info {
+		unsigned int link_id;
+		const u8 *rsn_ies;
+		size_t rsn_ies_len;
+	} links[MAX_NUM_MLD_LINKS];
+};
+
+struct wpa_auth_ml_key_info {
+	unsigned int n_mld_links;
+	bool mgmt_frame_prot;
+	bool beacon_prot;
+
+	struct wpa_auth_ml_link_key_info {
+		u8 link_id;
+
+		u8 gtkidx;
+		u8 gtk_len;
+		u8 pn[6];
+		const u8 *gtk;
+
+		u8 igtkidx;
+		u8 igtk_len;
+		const u8 *igtk;
+		u8 ipn[6];
+
+		u8 bigtkidx;
+		const u8 *bigtk;
+		u8 bipn[6];
+	} links[MAX_NUM_MLD_LINKS];
+};
+
 struct wpa_auth_callbacks {
 	void (*logger)(void *ctx, const u8 *addr, logger_level level,
 		       const char *txt);
@@ -309,6 +344,7 @@
 	int (*get_seqnum)(void *ctx, const u8 *addr, int idx, u8 *seq);
 	int (*send_eapol)(void *ctx, const u8 *addr, const u8 *data,
 			  size_t data_len, int encrypt);
+	int (*get_sta_count)(void *ctx);
 	int (*for_each_sta)(void *ctx, int (*cb)(struct wpa_state_machine *sm,
 						 void *ctx), void *cb_ctx);
 	int (*for_each_auth)(void *ctx, int (*cb)(struct wpa_authenticator *a,
@@ -357,6 +393,11 @@
 	int (*set_ltf_keyseed)(void *ctx, const u8 *addr, const u8 *ltf_keyseed,
 			       size_t ltf_keyseed_len);
 #endif /* CONFIG_PASN */
+#ifdef CONFIG_IEEE80211BE
+	int (*get_ml_rsn_info)(void *ctx, struct wpa_auth_ml_rsn_info *info);
+	int (*get_ml_key_info)(void *ctx, struct wpa_auth_ml_key_info *info);
+#endif /* CONFIG_IEEE80211BE */
+	int (*get_drv_flags)(void *ctx, u64 *drv_flags, u64 *drv_flags2);
 };
 
 struct wpa_authenticator * wpa_init(const u8 *addr,
@@ -599,4 +640,12 @@
 
 void wpa_auth_sta_radius_psk_resp(struct wpa_state_machine *sm, bool success);
 
+void wpa_auth_set_ml_info(struct wpa_state_machine *sm, const u8 *mld_addr,
+			  u8 mld_assoc_link_id, struct mld_info *info);
+void wpa_auth_ml_get_rsn_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_rsn_info *info);
+void wpa_auth_ml_get_key_info(struct wpa_authenticator *a,
+			      struct wpa_auth_ml_link_key_info *info,
+			      bool mgmt_frame_prot, bool beacon_prot);
+
 #endif /* WPA_AUTH_H */
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 2402ad9..4b16f62 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -2398,7 +2398,7 @@
 	wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos);
 	pos += 6;
 	*pos++ = bigtk_len;
-	bigtk = gsm->IGTK[gsm->GN_bigtk - 6];
+	bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
 	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_OSEN) {
 		/*
 		 * Provide unique random BIGTK to each OSEN STA to prevent use
@@ -2805,7 +2805,7 @@
 
 	ric_start = pos;
 	if (wpa_ft_parse_ies(req_ies, req_ies_len, &parse,
-			     sm->wpa_key_mgmt) == 0 && parse.ric) {
+			     sm->wpa_key_mgmt, false) == 0 && parse.ric) {
 		pos = wpa_ft_process_ric(sm, pos, end, parse.ric,
 					 parse.ric_len);
 		if (auth_alg == WLAN_AUTH_FT)
@@ -2821,8 +2821,10 @@
 	} else {
 		res = wpa_write_rsnxe(&sm->wpa_auth->conf, rsnxe,
 				      sizeof(rsnxe_buf));
-		if (res < 0)
-			return NULL;
+		if (res < 0) {
+			pos = NULL;
+			goto fail;
+		}
 		rsnxe_len = res;
 	}
 #ifdef CONFIG_TESTING_OPTIONS
@@ -2851,17 +2853,23 @@
 		       rsnie, rsnie_len,
 		       ric_start, ric_start ? pos - ric_start : 0,
 		       rsnxe_len ? rsnxe : NULL, rsnxe_len,
+		       NULL,
 		       fte_mic) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
-		return NULL;
+		pos = NULL;
+		goto fail;
 	}
 
 	os_free(sm->assoc_resp_ftie);
 	sm->assoc_resp_ftie = os_malloc(ftie_len);
-	if (!sm->assoc_resp_ftie)
-		return NULL;
+	if (!sm->assoc_resp_ftie) {
+		pos = NULL;
+		goto fail;
+	}
 	os_memcpy(sm->assoc_resp_ftie, ftie, ftie_len);
 
+fail:
+	wpa_ft_parse_ies_free(&parse);
 	return pos;
 }
 
@@ -3173,6 +3181,7 @@
 	const u8 *identity, *radius_cui;
 	size_t identity_len = 0, radius_cui_len = 0;
 	size_t pmk_r1_len, kdk_len, len;
+	int retval = WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 	*resp_ies = NULL;
 	*resp_ies_len = 0;
@@ -3183,7 +3192,7 @@
 	wpa_hexdump(MSG_DEBUG, "FT: Received authentication frame IEs",
 		    ies, ies_len);
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, 0)) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, 0, false)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
@@ -3194,17 +3203,20 @@
 		      sm->wpa_auth->conf.mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return WLAN_STATUS_INVALID_MDIE;
+		retval = WLAN_STATUS_INVALID_MDIE;
+		goto out;
 	}
 
 	if (!parse.ftie || parse.ftie_len < sizeof(struct rsn_ftie)) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid FTIE - no R0KH-ID");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	wpa_hexdump(MSG_DEBUG, "FT: STA R0KH-ID",
@@ -3214,11 +3226,12 @@
 
 	if (parse.rsn_pmkid == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	if (wpa_ft_set_key_mgmt(sm, &parse) < 0)
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 
 	wpa_hexdump(MSG_DEBUG, "FT: Requested PMKR0Name",
 		    parse.rsn_pmkid, WPA_PMK_NAME_LEN);
@@ -3228,12 +3241,14 @@
 		if (wpa_derive_pmk_r1_name(parse.rsn_pmkid,
 					   sm->wpa_auth->conf.r1_key_holder,
 					   sm->addr, pmk_r1_name, PMK_LEN) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		if (wpa_ft_psk_pmk_r1(sm, pmk_r1_name, pmk_r1, &pairwise,
 				      &vlan, &identity, &identity_len,
 				      &radius_cui, &radius_cui_len,
-				      &session_timeout) < 0)
-			return WLAN_STATUS_INVALID_PMKID;
+				      &session_timeout) < 0) {
+			retval = WLAN_STATUS_INVALID_PMKID;
+			goto out;
+		}
 		pmk_r1_len = PMK_LEN;
 		wpa_printf(MSG_DEBUG,
 			   "FT: Generated PMK-R1 for FT-PSK locally");
@@ -3284,10 +3299,12 @@
 	if (wpa_ft_pull_pmk_r1(sm, ies, ies_len, parse.rsn_pmkid) < 0) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: Did not have matching PMK-R1 and either unknown or blocked R0KH-ID or NAK from R0KH");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
-	return -1; /* Status pending */
+	retval = -1; /* Status pending */
+	goto out;
 
 pmk_r1_derived:
 	wpa_hexdump_key(MSG_DEBUG, "FT: Selected PMK-R1", pmk_r1, pmk_r1_len);
@@ -3299,7 +3316,7 @@
 	if (random_get_bytes(sm->ANonce, WPA_NONCE_LEN)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to get random data for "
 			   "ANonce");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	/* Now that we know the correct PMK-R1 length and as such, the length
@@ -3310,7 +3327,8 @@
 		ftie = (const struct rsn_ftie_sha512 *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3320,7 +3338,8 @@
 		ftie = (const struct rsn_ftie_sha384 *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3330,7 +3349,8 @@
 		ftie = (const struct rsn_ftie *) parse.ftie;
 		if (!ftie || parse.ftie_len < sizeof(*ftie)) {
 			wpa_printf(MSG_DEBUG, "FT: Invalid FTIE");
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 
 		os_memcpy(sm->SNonce, ftie->snonce, WPA_NONCE_LEN);
@@ -3352,14 +3372,14 @@
 			      sm->addr, sm->wpa_auth->addr, pmk_r1_name,
 			      &sm->PTK, ptk_name, parse.key_mgmt,
 			      pairwise, kdk_len) < 0)
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 
 #ifdef CONFIG_PASN
 	if (sm->wpa_auth->conf.secure_ltf &&
 	    ieee802_11_rsnx_capab(sm->rsnxe, WLAN_RSNX_CAPAB_SECURE_LTF) &&
 	    wpa_ltf_keyseed(&sm->PTK, parse.key_mgmt, pairwise)) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to derive LTF keyseed");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 #endif /* CONFIG_PASN */
 
@@ -3370,14 +3390,14 @@
 
 	if (wpa_ft_set_vlan(sm->wpa_auth, sm->addr, &vlan) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to configure VLAN");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 	if (wpa_ft_set_identity(sm->wpa_auth, sm->addr,
 				identity, identity_len) < 0 ||
 	    wpa_ft_set_radius_cui(sm->wpa_auth, sm->addr,
 				  radius_cui, radius_cui_len) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to configure identity/CUI");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 	wpa_ft_set_session_timeout(sm->wpa_auth, sm->addr, session_timeout);
 
@@ -3410,11 +3430,14 @@
 
 	*resp_ies_len = pos - *resp_ies;
 
-	return WLAN_STATUS_SUCCESS;
+	retval = WLAN_STATUS_SUCCESS;
+	goto out;
 fail:
 	os_free(*resp_ies);
 	*resp_ies = NULL;
-	return WLAN_STATUS_UNSPECIFIED_FAILURE;
+out:
+	wpa_ft_parse_ies_free(&parse);
+	return retval;
 }
 
 
@@ -3473,6 +3496,7 @@
 	const u8 *kck;
 	size_t kck_len;
 	struct wpa_auth_config *conf;
+	int retval = WLAN_STATUS_UNSPECIFIED_FAILURE;
 
 	if (sm == NULL)
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
@@ -3481,26 +3505,29 @@
 
 	wpa_hexdump(MSG_DEBUG, "FT: Reassoc Req IEs", ies, ies_len);
 
-	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->wpa_key_mgmt) < 0) {
+	if (wpa_ft_parse_ies(ies, ies_len, &parse, sm->wpa_key_mgmt,
+			     false) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to parse FT IEs");
 		return WLAN_STATUS_UNSPECIFIED_FAILURE;
 	}
 
 	if (parse.rsn == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No RSNIE in Reassoc Req");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (parse.rsn_pmkid == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No PMKID in RSNIE");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	if (os_memcmp_const(parse.rsn_pmkid, sm->pmk_r1_name, WPA_PMK_NAME_LEN)
 	    != 0) {
 		wpa_printf(MSG_DEBUG, "FT: PMKID in Reassoc Req did not match "
 			   "with the PMKR1Name derived from auth request");
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	mdie = (struct rsn_mdie *) parse.mdie;
@@ -3508,7 +3535,8 @@
 	    os_memcmp(mdie->mobility_domain, conf->mobility_domain,
 		      MOBILITY_DOMAIN_ID_LEN) != 0) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid MDIE");
-		return WLAN_STATUS_INVALID_MDIE;
+		retval = WLAN_STATUS_INVALID_MDIE;
+		goto out;
 	}
 
 	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_FT_SAE_EXT_KEY &&
@@ -3526,7 +3554,8 @@
 		wpa_printf(MSG_DEBUG,
 			   "FT: Invalid FTE (fte_mic_len=%zu mic_len=%zu)",
 			   parse.fte_mic_len, mic_len);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp(parse.fte_snonce, sm->SNonce, WPA_NONCE_LEN) != 0) {
@@ -3535,7 +3564,8 @@
 			    parse.fte_snonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected SNonce",
 			    sm->SNonce, WPA_NONCE_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp(parse.fte_anonce, sm->ANonce, WPA_NONCE_LEN) != 0) {
@@ -3544,12 +3574,14 @@
 			    parse.fte_anonce, WPA_NONCE_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected ANonce",
 			    sm->ANonce, WPA_NONCE_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R0KH-ID subelem in FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r0kh_id_len != sm->r0kh_id_len ||
@@ -3561,12 +3593,14 @@
 			    parse.r0kh_id, parse.r0kh_id_len);
 		wpa_hexdump(MSG_DEBUG, "FT: The current R0KH-ID",
 			    sm->r0kh_id, sm->r0kh_id_len);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.r1kh_id == NULL) {
 		wpa_printf(MSG_DEBUG, "FT: No R1KH-ID subelem in FTIE");
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (os_memcmp_const(parse.r1kh_id, conf->r1_key_holder,
@@ -3577,7 +3611,8 @@
 			    parse.r1kh_id, FT_R1KH_ID_LEN);
 		wpa_hexdump(MSG_DEBUG, "FT: Expected R1KH-ID",
 			    conf->r1_key_holder, FT_R1KH_ID_LEN);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.rsn_pmkid == NULL ||
@@ -3585,7 +3620,8 @@
 	{
 		wpa_printf(MSG_DEBUG, "FT: No matching PMKR1Name (PMKID) in "
 			   "RSNIE (pmkid=%d)", !!parse.rsn_pmkid);
-		return WLAN_STATUS_INVALID_PMKID;
+		retval = WLAN_STATUS_INVALID_PMKID;
+		goto out;
 	}
 
 	count = 3;
@@ -3597,7 +3633,7 @@
 		wpa_printf(MSG_DEBUG, "FT: Unexpected IE count in MIC "
 			   "Control: received %u expected %u",
 			   parse.fte_elem_count, count);
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (wpa_key_mgmt_fils(sm->wpa_key_mgmt)) {
@@ -3615,9 +3651,10 @@
 		       parse.ric, parse.ric_len,
 		       parse.rsnxe ? parse.rsnxe - 2 : NULL,
 		       parse.rsnxe ? parse.rsnxe_len + 2 : 0,
+		       NULL,
 		       mic) < 0) {
 		wpa_printf(MSG_DEBUG, "FT: Failed to calculate MIC");
-		return WLAN_STATUS_UNSPECIFIED_FAILURE;
+		goto out;
 	}
 
 	if (os_memcmp_const(mic, parse.fte_mic, mic_len) != 0) {
@@ -3636,7 +3673,8 @@
 		wpa_hexdump(MSG_MSGDUMP, "FT: RSNXE",
 			    parse.rsnxe ? parse.rsnxe - 2 : NULL,
 			    parse.rsnxe ? parse.rsnxe_len + 2 : 0);
-		return WLAN_STATUS_INVALID_FTIE;
+		retval = WLAN_STATUS_INVALID_FTIE;
+		goto out;
 	}
 
 	if (parse.fte_rsnxe_used &&
@@ -3645,7 +3683,8 @@
 	    !parse.rsnxe) {
 		wpa_printf(MSG_INFO,
 			   "FT: FTE indicated that STA uses RSNXE, but RSNXE was not included");
-		return -1; /* discard request */
+		retval = -1; /* discard request */
+		goto out;
 	}
 
 #ifdef CONFIG_OCV
@@ -3658,14 +3697,14 @@
 		if (wpa_channel_info(sm->wpa_auth, &ci) != 0) {
 			wpa_printf(MSG_WARNING,
 				   "Failed to get channel info to validate received OCI in (Re)Assoc Request");
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 		}
 
 		if (get_sta_tx_parameters(sm,
 					  channel_width_to_int(ci.chanwidth),
 					  ci.seg1_idx, &tx_chanwidth,
 					  &tx_seg1_idx) < 0)
-			return WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
 
 		res = ocv_verify_tx_params(parse.oci, parse.oci_len, &ci,
 					   tx_chanwidth, tx_seg1_idx);
@@ -3681,12 +3720,16 @@
 					OCV_FAILURE "addr=" MACSTR
 					" frame=ft-reassoc-req error=%s",
 					MAC2STR(sm->addr), ocv_errorstr);
-			return WLAN_STATUS_INVALID_FTIE;
+			retval = WLAN_STATUS_INVALID_FTIE;
+			goto out;
 		}
 	}
 #endif /* CONFIG_OCV */
 
-	return WLAN_STATUS_SUCCESS;
+	retval = WLAN_STATUS_SUCCESS;
+out:
+	wpa_ft_parse_ies_free(&parse);
+	return retval;
 }
 
 
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 9f6699b..82d79f2 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -513,7 +513,14 @@
 				       u8 *seq)
 {
 	struct hostapd_data *hapd = ctx;
-	return hostapd_get_seqnum(hapd->conf->iface, hapd, addr, idx, seq);
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && idx)
+		link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+	return hostapd_get_seqnum(hapd->conf->iface, hapd, addr, idx, link_id,
+				  seq);
 }
 
 
@@ -524,6 +531,11 @@
 	struct hostapd_data *hapd = ctx;
 	struct sta_info *sta;
 	u32 flags = 0;
+	int link_id = -1;
+
+#ifdef CONFIG_IEEE80211BE
+	link_id = hapd->conf->mld_ap ? hapd->mld_link_id : -1;
+#endif /* CONFIG_IEEE80211BE */
 
 #ifdef CONFIG_TESTING_OPTIONS
 	if (hapd->ext_eapol_frame_io) {
@@ -541,11 +553,24 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	sta = ap_get_sta(hapd, addr);
-	if (sta)
+	if (sta) {
 		flags = hostapd_sta_flags_to_drv(sta->flags);
+#ifdef CONFIG_IEEE80211BE
+		if (sta->mld_info.mld_sta && (sta->flags & WLAN_STA_AUTHORIZED))
+			link_id = -1;
+#endif /* CONFIG_IEEE80211BE */
+	}
 
 	return hostapd_drv_hapd_send_eapol(hapd, addr, data, data_len,
-					   encrypt, flags);
+					   encrypt, flags, link_id);
+}
+
+
+static int hostapd_wpa_auth_get_sta_count(void *ctx)
+{
+	struct hostapd_data *hapd = ctx;
+
+	return hapd->num_sta;
 }
 
 
@@ -1488,6 +1513,110 @@
 #endif /* CONFIG_PASN */
 
 
+#ifdef CONFIG_IEEE80211BE
+
+static int hostapd_wpa_auth_get_ml_rsn_info(void *ctx,
+					    struct wpa_auth_ml_rsn_info *info)
+{
+	struct hostapd_data *hapd = ctx;
+	unsigned int i, j;
+
+	wpa_printf(MSG_DEBUG, "WPA_AUTH: MLD: Get RSN info CB: n_mld_links=%u",
+		   info->n_mld_links);
+
+	if (!hapd->conf->mld_ap || !hapd->iface || !hapd->iface->interfaces)
+		return -1;
+
+	for (i = 0; i < info->n_mld_links; i++) {
+		unsigned int link_id = info->links[i].link_id;
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: Get link RSN CB: link_id=%u",
+			   link_id);
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			struct hostapd_iface *iface =
+				hapd->iface->interfaces->iface[j];
+
+			if (!iface->bss[0]->conf->mld_ap ||
+			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			    link_id != iface->bss[0]->mld_link_id)
+				continue;
+
+			wpa_auth_ml_get_rsn_info(iface->bss[0]->wpa_auth,
+						 &info->links[i]);
+			break;
+		}
+
+		if (j == hapd->iface->interfaces->count)
+			wpa_printf(MSG_DEBUG,
+				   "WPA_AUTH: MLD: link=%u not found", link_id);
+	}
+
+	return 0;
+}
+
+
+static int hostapd_wpa_auth_get_ml_key_info(void *ctx,
+					    struct wpa_auth_ml_key_info *info)
+{
+	struct hostapd_data *hapd = ctx;
+	unsigned int i, j;
+
+	wpa_printf(MSG_DEBUG, "WPA_AUTH: MLD: Get key info CB: n_mld_links=%u",
+		   info->n_mld_links);
+
+	if (!hapd->conf->mld_ap || !hapd->iface || !hapd->iface->interfaces)
+		return -1;
+
+	for (i = 0; i < info->n_mld_links; i++) {
+		u8 link_id = info->links[i].link_id;
+
+		wpa_printf(MSG_DEBUG,
+			   "WPA_AUTH: MLD: Get link info CB: link_id=%u",
+			   link_id);
+
+		for (j = 0; j < hapd->iface->interfaces->count; j++) {
+			struct hostapd_iface *iface =
+				hapd->iface->interfaces->iface[j];
+
+			if (!iface->bss[0]->conf->mld_ap ||
+			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
+			    link_id != iface->bss[0]->mld_link_id)
+				continue;
+
+			wpa_auth_ml_get_key_info(iface->bss[0]->wpa_auth,
+						 &info->links[i],
+						 info->mgmt_frame_prot,
+						 info->beacon_prot);
+			break;
+		}
+
+		if (j == hapd->iface->interfaces->count)
+			wpa_printf(MSG_DEBUG,
+				   "WPA_AUTH: MLD: link=%u not found", link_id);
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_IEEE80211BE */
+
+
+static int hostapd_wpa_auth_get_drv_flags(void *ctx,
+					  u64 *drv_flags, u64 *drv_flags2)
+{
+	struct hostapd_data *hapd = ctx;
+
+	if (drv_flags)
+		*drv_flags = hapd->iface->drv_flags;
+	if (drv_flags2)
+		*drv_flags2 = hapd->iface->drv_flags2;
+
+	return 0;
+}
+
+
 int hostapd_setup_wpa(struct hostapd_data *hapd)
 {
 	struct wpa_auth_config _conf;
@@ -1503,6 +1632,7 @@
 		.set_key = hostapd_wpa_auth_set_key,
 		.get_seqnum = hostapd_wpa_auth_get_seqnum,
 		.send_eapol = hostapd_wpa_auth_send_eapol,
+		.get_sta_count = hostapd_wpa_auth_get_sta_count,
 		.for_each_sta = hostapd_wpa_auth_for_each_sta,
 		.for_each_auth = hostapd_wpa_auth_for_each_auth,
 		.send_ether = hostapd_wpa_auth_send_ether,
@@ -1537,6 +1667,11 @@
 #ifdef CONFIG_PASN
 		.set_ltf_keyseed = hostapd_set_ltf_keyseed,
 #endif /* CONFIG_PASN */
+#ifdef CONFIG_IEEE80211BE
+		.get_ml_rsn_info = hostapd_wpa_auth_get_ml_rsn_info,
+		.get_ml_key_info = hostapd_wpa_auth_get_ml_key_info,
+#endif /* CONFIG_IEEE80211BE */
+		.get_drv_flags = hostapd_wpa_auth_get_drv_flags,
 	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index d401550..74ae5ad 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -172,6 +172,24 @@
 	void *eapol_status_cb_ctx1;
 	void *eapol_status_cb_ctx2;
 #endif /* CONFIG_TESTING_OPTIONS */
+
+#ifdef CONFIG_IEEE80211BE
+	u8 own_mld_addr[ETH_ALEN];
+	u8 peer_mld_addr[ETH_ALEN];
+	s8 mld_assoc_link_id;
+	u8 n_mld_affiliated_links;
+
+	struct mld_link {
+		bool valid;
+		u8 peer_addr[ETH_ALEN];
+		u8 own_addr[ETH_ALEN];
+
+		const u8 *rsne;
+		size_t rsne_len;
+		const u8 *rsnxe;
+		size_t rsnxe_len;
+	} mld_links[MAX_NUM_MLD_LINKS];
+#endif /* CONFIG_IEEE80211BE */
 };
 
 
diff --git a/src/ap/wpa_auth_ie.c b/src/ap/wpa_auth_ie.c
index 43ccec9..a5f2861 100644
--- a/src/ap/wpa_auth_ie.c
+++ b/src/ap/wpa_auth_ie.c
@@ -10,6 +10,7 @@
 
 #include "utils/common.h"
 #include "common/ieee802_11_defs.h"
+#include "drivers/driver.h"
 #include "eapol_auth/eapol_auth_sm.h"
 #include "ap_config.h"
 #include "ieee802_11.h"
@@ -212,6 +213,13 @@
 		num_suites++;
 	}
 #endif /* CONFIG_IEEE80211R_AP */
+#ifdef CONFIG_SHA384
+	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384) {
+		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA384);
+		pos += RSN_SELECTOR_LEN;
+		num_suites++;
+	}
+#endif /* CONFIG_SHA384 */
 	if (conf->wpa_key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256) {
 		RSN_SELECTOR_PUT(pos, RSN_AUTH_KEY_MGMT_802_1X_SHA256);
 		pos += RSN_SELECTOR_LEN;
@@ -705,6 +713,10 @@
 		else if (data.key_mgmt & WPA_KEY_MGMT_OSEN)
 			selector = RSN_AUTH_KEY_MGMT_OSEN;
 #endif /* CONFIG_HS20 */
+#ifdef CONFIG_SHA384
+		else if (data.key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384)
+			selector = RSN_AUTH_KEY_MGMT_802_1X_SHA384;
+#endif /* CONFIG_SHA384 */
 		wpa_auth->dot11RSNAAuthenticationSuiteSelected = selector;
 
 		selector = wpa_cipher_to_suite(WPA_PROTO_RSN,
@@ -787,6 +799,10 @@
 	else if (key_mgmt & WPA_KEY_MGMT_FT_PSK)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_FT_PSK;
 #endif /* CONFIG_IEEE80211R_AP */
+#ifdef CONFIG_SHA384
+	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA384)
+		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 	else if (key_mgmt & WPA_KEY_MGMT_IEEE8021X_SHA256)
 		sm->wpa_key_mgmt = WPA_KEY_MGMT_IEEE8021X_SHA256;
 	else if (key_mgmt & WPA_KEY_MGMT_PSK_SHA256)
@@ -998,11 +1014,24 @@
 	}
 
 #ifdef CONFIG_SAE
-	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE && data.num_pmkid &&
-	    !sm->pmksa) {
-		wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
-				 "No PMKSA cache entry found for SAE");
-		return WPA_INVALID_PMKID;
+	if (sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE ||
+	    sm->wpa_key_mgmt == WPA_KEY_MGMT_SAE_EXT_KEY) {
+		u64 drv_flags = 0;
+		u64 drv_flags2 = 0;
+		bool ap_sae_offload = false;
+
+		if (wpa_auth->cb->get_drv_flags &&
+		    wpa_auth->cb->get_drv_flags(wpa_auth->cb_ctx, &drv_flags,
+						&drv_flags2) == 0)
+			ap_sae_offload =
+				!!(drv_flags2 &
+				   WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP);
+
+		if (!ap_sae_offload && data.num_pmkid && !sm->pmksa) {
+			wpa_auth_vlogger(wpa_auth, sm->addr, LOGGER_DEBUG,
+					 "No PMKSA cache entry found for SAE");
+			return WPA_INVALID_PMKID;
+		}
 	}
 #endif /* CONFIG_SAE */