[wpa_supplicant] cumilative patch from commit 3a5d1a7e6

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

BYPASS_INCLUSIVE_LANGUAGE_REASON=Merged from open source

3a5d1a7e6 NAN: USD in hostapd
e3f9ab3c3 NAN: USD in wpa_supplicant
9eb0bc1f0 NAN: Unsynchronized service discovery (USD)
f2ea8791c NAN: Protocol definitions
4f557c594 Add os_reltime helpers to work with milliseconds
0b5d370c0 DPP: Fix DPP Action frame check for EVENT_RX_MGMT events
8fa52a797 FT: Allow wpa_supplicant to be configured to prepend PMKR1Name
9929426b9 FT: Allow PMKIDs from AssocReq to be in EAPOL-Key msg 2/4
560389997 AP MLD: Handle EAPOL only on the association link
7ba039ba1 AP MLD: Do not allow disabling first interface affiliated with an AP MLD
9a47ede87 AP MLD: Add support for hostapd_cli to disable/enable AP MLD
0102c5c60 hostapd: Do not use prefix matching for ENABLE/RELOAD/DISABLE
03e89de47 AP MLD: Process link info when handling new STA event with driver SME
d3d59967a Handle both HT40+ and HT40- allowed consistently in channel check
e650fa4d7 ACS: Handle ACS channel selected event in specified link
0e91a86ec ACS: Add link id if operating as an AP MLD
f972420e8 AP MLD: Fix AID allocation for legacy STA
fe36750b3 Add QCA vendor command to disassociate with peer
9fe2970ff OpenSSL: Use library functions for HPKE when possible
14c5f401f Remove forgotted STAKey related functionality in EAPOL-Key Request
3f60fcdd8 FILS: Fix EAPOL-Key request generation
b27086e6e Discard EAPOL-Key request without Secure=1
096794088 Discard EAPOL-Key Request frames during 4-way handshake
8037c1ad6 Move Key Replay Counter checks for EAPOL-Key frames to helper functions
2c6147404 Check Key Descriptor Version value earlier in the process
bd1e07899 Reject undefined Key Descriptor Version values explicitly
fff69bba1 Use more generic checks for Key Descriptor Version 2 and 3
74a25a660 Remove always true check on EAPOL-Key message in authenticator
9e9afd956 Extend frequency configuration to handle 6 GHz channel 2
8677844db Add a QCA vendor attribute to determine QCA device
576f46250 P2P: Accept P2P SD response without TX status
16a22ef34 nl80211: Increase the hard scan timeout for initial attempt
f20ca22dc DFS: Print the random channel list entry selection in debug print
d88fe8fe5 DFS: Fix a typo in a debug message
e3fb9e6f2 Send actual BTM capability when the driver takes care of BSS selection
6c334d9f0 nl80211: Set allowed frequency list per link for AP MLD
42cd2376f Enhance QCA vendor interface with new SAR version numbers
5ae8838a0 Make test code easier for static analyzers
c03377cf2 SAE: Fix resource leak on reading a separate password file
348c047af ACS: More consistent checking of the best channel pointer
5d54bf6fb Fix error path on Key Data field decryption
a4d599a53 FT: Fix architecture for RxKH loading from a file
0b95d1346 OpenSSL: Fix a memory leak on an error path
456bfec47 Avoid uninitialized seq number in debug print for testing functionality
c17900278 tests: Fix a memory leak in a module test
7184e63ce dbus: Avoid memory leak on error when signaling PropertiesChanged
4037c0ac1 nl80211: Fix wiphy event handling when the driver is deinitialized
1c90c8d24 DPP: Avoid a potential use-after-free on an error path in AP
32940c7a4 DPP: Fix use-after-free in connection status reporting when using TCP
9456adeeb DPP3: Fix potential use-after-free on push button bootstrap info
e3d6fce84 EAP-SIM/AKA peer: Fix use-after-free for privacy identity
fd71cae6c nl80211: Fix memory leak on libnl nl_cb
2814dbd6d OpenSSL: Fix a memory leak in crypto_ec_key_parse_priv()
aa1aa289c AP MLD: Optimize struct mld_link_info size
93eab9f0f Mark hostapd_gen_probe_resp() static
512b92524 AP MLD: Reduce struct mld_link_info size
b91572b30 AP MLD: Fix RADIUS deinit
fa79e46c7 AP MLD: Use a helper function to set whether a STA is a non-AP MLD
6bda0aca8 AP MLD: Use a helper function to check if a STA is a non-AP MLD
ee9375fb3 tests: Association comeback mechanism in wpa_supplicant
33179cd29 SME: Handle PMF association comeback when not handled in driver
0ef4b1e1d D-Bus: Add a signal for HS2.0 terms and conditions
4e3f6b847 wlantest: Add test vectors for S1G BIP
542ccf00b FT: Add control interface command to show configured RxKHs
392114a17 FT: Add dynamic reload of RxKH definitions from file
e94a7d794 FT: Move RxKH configuration clearing into a helper function
c69ce778f Fix building against OpenSSL 3
5589d62c5 nl80211: Avoid NL80211_WPA_VERSION_3 on older kernel versions
5ff6a2749 Remove the MLD specific exception for distinguishing EAPOL-Key msg 2 and 4
2314a3569 Testing functionality for EAPOL-Key Key Data field encryption
4abc37e67 Support Key Data field decryption for EAPOL-Key msg 2/4 and 4/4
f591732af Supplicant side testing functionality for EAPOL-Key Key Data field
f7a903654 Extend mechanism to distinguish EAPOL-Key msg 2/4 from 4/4
3547ed403 Authenticator side testing functionality for EAPOL-Key Key Data field
38719f113 Verify center frequency seg0/seg1 mapping result before use
acea0654f Initialize the variables before using it in channel update
af6e21faa P2P: Fix a logical error of workaround of extended listen failure
abc239a0b Get rid of multiple MIN macros
5290523db Apply a symmetrical bias against moving away from higher bands
05474b34b Decrease cross-threshold roam difficulty with bgscan_simple
73f06af14 wpa_supplicant: Do not invalidate PMKSA cache for bssid_* updates
05c167eb0 MLD: Fail connection if ML Authentication frame could not be parsed
a80dcf0e2 MLD: Read the correct BSSID from the RNR
906dade4f RRM: Handle scan TSF BSSID matching in context of MLD
12cdeb501 nl80211: Print driver name in debug output
197b440c4 nl80211: Print kernel version in debug output
5ae010aae nl80211: Avoid sending unsupported attributes
7fec9e7bc nl80211: Retrieve maxattr via genl for nl80211
f13683720 nl80211: Pass wiphy events to all affected interfaces
f9b3ecb0a DPP: Work arouind missing Auth Confirm ACK for testing
655794898 RRM: Fix the parsing of the Extended Request subelement in beacon req
b9983b35d MSCS: Process unsolciited MSCS Response frames
b427683bf MSCS: Extend MSCS response handling
db036b534 MSCS: Use a define for the MSCS Descriptor element fixed field length
2d83d224f Use ether_addr_equal() to compare whether two MAC addresses are equal
58027cfec WPS: Fix authorized MAC removal
95123ab3b Introduce ether_addr_equal()
76616a46b RSN: Fix (B)IGTK MLO KDE length print
f048e6626 wpa_supplicant: Don't assign pointer to bool
e0a2b3222 Fix compiler warnings on supplicant build with PASN but no FILS
8e8964cdb AP: Fix compilation warning in hapd_pasn_update_params()
a2fd63964 build: bgscan_simple depends on WNM
628f28610 trace: Fix compilation issue due to using an undefined symbol
cbcd056ec AP: Fix a typo in function name
594f85e30 mesh: Set the mld_link_id to -1 when adding a station
f40a58833 nl80211: Fix AP MLD MAC address on auth retry
38711a011 AP MLD: Remove link stations on a new station authentication
c6f519ff1 AP: Support deauthenticate/disassociate with MLD
9c937c889 AP: Move hostapd_ml_get_assoc_sta() to shared
ea401c168 AP MLD: Fix station lookup in hostapd_ml_get_assoc_sta()
e9f75a352 AP: Unify code handling deauthentication/disassociation
a1d7a9e3b build: Properly grab the libpcsclite cflags
9569315de Disable _FORTIFY_SOURCE when building with -O0
2112f0572 AP MLD: Correctly set the BSS parameters change count in RNR
0120d052d nl80211: Add NL80211_ATTR_MLO_LINK_ID for NL80211_CMD_REMAIN_ON_CHANNEL
bef417152 GAS: Accept GAS response using AP MLD MAC address
29814ee96 Extend pmf_in_use() to be aware of affiliated links on non-AP MLD
6ea81f323 nl80211: More detailed debug print for Management frame TX
9ccfc0d51 AP MLD: MLD address conversion for hostapd_drv_send_action_addr3_ap()
febb51bf8 AP MLD: Fix Association Response frame ACK handling
07f44a7c4 AP MLD: Prefer STA entry that has sta->wpa_sm initialized
0aeeaaaf1 Add QCA vendor command for flow policy configuration
064c233d1 AP: Fix a regression in indoor 6 GHz AP determination
196d6c83b Limit throughput estimation for HE 80/160 MHz based on VHT info
12c0f8ae3 Limit throughput estimation for HE 40 MHz based on HT info
98f3bd26d ACS: Extend the 320 MHz support
e6f2494c3 hostapd: Add eht_bw320_offset configuration option
733de8568 ACS: Fix not selecting the best channel in the segment
4881accbb ACS: Add HT40- support in the 2.4 GHz band
47e89935c dbus: Use PHY parameters from dbus or config for the GroupAdd command
fae12c4b1 Fix P2P_GROUP_ADD handling of the persistent group parameters
0143cf42c Move parse_freq() to be a common helper function
e3570f5e1 dbus: Use current_bss to get correct group BSSID and frequency on client
b91113e05 Support all PSK AKMs in case of AP mode PSK offload
4efb0247a Update definitions to point to the current IEEE 802.11 standard
121ccadeb AP: A helper function for determining whether the AP is an SP AP
24baffc8b AP: Share a common helper function for determining length of TPE elements
150ee0c06 AP: Add an additional TPE element when needed
3cbb3ac3f AP: Add TPE element for Indoor standard power AP
bcad7fec6 AP: Publish the correct PSD value in RNR TBTT information field
7065e5242 AP: Add configuration options for 6 GHz TPE Tx power
ada9083ac AP: Update the HE regulatory information AP types for the 6 GHz band
2d4f90521 RRM: Add support for including extended ID elements in beacon report
c88c08f0c SME: Remove comment in missing ML links handling
41e65efa3 ctrl_iface: Fix newline in print_ml()
a3a34c0eb HS 2.0: Remove useless debug print in non-Hotspot 2.0 cases
615835626 AP: Use the MLD MAC address for SAE authentication failures and testing
4a973718d Split hostapd_eid_rnr_iface() into two functions
0b55b8da3 AP: Add testing option to indicate an AP is disabled
2249b9f77 nl80211: Include disabled links indication in association command
5927455b8 MLD: Add support for disabled APs
981e8c1e4 nl80211: Add link ID to the queue parameters configuration print
409ebaaa1 AP: Support overriding EHT operation puncturing mask
26ad0be4f AP: Allow hex format for puncturing bitmap
799115a8e AP: Fix EHT MCS size validation for received element
14d7b9e37 ctrl_iface: Don't return -1 when dumping BSS information
12a957434 nl80211: Fix AP deinit path (link removal) in error cases
6c3438eef nl80211: Fix AP deinit path in error cases
6b7bb1001 Add QCA vendor attribute for EHT SCS traffic description support
f8657ea03 Add QCA vendor command for reporting firmware page fault informatin
ee00bbd29 Support VLAN offload with SAE password based selection
e748e50c6 SAE passwords from a separate file
40b255882 PASN: Select the latest available BSS entry for a BSSID
09d57e5f8 MBSSID: Element ID values in increasing order in Non-Inheritance element
88984bbb9 MLD STA: Update SAE PWE derivation in hunting-and-pecking loop case
c8dd70cfb Fix Multiple BSSID element length calculation
618df655a Use sta->vlan_id when needed for VLAN offload
afd306cf7 nl80211: Remove send_and_recv_msgs()
7c2f67cea nl80211: Add send_and_recv_resp() helper
ab506d777 nl80211: Add send_and_recv_cmd() helper
c73f9cde8 nl80211: Remove send_and_recv_msgs_connect_handle()
d2e6a395c nl80211: Do not set socket owner for NL80211_CMD_LEAVE_IBSS
35e58b741 nl80211: Remove send_and_recv_msgs_owner()
9823f4305 nl80211: Move control port attribute adding into more accurate location
0d619df8b nl80211: Use bss->nl_connect unconditionally
da0d51fee nl80211: Use socket cb instead of global->nl_cb in send_and_recv()
84fdc8cd8 nl80211: Accept NL80211_CMD_FRAME events in global context
828311ef3 AP MLD: More careful checking of Multi-Link element length fields
38a5ed5fd AP MLD: Skip unknown Multi-Link element subelements
62141825f tests: PASN authentication using driver event as trigger
2ab56694f Split ap_sta_set_authorized() into two steps
da8a38fec Remove unused assignment from Country element generation
33b5fc076 PKCS#1: Do not use pointer value after freeing
231d86ef9 OpenSSL: Check EVP_MAC_update() return value more consistently
a92694b00 OpenSSL: Check EVP_CIPHER_CTX_set_padding() return value more consistently
88bc6711a TDLS: Avoid unnecessary copying of the Link Identifier element
656cf50d8 More consistent sta pointer checks in handle_assoc()
29f38ebcf ACS: Check whether iface->current_mode is NULL before use
7fa840309 WNM: Skip current connection BSS when disassociate imminent is set
7a873c81e AP MLD: Do not schedule disconnection on BSS TM Request link removal
ec70d14f7 AP: MLD: Extend BSS transition management request for link removal
09988c435 WNM: Accept link removal BSS TM Request
80810929a WNM: Handle BTM request with Link Removal Imminent field set to 1
31e025c03 AP: When sending Action frames, use the AP MLD MAC address if needed
54e6c56d2 AP: Use AP MLD MAC address for terminating MLO association
7ee12fca4 WNM: Allow frames from AP MLD
0546f0e1b WNM: Use correct address when configured as AP MLD
1efdba5fd Handle PMKSA flush in the driver for SAE/OWE offload cases
6a793c5f2 bgscan: Fix bgscan_init() stub declaration
0af4c1478 hostapd: Check the bridge if ioctl SIOCBRADDIF fails
1b9006a8c Use the link BSSID to resolve current BSS for whether to roam check
c4dac077b wpa_supplicant: Remove redundant CONFIG_WNM in wnm_sta.c
d43a49a66 Remove a spurious tab in hostapd_eid_rnr()
a28ea8e51 AP: Fix setting MLD Parameters subfield in RNR element
07525cd5e Fix HE enabling for IBSS and mesh
40410c04f AP MLD: Channel switch for specific link
1b448c865 hostapd configuration file update using control interface
7dd7ae965 DFS: Change vht_capab according to user requested bandwidth
8920e0390 P2P: Force clearing of p2p-send-action radio work on P2P_STOP_FIND
8e294c3a2 P2P: Recover from successfully requested, but not started, listen
ceb7f65dc bgscan: Allow simple bgscan to do BTM queries
a83d3132e WNM: Define BSS transition management reason values
7ee7b046a nl80211: Use attribute NL80211_ATTR_BSSID to scan for specific BSSID
5bbc9462a tests: Test driver association ML link rejection flow
32434aa68 AP: Always include WPA_STA_AUTHORIZED in station flags mask
174a8fc41 AP MLD: Do not modify flags for link stations
0b5d11165 Fix MBO build with GAS dependency
58116877b EHT: Fix updating center freq segment 0 index for HE and VHT
40b04b703 Document Tunnel-Password encoding for passphrase/PSK
37d122c2f KaY: Make debug output consistent
f0cb82351 EHT: Add configuration for the EHT default PE duration
5a47bbc36 Add QCA vendor command to query transmit power information
9c2d6c423 Add QCA vendor attributes for link id for HT-scan/ACS command
7b6705579 Add QCA vendor commands for SDWF
20c82a270 Add an option to remove WMM-AC
32b5f7f50 Add an option to remove Robust AV (SCS, MSCS, QoS Management)
6ed8eba00 Add an option to remove RRM and supported operating class indication
4b80ad119 Populate the new beacon hint event to wpa_msg()
d8cae2d02 nl80211: Do not allow off channel when frequency is not specified
bbb0d3a40 mesh: Add for_each_sta implementation in wpa_auth_callbacks
4f69b4a31 mesh: Fix PMKSA cache entry addition with external PMKSA management
0302c3ad2 trace: binutils replaces bfd_hostptr_t with uintptr_t
033634019 Ignore missing set_secure_ranging_ctx callback for testing purposes
d54d0d898 AP MLD: Handle DFS in correct link
f1fee0d1f AP MLD: Handle channel switch event in correct link
fb6598864 nl80211: Add link ID when setting BSS attributes for AP MLD
5487d8d9e nl80211: Specify link ID when sending Management frames
859cbc396 nl80211: Remove links when stopping AP MLD in hostapd
780e72cc1 AP MLD: Do not include empty MLO KDEs
ecd9ea0c8 AP MLD: Do not access WPA authenticator object if not valid
21e8fcc80 nl80211: Add support for handling MLO removed links
cd79d834b trace: Add TEST_FAIL_TAG macro to allow more narrow matching
781e87c41 trace: Allow multiple failures in one test
e62d351ce trace: Document function pattern prefixes
5545d995b trace: Share common implementation for TEST_FAIL and TEST_ALLOC_FAIL
7d901dc7e trace: Use an array of skipped function names
e9bdecce4 Share TEST_FAIL/TEST_ALLOC_FAIL/GET_FAIL/GET_ALLOC_FAIL handler
2c89ca922 wpa_supplicant: Use wpa_msg() in bssid_ignore.c
6fc2d1357 AP: Get rid of wpa_auth_pmksa_add3()
e99670420 AP: Handle re-association from a non-AP MLD
a18f8ee0f AP MLD: Use MLD MAC address for SA query and response when needed
b9c81e200 MLD: Use MLD MAC address for deauthentication
f60287e6c AP: Avoid setting same MLD and link address
e5917e2a5 scan: MLD: Include SSID in ML probe request
b29ac99d5 scan: Include AP MLD ID in ML probe request if needed
d64ec9414 AP MLD: Don't include AP MLD ID in Beacon frames
3bde81175 ML: Add basic handling of ML probe requests
ecb22ba12 AP: MLO: Add helper to iterate all links of an AP MLD
db2bc0364 AP: Add parsing of ML probe requests
82453a348 AP: Split Probe Response frame IE generation into a separate function
6b5e00a80 AP: Use a struct for Probe Response generation in/out params
3cd377eb5 MLD: Ignore failed links from association attempt
4a1cd7f54 nl80211: Report link specific association failures from the kernel
6ba9b9440 nl80211: Add support to parse out link from error reply
e6eebd0d5 MLD: Do not consider ignored BSSs for links when parsing RNR info
92d8d1d76 MLD: Add a TEST_FAIL to fail one link in an MLD association.
846e65c7c MLD: Return status code for links when rejecting association
5af986c75 MLD: Also mark links as failed after association failure
c55a272f6 EHT: Define status codes from IEEE P802.11be/D4.0
d95838b79 AP: Add support for testing ML link removal
73a6f5c37 AP MLD: Make BSS parameter change variable
9160540ec wpa_supplicant: Fix ml_ie_len type in wpa_bss_parse_basic_ml_element()
cb90aa3ac wpa_supplicant: Remove duplicate logic in wpas_ml_element()
74b688430 wpa_supplicant: Add config parameters for MLD testing
6220fb52e dbus: Increase XML buffer size for an interface introspection data
b3aafd5a8 common: Simplify and avoid confusing defragmentation API
0f7d15dd3 ieee802_11_defs: Fix EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID
9ffebf758 SME: Drop old disassoc_while_authenticating workaround
645ec9b58 nl80211: Do a roundtrip to reset event supressions
c8b4ad70b tests: Set wpa_s->global for module tests
69ea73bfe nl80211: Update port authorized indication for MLO address
6f014c0d0 ACS: Add 320 MHz support for EHT
f0aea885f EHT: Fix HE Channel Center Freq Seg0/Seg1 for 6 GHz 320 MHz cases
07c03a655 EHT: Fix conditions for including EHT Operation Information field
01d95b75b SAE: prime_len means length in bytes not bits
a02585cef MBSSID: Use BIGTK from the transmitted BSS for beacon protection
a768556f7 Prefer SAE over PSK in WPA3-Personal transition mode cases
43184bf06 OWE: Optimize transition mode AP scan with owe_only=1 STA configuration
4ff287e92 P2P: Do not skip updating BSS table when frequency is changed
73b49016f Fix references to correct driver capability flag for PSK/OWE offloads
415839406 OpenSSL: Allow openssl_ciphers override with Suite B config on server
e9b13938a Add EHT mode support for ratemask configuration vendor command
239469eb1 Define new command in qca_tsf_cmd to get AP channel switch TSF time

Change-Id: I1540a39cd343b6fb9915fcc78e3f6bd2cb8eb0d9
Signed-off-by: Sunil Ravi <sunilravi@google.com>
diff --git a/hostapd/Android.mk b/hostapd/Android.mk
index 35ada83..35ae4fb 100644
--- a/hostapd/Android.mk
+++ b/hostapd/Android.mk
@@ -253,6 +253,8 @@
 OBJS += src/common/ocv.c
 endif
 
+NEED_AES_UNWRAP=y
+
 ifdef CONFIG_IEEE80211R
 L_CFLAGS += -DCONFIG_IEEE80211R -DCONFIG_IEEE80211R_AP
 OBJS += src/ap/wpa_auth_ft.c
@@ -599,6 +601,12 @@
 endif
 endif
 
+ifdef CONFIG_NAN_USD
+OBJS += src/common/nan_de.c
+OBJS += src/ap/nan_usd_ap.c
+L_CFLAGS += -DCONFIG_NAN_USD
+endif
+
 ifdef CONFIG_PASN
 L_CFLAGS += -DCONFIG_PASN
 L_CFLAGS += -DCONFIG_PTKSA_CACHE
diff --git a/hostapd/Makefile b/hostapd/Makefile
index a2adc85..274a82d 100644
--- a/hostapd/Makefile
+++ b/hostapd/Makefile
@@ -167,7 +167,7 @@
 
 
 ifdef CONFIG_CODE_COVERAGE
-CFLAGS += -O0 -fprofile-arcs -ftest-coverage
+CFLAGS += -O0 -fprofile-arcs -ftest-coverage -U_FORTIFY_SOURCE
 LIBS += -lgcov
 LIBS_c += -lgcov
 LIBS_h += -lgcov
@@ -276,6 +276,8 @@
 OBJS += ../src/common/ocv.o
 endif
 
+NEED_AES_UNWRAP=y
+
 ifdef CONFIG_IEEE80211R
 CFLAGS += -DCONFIG_IEEE80211R -DCONFIG_IEEE80211R_AP
 OBJS += ../src/ap/wpa_auth_ft.o
@@ -609,6 +611,12 @@
 endif
 endif
 
+ifdef CONFIG_NAN_USD
+OBJS += ../src/common/nan_de.o
+OBJS += ../src/ap/nan_usd_ap.o
+CFLAGS += -DCONFIG_NAN_USD
+endif
+
 ifdef CONFIG_PASN
 CFLAGS += -DCONFIG_PASN
 CFLAGS += -DCONFIG_PTKSA_CACHE
diff --git a/hostapd/android.config b/hostapd/android.config
index 1c4dab2..9d20c8d 100644
--- a/hostapd/android.config
+++ b/hostapd/android.config
@@ -264,3 +264,6 @@
 # certain percentage of probe requests or auth/(re)assoc frames.
 # Each test case requires a flag set in hostapd.conf or through hostapd_cli
 #CONFIG_TESTING_OPTIONS=y
+
+# Wi-Fi Aware unsynchronized service discovery (NAN USD)
+#CONFIG_NAN_USD=y
diff --git a/hostapd/config_file.c b/hostapd/config_file.c
index bfb152e..79cc1c2 100644
--- a/hostapd/config_file.c
+++ b/hostapd/config_file.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration file parser
- * Copyright (c) 2003-2018, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -1020,6 +1020,78 @@
 
 	return 0;
 }
+
+
+int hostapd_config_read_rxkh_file(struct hostapd_bss_config *conf,
+				  const char *fname)
+{
+	FILE *f;
+	char buf[256], *pos;
+	int line = 0, errors = 0;
+
+	if (!fname)
+		return 0;
+
+	f = fopen(fname, "r");
+	if (!f) {
+		wpa_printf(MSG_ERROR, "rxkh file '%s' not found.", fname);
+		return -1;
+	}
+
+	while (fgets(buf, sizeof(buf), f)) {
+		line++;
+
+		if (buf[0] == '#')
+			continue;
+		pos = buf;
+		while (*pos != '\0') {
+			if (*pos == '\n') {
+				*pos = '\0';
+				break;
+			}
+			pos++;
+		}
+		if (buf[0] == '\0')
+			continue;
+
+		pos = os_strchr(buf, '=');
+		if (!pos) {
+			wpa_printf(MSG_ERROR, "Line %d: Invalid line '%s'",
+				   line, buf);
+			errors++;
+			continue;
+		}
+		*pos = '\0';
+		pos++;
+
+		if (os_strcmp(buf, "r0kh") == 0) {
+			if (add_r0kh(conf, pos) < 0) {
+				wpa_printf(MSG_ERROR,
+					   "Line %d: Invalid r0kh '%s'",
+					   line, pos);
+				errors++;
+			}
+		} else if (os_strcmp(buf, "r1kh") == 0) {
+			if (add_r1kh(conf, pos) < 0) {
+				wpa_printf(MSG_ERROR,
+					   "Line %d: Invalid r1kh '%s'",
+					   line, pos);
+				errors++;
+			}
+		}
+	}
+
+	fclose(f);
+
+	if (errors) {
+		wpa_printf(MSG_ERROR,
+			   "%d errors in configuring RxKHs from '%s'",
+			   errors, fname);
+		return -1;
+	}
+	return 0;
+}
+
 #endif /* CONFIG_IEEE80211R_AP */
 
 
@@ -2159,6 +2231,7 @@
 
 
 #ifdef CONFIG_SAE
+
 static int parse_sae_password(struct hostapd_bss_config *bss, const char *val)
 {
 	struct sae_password_entry *pw;
@@ -2262,6 +2335,40 @@
 	os_free(pw);
 	return -1;
 }
+
+
+static int parse_sae_password_file(struct hostapd_bss_config *bss,
+				   const char *fname)
+{
+	FILE *f;
+	char buf[500], *pos;
+	unsigned int line = 0;
+
+	f = fopen(fname, "r");
+	if (!f) {
+		wpa_printf(MSG_ERROR, "sae_password_file '%s' not found.",
+			   fname);
+		return -1;
+	}
+
+	while (fgets(buf, sizeof(buf), f)) {
+		pos = os_strchr(buf, '\n');
+		if (pos)
+			*pos = '\0';
+		line++;
+		if (parse_sae_password(bss, buf)) {
+			wpa_printf(MSG_ERROR,
+				   "Invalid SAE password at line %d in '%s'",
+				   line, fname);
+			fclose(f);
+			return -1;
+		}
+	}
+
+	fclose(f);
+	return 0;
+}
+
 #endif /* CONFIG_SAE */
 
 
@@ -2311,6 +2418,24 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static int get_u16(const char *pos, int line, u16 *ret_val)
+{
+	char *end;
+	long int val = strtol(pos, &end, 0);
+
+	if (*end || val < 0 || val > 0xffff) {
+		wpa_printf(MSG_ERROR, "Line %d: Invalid value '%s'",
+			   line, pos);
+		return -1;
+	}
+
+	*ret_val = val;
+	return 0;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 static int hostapd_config_fill(struct hostapd_config *conf,
 			       struct hostapd_bss_config *bss,
 			       const char *buf, char *pos, int line)
@@ -3047,6 +3172,21 @@
 				   line, pos);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "rxkh_file") == 0) {
+		os_free(bss->rxkh_file);
+		bss->rxkh_file = os_strdup(pos);
+		if (!bss->rxkh_file) {
+			wpa_printf(MSG_ERROR, "Line %d: allocation failed",
+				   line);
+			return 1;
+		}
+		if (hostapd_config_read_rxkh_file(bss, pos)) {
+			wpa_printf(MSG_DEBUG,
+				   "Line %d: failed to read rxkh_file '%s'",
+				   line, pos);
+			/* Allow the file to be created later and read into
+			 * already operating AP context. */
+		}
 	} else if (os_strcmp(buf, "pmk_r1_push") == 0) {
 		bss->pmk_r1_push = atoi(pos);
 	} else if (os_strcmp(buf, "ft_over_ds") == 0) {
@@ -3624,6 +3764,18 @@
 		}
 	} else if (os_strcmp(buf, "he_6ghz_reg_pwr_type") == 0) {
 		conf->he_6ghz_reg_pwr_type = atoi(pos);
+		if (conf->he_6ghz_reg_pwr_type > HE_REG_INFO_6GHZ_AP_TYPE_MAX) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: invalid he_6ghz_reg_pwr_type value",
+				   line);
+			return 1;
+		}
+	} else if (os_strcmp(buf, "reg_def_cli_eirp_psd") == 0) {
+		conf->reg_def_cli_eirp_psd = atoi(pos);
+	} else if (os_strcmp(buf, "reg_sub_cli_eirp_psd") == 0) {
+		conf->reg_sub_cli_eirp_psd = atoi(pos);
+	} else if (os_strcmp(buf, "reg_def_cli_eirp") == 0) {
+		conf->reg_def_cli_eirp = atoi(pos);
 	} else if (os_strcmp(buf, "he_oper_chwidth") == 0) {
 		conf->he_oper_chwidth = atoi(pos);
 	} else if (os_strcmp(buf, "he_oper_centr_freq_seg0_idx") == 0) {
@@ -4296,6 +4448,16 @@
 		bss->eap_skip_prot_success = atoi(pos);
 	} else if (os_strcmp(buf, "delay_eapol_tx") == 0) {
 		conf->delay_eapol_tx = atoi(pos);
+	} else if (os_strcmp(buf, "eapol_m1_elements") == 0) {
+		if (parse_wpabuf_hex(line, buf, &bss->eapol_m1_elements, pos))
+			return 1;
+	} else if (os_strcmp(buf, "eapol_m3_elements") == 0) {
+		if (parse_wpabuf_hex(line, buf, &bss->eapol_m3_elements, pos))
+			return 1;
+	} else if (os_strcmp(buf, "eapol_m3_no_encrypt") == 0) {
+		bss->eapol_m3_no_encrypt = atoi(pos);
+	} else if (os_strcmp(buf, "test_assoc_comeback_type") == 0) {
+		bss->test_assoc_comeback_type = atoi(pos);
 #endif /* CONFIG_TESTING_OPTIONS */
 #ifdef CONFIG_SAE
 	} else if (os_strcmp(buf, "sae_password") == 0) {
@@ -4304,6 +4466,13 @@
 				   line);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "sae_password_file") == 0) {
+		if (parse_sae_password_file(bss, pos) < 0) {
+			wpa_printf(MSG_ERROR,
+				   "Line %d: Invalid sae_password in file",
+				   line);
+			return 1;
+		}
 #endif /* CONFIG_SAE */
 	} else if (os_strcmp(buf, "vendor_elements") == 0) {
 		if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos))
@@ -4770,8 +4939,11 @@
 		conf->eht_phy_capab.su_beamformee = atoi(pos);
 	} else if (os_strcmp(buf, "eht_mu_beamformer") == 0) {
 		conf->eht_phy_capab.mu_beamformer = atoi(pos);
+	} else if (os_strcmp(buf, "eht_default_pe_duration") == 0) {
+		conf->eht_default_pe_duration = atoi(pos);
 	} else if (os_strcmp(buf, "punct_bitmap") == 0) {
-		conf->punct_bitmap = atoi(pos);
+		if (get_u16(pos, line, &conf->punct_bitmap))
+			return 1;
 	} else if (os_strcmp(buf, "punct_acs_threshold") == 0) {
 		int val = atoi(pos);
 
@@ -4792,6 +4964,15 @@
 				   line);
 			return 1;
 		}
+	} else if (os_strcmp(buf, "eht_bw320_offset") == 0) {
+		conf->eht_bw320_offset = atoi(pos);
+#ifdef CONFIG_TESTING_OPTIONS
+	} else if (os_strcmp(buf, "eht_oper_puncturing_override") == 0) {
+		if (get_u16(pos, line, &bss->eht_oper_puncturing_override))
+			return 1;
+	} else if (os_strcmp(buf, "mld_indicate_disabled") == 0) {
+		bss->mld_indicate_disabled = atoi(pos);
+#endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_IEEE80211BE */
 	} else {
 		wpa_printf(MSG_ERROR,
diff --git a/hostapd/config_file.h b/hostapd/config_file.h
index c98bdb6..9ef6ac8 100644
--- a/hostapd/config_file.h
+++ b/hostapd/config_file.h
@@ -10,6 +10,8 @@
 #define CONFIG_FILE_H
 
 struct hostapd_config * hostapd_config_read(const char *fname);
+int hostapd_config_read_rxkh_file(struct hostapd_bss_config *conf,
+				  const char *fname);
 int hostapd_set_iface(struct hostapd_config *conf,
 		      struct hostapd_bss_config *bss, const char *field,
 		      char *value);
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 57c9997..a6b16c2 100644
--- a/hostapd/ctrl_iface.c
+++ b/hostapd/ctrl_iface.c
@@ -39,6 +39,7 @@
 #include "common/wpa_ctrl.h"
 #include "common/ptksa_cache.h"
 #include "common/hw_features_common.h"
+#include "common/nan_de.h"
 #include "crypto/tls.h"
 #include "drivers/driver.h"
 #include "eapol_auth/eapol_auth_sm.h"
@@ -63,6 +64,7 @@
 #include "ap/rrm.h"
 #include "ap/dpp_hostapd.h"
 #include "ap/dfs.h"
+#include "ap/nan_usd_ap.h"
 #include "wps/wps_defs.h"
 #include "wps/wps.h"
 #include "fst/fst_ctrl_iface.h"
@@ -1435,7 +1437,7 @@
 		pmk_match = PMK_LEN == pmk_len &&
 			os_memcmp(psk->psk, pmk, pmk_len) == 0;
 		sta_match = psk->group == 0 &&
-			os_memcmp(sta->addr, psk->addr, ETH_ALEN) == 0;
+			ether_addr_equal(sta->addr, psk->addr);
 		bss_match = psk->group == 1;
 
 		if (pmk_match && (sta_match || bss_match))
@@ -1474,6 +1476,79 @@
 }
 
 
+#ifdef CONFIG_IEEE80211R_AP
+
+static int hostapd_ctrl_iface_get_rxkhs(struct hostapd_data *hapd,
+					char *buf, size_t buflen)
+{
+	int ret, start_pos;
+	char *pos, *end;
+	struct ft_remote_r0kh *r0kh;
+	struct ft_remote_r1kh *r1kh;
+	struct hostapd_bss_config *conf = hapd->conf;
+
+	pos = buf;
+	end = buf + buflen;
+
+	for (r0kh = conf->r0kh_list; r0kh; r0kh=r0kh->next) {
+		start_pos = pos - buf;
+		ret = os_snprintf(pos, end - pos, "r0kh=" MACSTR " ",
+				  MAC2STR(r0kh->addr));
+		if (os_snprintf_error(end - pos, ret))
+			return start_pos;
+		pos += ret;
+		if (r0kh->id_len + 1 >= (size_t) (end - pos))
+			return start_pos;
+		os_memcpy(pos, r0kh->id, r0kh->id_len);
+		pos += r0kh->id_len;
+		*pos++ = ' ';
+		pos += wpa_snprintf_hex(pos, end - pos, r0kh->key,
+					sizeof(r0kh->key));
+		ret = os_snprintf(pos, end - pos, "\n");
+		if (os_snprintf_error(end - pos, ret))
+			return start_pos;
+		pos += ret;
+	}
+
+	for (r1kh = conf->r1kh_list; r1kh; r1kh=r1kh->next) {
+		start_pos = pos - buf;
+		ret = os_snprintf(pos, end - pos, "r1kh=" MACSTR " " MACSTR " ",
+			MAC2STR(r1kh->addr), MAC2STR(r1kh->id));
+		if (os_snprintf_error(end - pos, ret))
+			return start_pos;
+		pos += ret;
+		pos += wpa_snprintf_hex(pos, end - pos, r1kh->key,
+					sizeof(r1kh->key));
+		ret = os_snprintf(pos, end - pos, "\n");
+		if (os_snprintf_error(end - pos, ret))
+			return start_pos;
+		pos += ret;
+	}
+
+	return pos - buf;
+}
+
+
+static int hostapd_ctrl_iface_reload_rxkhs(struct hostapd_data *hapd)
+{
+	struct hostapd_bss_config *conf = hapd->conf;
+	int err;
+
+	hostapd_config_clear_rxkhs(conf);
+
+	err = hostapd_config_read_rxkh_file(conf, conf->rxkh_file);
+	if (err < 0) {
+		wpa_printf(MSG_ERROR, "Reloading RxKHs failed: %d",
+			   err);
+		return -1;
+	}
+
+	return 0;
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 #ifdef CONFIG_TESTING_OPTIONS
 
 static int hostapd_ctrl_iface_radar(struct hostapd_data *hapd, char *cmd)
@@ -2010,74 +2085,6 @@
 }
 
 
-static int hostapd_ctrl_test_alloc_fail(struct hostapd_data *hapd, char *cmd)
-{
-#ifdef WPA_TRACE_BFD
-	char *pos;
-
-	wpa_trace_fail_after = atoi(cmd);
-	pos = os_strchr(cmd, ':');
-	if (pos) {
-		pos++;
-		os_strlcpy(wpa_trace_fail_func, pos,
-			   sizeof(wpa_trace_fail_func));
-	} else {
-		wpa_trace_fail_after = 0;
-	}
-
-	return 0;
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int hostapd_ctrl_get_alloc_fail(struct hostapd_data *hapd,
-				       char *buf, size_t buflen)
-{
-#ifdef WPA_TRACE_BFD
-	return os_snprintf(buf, buflen, "%u:%s", wpa_trace_fail_after,
-			   wpa_trace_fail_func);
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int hostapd_ctrl_test_fail(struct hostapd_data *hapd, char *cmd)
-{
-#ifdef WPA_TRACE_BFD
-	char *pos;
-
-	wpa_trace_test_fail_after = atoi(cmd);
-	pos = os_strchr(cmd, ':');
-	if (pos) {
-		pos++;
-		os_strlcpy(wpa_trace_test_fail_func, pos,
-			   sizeof(wpa_trace_test_fail_func));
-	} else {
-		wpa_trace_test_fail_after = 0;
-	}
-
-	return 0;
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int hostapd_ctrl_get_fail(struct hostapd_data *hapd,
-				 char *buf, size_t buflen)
-{
-#ifdef WPA_TRACE_BFD
-	return os_snprintf(buf, buflen, "%u:%s", wpa_trace_test_fail_after,
-			   wpa_trace_test_fail_func);
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
 static int hostapd_ctrl_reset_pn(struct hostapd_data *hapd, const char *cmd)
 {
 	struct sta_info *sta;
@@ -2640,6 +2647,12 @@
 	if (ret)
 		return ret;
 
+	settings.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (iface->num_bss && iface->bss[0]->conf->mld_ap)
+		settings.link_id = iface->bss[0]->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
+
 	ret = hostapd_ctrl_check_freq_params(&settings.freq_params,
 					     settings.punct_bitmap);
 	if (ret) {
@@ -3449,6 +3462,416 @@
 #endif /* ANDROID */
 
 
+#ifdef CONFIG_IEEE80211BE
+
+static int hostapd_ctrl_iface_enable_mld(struct hostapd_iface *iface)
+{
+	unsigned int i;
+
+	if (!iface || !iface->bss[0]->conf->mld_ap) {
+		wpa_printf(MSG_ERROR,
+			   "Trying to enable AP MLD on an interface that is not affiliated with an AP MLD");
+		return -1;
+	}
+
+	for (i = 0; i < iface->interfaces->count; ++i) {
+		struct hostapd_iface *h_iface = iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h_iface->bss[0];
+		struct hostapd_bss_config *h_conf = h_hapd->conf;
+
+		if (!h_conf->mld_ap ||
+		    h_conf->mld_id != iface->bss[0]->conf->mld_id)
+			continue;
+
+		if (hostapd_enable_iface(h_iface)) {
+			wpa_printf(MSG_ERROR, "Enabling of AP MLD failed");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
+static void hostapd_disable_iface_bss(struct hostapd_iface *iface)
+{
+	unsigned int i;
+
+	for (i = 0; i < iface->num_bss; i++)
+		hostapd_bss_deinit_no_free(iface->bss[i]);
+}
+
+
+static int hostapd_ctrl_iface_disable_mld(struct hostapd_iface *iface)
+{
+	unsigned int i;
+	struct hostapd_iface *first_iface = NULL;
+
+	if (!iface || !iface->bss[0]->conf->mld_ap) {
+		wpa_printf(MSG_ERROR,
+			   "Trying to disable AP MLD on an interface that is not affiliated with an AP MLD.");
+		return -1;
+	}
+
+	/* First, disable BSSs before stopping beaconing and doing driver
+	 * deinit so that the broadcast Deauthentication frames go out. */
+
+	for (i = 0; i < iface->interfaces->count; ++i) {
+		struct hostapd_iface *h_iface = iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h_iface->bss[0];
+		struct hostapd_bss_config *h_conf = h_hapd->conf;
+
+		if (!h_conf->mld_ap ||
+		    h_conf->mld_id != iface->bss[0]->conf->mld_id)
+			continue;
+
+		if (!h_hapd->mld_first_bss) {
+			first_iface = h_iface;
+			continue;
+		}
+		hostapd_disable_iface_bss(iface);
+	}
+
+	if (first_iface)
+		hostapd_disable_iface_bss(first_iface);
+
+	/* Then, fully disable interfaces */
+
+	for (i = 0; i < iface->interfaces->count; ++i) {
+		struct hostapd_iface *h_iface = iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h_iface->bss[0];
+		struct hostapd_bss_config *h_conf = h_hapd->conf;
+
+		if (!h_conf->mld_ap ||
+		    h_conf->mld_id != iface->bss[0]->conf->mld_id ||
+		    !h_hapd->mld_first_bss)
+			continue;
+
+		if (hostapd_disable_iface(h_iface)) {
+			wpa_printf(MSG_ERROR, "Disabling AP MLD failed");
+			return -1;
+		}
+	}
+
+	if (first_iface && hostapd_disable_iface(first_iface)) {
+		wpa_printf(MSG_ERROR, "Disabling AP MLD failed");
+		return -1;
+	}
+
+	return 0;
+}
+
+
+#ifdef CONFIG_TESTING_OPTIONS
+static int hostapd_ctrl_iface_link_remove(struct hostapd_data *hapd, char *cmd,
+					  char *buf, size_t buflen)
+{
+	int ret;
+	u32 count = atoi(cmd);
+
+	if (!count)
+		count = 1;
+
+	ret = hostapd_link_remove(hapd, count);
+	if (ret == 0) {
+		ret = os_snprintf(buf, buflen, "%s\n", "OK");
+		if (os_snprintf_error(buflen, ret))
+			ret = -1;
+		else
+			ret = 0;
+	}
+
+	return ret;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+
+#ifdef CONFIG_NAN_USD
+
+static int hostapd_ctrl_nan_publish(struct hostapd_data *hapd, char *cmd,
+				    char *buf, size_t buflen)
+{
+	char *token, *context = NULL;
+	int publish_id;
+	struct nan_publish_params params;
+	const char *service_name = NULL;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+	enum nan_service_protocol_type srv_proto_type = 0;
+
+	os_memset(&params, 0, sizeof(params));
+	/* USD shall use both solicited and unsolicited transmissions */
+	params.unsolicited = true;
+	params.solicited = true;
+	/* USD shall require FSD without GAS */
+	params.fsd = true;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "service_name=", 13) == 0) {
+			service_name = token + 13;
+			continue;
+		}
+
+		if (os_strncmp(token, "ttl=", 4) == 0) {
+			params.ttl = atoi(token + 4);
+			continue;
+		}
+
+		if (os_strncmp(token, "srv_proto_type=", 15) == 0) {
+			srv_proto_type = atoi(token + 15);
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		if (os_strcmp(token, "solicited=0") == 0) {
+			params.solicited = false;
+			continue;
+		}
+
+		if (os_strcmp(token, "unsolicited=0") == 0) {
+			params.unsolicited = false;
+			continue;
+		}
+
+		if (os_strcmp(token, "fsd=0") == 0) {
+			params.fsd = false;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO, "CTRL: Invalid NAN_PUBLISH parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	publish_id = hostapd_nan_usd_publish(hapd, service_name, srv_proto_type,
+					     ssi, &params);
+	if (publish_id > 0)
+		ret = os_snprintf(buf, buflen, "%d", publish_id);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+
+static int hostapd_ctrl_nan_cancel_publish(struct hostapd_data *hapd,
+					   char *cmd)
+{
+	char *token, *context = NULL;
+	int publish_id = 0;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "publish_id=%i", &publish_id) == 1)
+			continue;
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_CANCEL_PUBLISH parameter: %s",
+			   token);
+		return -1;
+	}
+
+	if (publish_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_CANCEL_PUBLISH publish_id");
+		return -1;
+	}
+
+	hostapd_nan_usd_cancel_publish(hapd, publish_id);
+	return 0;
+}
+
+
+static int hostapd_ctrl_nan_update_publish(struct hostapd_data *hapd,
+					   char *cmd)
+{
+	char *token, *context = NULL;
+	int publish_id = 0;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "publish_id=%i", &publish_id) == 1)
+			continue;
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_UPDATE_PUBLISH parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	if (publish_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_UPDATE_PUBLISH publish_id");
+		goto fail;
+	}
+
+	ret = hostapd_nan_usd_update_publish(hapd, publish_id, ssi);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+
+static int hostapd_ctrl_nan_subscribe(struct hostapd_data *hapd, char *cmd,
+				      char *buf, size_t buflen)
+{
+	char *token, *context = NULL;
+	int subscribe_id;
+	struct nan_subscribe_params params;
+	const char *service_name = NULL;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+	enum nan_service_protocol_type srv_proto_type = 0;
+
+	os_memset(&params, 0, sizeof(params));
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "service_name=", 13) == 0) {
+			service_name = token + 13;
+			continue;
+		}
+
+		if (os_strcmp(token, "active=1") == 0) {
+			params.active = true;
+			continue;
+		}
+
+		if (os_strncmp(token, "ttl=", 4) == 0) {
+			params.ttl = atoi(token + 4);
+			continue;
+		}
+
+		if (os_strncmp(token, "srv_proto_type=", 15) == 0) {
+			srv_proto_type = atoi(token + 15);
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_SUBSCRIBE parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	subscribe_id = hostapd_nan_usd_subscribe(hapd, service_name,
+						 srv_proto_type, ssi,
+						 &params);
+	if (subscribe_id > 0)
+		ret = os_snprintf(buf, buflen, "%d", subscribe_id);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+
+static int hostapd_ctrl_nan_cancel_subscribe(struct hostapd_data *hapd,
+					     char *cmd)
+{
+	char *token, *context = NULL;
+	int subscribe_id = 0;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "subscribe_id=%i", &subscribe_id) == 1)
+			continue;
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_CANCEL_SUBSCRIBE parameter: %s",
+			   token);
+		return -1;
+	}
+
+	if (subscribe_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_CANCEL_SUBSCRIBE subscribe_id");
+		return -1;
+	}
+
+	hostapd_nan_usd_cancel_subscribe(hapd, subscribe_id);
+	return 0;
+}
+
+
+static int hostapd_ctrl_nan_transmit(struct hostapd_data *hapd, char *cmd)
+{
+	char *token, *context = NULL;
+	int handle = 0;
+	int req_instance_id = 0;
+	struct wpabuf *ssi = NULL;
+	u8 peer_addr[ETH_ALEN];
+	int ret = -1;
+
+	os_memset(peer_addr, 0, ETH_ALEN);
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "handle=%i", &handle) == 1)
+			continue;
+
+		if (sscanf(token, "req_instance_id=%i", &req_instance_id) == 1)
+			continue;
+
+		if (os_strncmp(token, "address=", 8) == 0) {
+			if (hwaddr_aton(token + 8, peer_addr) < 0)
+				return -1;
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_TRANSMIT parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	if (handle <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_TRANSMIT handle");
+		goto fail;
+	}
+
+	if (is_zero_ether_addr(peer_addr)) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_TRANSMIT address");
+		goto fail;
+	}
+
+	ret = hostapd_nan_usd_transmit(hapd, handle, ssi, NULL, peer_addr,
+				    req_instance_id);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+#endif /* CONFIG_NAN_USD */
+
+
 static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd,
 					      char *buf, char *reply,
 					      int reply_size,
@@ -3633,19 +4056,30 @@
 	} else if (os_strncmp(buf, "GET ", 4) == 0) {
 		reply_len = hostapd_ctrl_iface_get(hapd, buf + 4, reply,
 						   reply_size);
-	} else if (os_strncmp(buf, "ENABLE", 6) == 0) {
+	} else if (os_strcmp(buf, "ENABLE") == 0) {
 		if (hostapd_ctrl_iface_enable(hapd->iface))
 			reply_len = -1;
 	} else if (os_strcmp(buf, "RELOAD_WPA_PSK") == 0) {
 		if (hostapd_ctrl_iface_reload_wpa_psk(hapd))
 			reply_len = -1;
+#ifdef CONFIG_IEEE80211R_AP
+	} else if (os_strcmp(buf, "GET_RXKHS") == 0) {
+		reply_len = hostapd_ctrl_iface_get_rxkhs(hapd, reply,
+							 reply_size);
+	} else if (os_strcmp(buf, "RELOAD_RXKHS") == 0) {
+		if (hostapd_ctrl_iface_reload_rxkhs(hapd))
+			reply_len = -1;
+#endif /* CONFIG_IEEE80211R_AP */
 	} else if (os_strcmp(buf, "RELOAD_BSS") == 0) {
 		if (hostapd_ctrl_iface_reload_bss(hapd))
 			reply_len = -1;
-	} else if (os_strncmp(buf, "RELOAD", 6) == 0) {
+	} else if (os_strcmp(buf, "RELOAD_CONFIG") == 0) {
+		if (hostapd_reload_config(hapd->iface))
+			reply_len = -1;
+	} else if (os_strcmp(buf, "RELOAD") == 0) {
 		if (hostapd_ctrl_iface_reload(hapd->iface))
 			reply_len = -1;
-	} else if (os_strncmp(buf, "DISABLE", 7) == 0) {
+	} else if (os_strcmp(buf, "DISABLE") == 0) {
 		if (hostapd_ctrl_iface_disable(hapd->iface))
 			reply_len = -1;
 	} else if (os_strcmp(buf, "UPDATE_BEACON") == 0) {
@@ -3681,16 +4115,15 @@
 		if (hostapd_ctrl_iface_data_test_frame(hapd, buf + 16) < 0)
 			reply_len = -1;
 	} else if (os_strncmp(buf, "TEST_ALLOC_FAIL ", 16) == 0) {
-		if (hostapd_ctrl_test_alloc_fail(hapd, buf + 16) < 0)
+		if (testing_set_fail_pattern(true, buf + 16) < 0)
 			reply_len = -1;
 	} else if (os_strcmp(buf, "GET_ALLOC_FAIL") == 0) {
-		reply_len = hostapd_ctrl_get_alloc_fail(hapd, reply,
-							reply_size);
+		reply_len = testing_get_fail_pattern(true, reply, reply_size);
 	} else if (os_strncmp(buf, "TEST_FAIL ", 10) == 0) {
-		if (hostapd_ctrl_test_fail(hapd, buf + 10) < 0)
+		if (testing_set_fail_pattern(false, buf + 10) < 0)
 			reply_len = -1;
 	} else if (os_strcmp(buf, "GET_FAIL") == 0) {
-		reply_len = hostapd_ctrl_get_fail(hapd, reply, reply_size);
+		reply_len = testing_get_fail_pattern(false, reply, reply_size);
 	} else if (os_strncmp(buf, "RESET_PN ", 9) == 0) {
 		if (hostapd_ctrl_reset_pn(hapd, buf + 9) < 0)
 			reply_len = -1;
@@ -3983,6 +4416,26 @@
 			reply_len = -1;
 #endif /* CONFIG_DPP3 */
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+	} else if (os_strncmp(buf, "NAN_PUBLISH ", 12) == 0) {
+		reply_len = hostapd_ctrl_nan_publish(hapd, buf + 12, reply,
+						     reply_size);
+	} else if (os_strncmp(buf, "NAN_CANCEL_PUBLISH ", 19) == 0) {
+		if (hostapd_ctrl_nan_cancel_publish(hapd, buf + 19) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_UPDATE_PUBLISH ", 19) == 0) {
+		if (hostapd_ctrl_nan_update_publish(hapd, buf + 19) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_SUBSCRIBE ", 14) == 0) {
+		reply_len = hostapd_ctrl_nan_subscribe(hapd, buf + 14, reply,
+						       reply_size);
+	} else if (os_strncmp(buf, "NAN_CANCEL_SUBSCRIBE ", 21) == 0) {
+		if (hostapd_ctrl_nan_cancel_subscribe(hapd, buf + 21) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_TRANSMIT ", 13) == 0) {
+		if (hostapd_ctrl_nan_transmit(hapd, buf + 13) < 0)
+			reply_len = -1;
+#endif /* CONFIG_NAN_USD */
 #ifdef RADIUS_SERVER
 	} else if (os_strncmp(buf, "DAC_REQUEST ", 12) == 0) {
 		if (radius_server_dac_request(hapd->radius_srv, buf + 12) < 0)
@@ -4000,6 +4453,20 @@
 		reply_len = hostapd_ctrl_iface_driver_cmd(hapd, buf + 7, reply,
 							  reply_size);
 #endif /* ANDROID */
+#ifdef CONFIG_IEEE80211BE
+	} else if (os_strcmp(buf, "ENABLE_MLD") == 0) {
+		if (hostapd_ctrl_iface_enable_mld(hapd->iface))
+			reply_len = -1;
+	} else if (os_strcmp(buf, "DISABLE_MLD") == 0) {
+		if (hostapd_ctrl_iface_disable_mld(hapd->iface))
+			reply_len = -1;
+#ifdef CONFIG_TESTING_OPTIONS
+	} else if (os_strncmp(buf, "LINK_REMOVE ", 12) == 0) {
+		if (hostapd_ctrl_iface_link_remove(hapd, buf + 12,
+						   reply, reply_size))
+			reply_len = -1;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
 	} else {
 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
 		reply_len = 16;
@@ -4491,6 +4958,7 @@
 #ifdef CONFIG_DPP
 	dpp_global_clear(interfaces->dpp);
 #ifdef CONFIG_DPP3
+	interfaces->dpp_pb_bi = NULL;
 	{
 		int i;
 
diff --git a/hostapd/defconfig b/hostapd/defconfig
index 0a5ceee..5d769e9 100644
--- a/hostapd/defconfig
+++ b/hostapd/defconfig
@@ -424,3 +424,6 @@
 # DPP version 3 support (experimental and still changing; do not enable for
 # production use)
 #CONFIG_DPP3=y
+
+# Wi-Fi Aware unsynchronized service discovery (NAN USD)
+#CONFIG_NAN_USD=y
diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf
index f02cd92..1357649 100644
--- a/hostapd/hostapd.conf
+++ b/hostapd/hostapd.conf
@@ -980,10 +980,22 @@
 
 # 6 GHz Access Point type
 # This config is to set the 6 GHz Access Point type. Possible options are:
-# 0 = Indoor AP (default)
-# 1 = Standard Power AP
+# 0 = Indoor AP
+# 1 = Standard power AP
+# 2 = Very low power AP (default)
+# 3 = Indoor enabled AP
+# 4 = Indoor standard power AP
 # This has no impact for operation on other bands.
+# See IEEE P802.11-REVme/D4.0, Table E-12 (Regulatory Info subfield encoding)
+# for more details.
 #he_6ghz_reg_pwr_type=0
+#
+# 6 GHz Maximum Tx Power used in Transmit Power Envelope elements, where the
+# "Transmit Power Interpretation" is set to "Regulatory client EIRP PSD".
+# For Maximum Transmit Power Category subfield encoding set to default (0):
+#reg_def_cli_eirp_psd=-1
+# For Maximum Transmit Power Category subfield encoding set to subordinate (1):
+#reg_sub_cli_eirp_psd=-1
 
 # Unsolicited broadcast Probe Response transmission settings
 # This is for the 6 GHz band only. If the interval is set to a non-zero value,
@@ -1027,6 +1039,20 @@
 #eht_oper_chwidth (see vht_oper_chwidth)
 #eht_oper_centr_freq_seg0_idx
 
+#eht_default_pe_duration: The duration of PE field in EHT TB PPDU
+# 0 = PE field duration is the same as he_default_pe_duration (default)
+# 1 = PE field duration is 20 us
+#eht_default_pe_duration=0
+
+#eht_bw320_offset: For automatic channel selection (ACS) to indicate a preferred
+# 320 MHz channelization in EHT mode.
+# If the channel is decided or the bandwidth is not 320 MHz, this option is
+# meaningless.
+# 0 = auto-detect by hostapd
+# 1 = 320 MHz-1 (channel center frequency 31, 95, 159)
+# 2 = 320 MHz-2 (channel center frequency 63, 127, 191)
+#eht_bw320_offset=0
+
 # Disabled subchannel bitmap (16 bits) as per IEEE P802.11be/3.0,
 # Figure 9-1002c (EHT Operation Information field format). Each bit corresponds
 # to a 20 MHz channel, the lowest bit corresponds to the lowest frequency. A
@@ -1775,6 +1801,10 @@
 #	Tunnel-Password
 # 3 = ask RADIUS server during 4-way handshake if there is no locally
 #	configured PSK/passphrase for the STA
+#
+# The Tunnel-Password attribute in Access-Accept can contain either the
+# 8..63 character ASCII passphrase or a 64 hex character encoding of the PSK.
+#
 #wpa_psk_radius=0
 
 # Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
@@ -2032,6 +2062,10 @@
 #sae_password=really secret|mac=ff:ff:ff:ff:ff:ff
 #sae_password=example secret|mac=02:03:04:05:06:07|id=pw identifier
 #sae_password=example secret|vlanid=3|id=pw identifier
+#
+# SAE passwords can also be read from a separate file in which each line
+# contains and entry in the same format as sae_password uses.
+#sae_password_file=/tc/hostapd.sae_passwords
 
 # SAE threshold for anti-clogging mechanism (dot11RSNASAEAntiCloggingThreshold)
 # This parameter defines how many open SAE instances can be in progress at the
@@ -2261,6 +2295,12 @@
 # list and thus will receive push notifications.
 #r1kh=00:00:00:00:00:00 00:00:00:00:00:00 00112233445566778899aabbccddeeff
 
+# Optionally, the list of RxKHs can be read from a text file. Format is the same
+# as specified above. File shall contain both r0kh and r1kh. Once this variable
+# is set, RxKHs can be reloaded at runtime without bringing down an interface
+# using the RELOAD_RXKHS command.
+#rxkh_file=<path>
+
 # Timeout (seconds) for newly discovered R0KH/R1KH (see wildcard entries above)
 # Special values: 0 -> do not expire
 # Warning: do not cache implies no sequence number validation with wildcards
diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c
index 45497cd..8fb6119 100644
--- a/hostapd/hostapd_cli.c
+++ b/hostapd/hostapd_cli.c
@@ -1235,6 +1235,13 @@
 }
 
 
+static int hostapd_cli_cmd_reload_config(struct wpa_ctrl *ctrl, int argc,
+					 char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "RELOAD_CONFIG");
+}
+
+
 static int hostapd_cli_cmd_disable(struct wpa_ctrl *ctrl, int argc,
 				      char *argv[])
 {
@@ -1242,6 +1249,20 @@
 }
 
 
+static int hostapd_cli_cmd_enable_mld(struct wpa_ctrl *ctrl, int argc,
+				      char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "ENABLE_MLD");
+}
+
+
+static int hostapd_cli_cmd_disable_mld(struct wpa_ctrl *ctrl, int argc,
+				      char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "DISABLE_MLD");
+}
+
+
 static int hostapd_cli_cmd_update_beacon(struct wpa_ctrl *ctrl, int argc,
 				      char *argv[])
 {
@@ -1584,6 +1605,24 @@
 }
 
 
+#ifdef CONFIG_IEEE80211R_AP
+
+static int hostapd_cli_cmd_get_rxkhs(struct wpa_ctrl *ctrl, int argc,
+				     char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "GET_RXKHS");
+}
+
+
+static int hostapd_cli_cmd_reload_rxkhs(struct wpa_ctrl *ctrl, int argc,
+					char *argv[])
+{
+	return wpa_ctrl_command(ctrl, "RELOAD_RXKHS");
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 #ifdef ANDROID
 static int hostapd_cli_cmd_driver(struct wpa_ctrl *ctrl, int argc, char *argv[])
 {
@@ -1710,8 +1749,14 @@
 	  "= reload configuration for current interface" },
 	{ "reload_bss", hostapd_cli_cmd_reload_bss, NULL,
 	  "= reload configuration for current BSS" },
+	{ "reload_config", hostapd_cli_cmd_reload_config, NULL,
+	  "= reload configuration for current interface" },
 	{ "disable", hostapd_cli_cmd_disable, NULL,
 	  "= disable hostapd on current interface" },
+	{ "enable_mld", hostapd_cli_cmd_enable_mld, NULL,
+	  "= enable AP MLD to which the interface is affiliated" },
+	{ "disable_mld", hostapd_cli_cmd_disable_mld, NULL,
+	  "= disable AP MLD to which the interface is affiliated" },
 	{ "update_beacon", hostapd_cli_cmd_update_beacon, NULL,
 	  "= update Beacon frame contents\n"},
 	{ "erp_flush", hostapd_cli_cmd_erp_flush, NULL,
@@ -1795,6 +1840,12 @@
 	  "<addr> [req_mode=] <measurement request hexdump>  = send a Beacon report request to a station" },
 	{ "reload_wpa_psk", hostapd_cli_cmd_reload_wpa_psk, NULL,
 	  "= reload wpa_psk_file only" },
+#ifdef CONFIG_IEEE80211R_AP
+	{ "reload_rxkhs", hostapd_cli_cmd_reload_rxkhs, NULL,
+	  "= reload R0KHs and R1KHs" },
+	{ "get_rxkhs", hostapd_cli_cmd_get_rxkhs, NULL,
+	  "= get R0KHs and R1KHs" },
+#endif /* CONFIG_IEEE80211R_AP */
 #ifdef ANDROID
 	{ "driver", hostapd_cli_cmd_driver, NULL,
 	  "<driver sub command> [<hex formatted data>] = send driver command data" },
diff --git a/hostapd/main.c b/hostapd/main.c
index 615dc2f..0fe2d74 100644
--- a/hostapd/main.c
+++ b/hostapd/main.c
@@ -249,8 +249,12 @@
 	 * Use the configured MLD MAC address as the interface hardware address
 	 * if this AP is a part of an AP MLD.
 	 */
-	if (!is_zero_ether_addr(hapd->conf->mld_addr) && hapd->conf->mld_ap)
-		params.bssid = hapd->conf->mld_addr;
+	if (hapd->conf->mld_ap) {
+		if (!is_zero_ether_addr(hapd->conf->mld_addr))
+			params.bssid = hapd->conf->mld_addr;
+		else
+			params.bssid = NULL;
+	}
 #endif /* CONFIG_IEEE80211BE */
 
 	params.ifname = hapd->conf->iface;
@@ -340,6 +344,9 @@
 			return -1;
 		}
 
+		/* Initialize the BSS parameter change to 1 */
+		hapd->eht_mld_bss_param_change = 1;
+
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Set link_id=%u, mld_addr=" MACSTR
 			   ", own_addr=" MACSTR,
diff --git a/src/ap/acs.c b/src/ap/acs.c
index e3cfe1d..28b0ba7 100644
--- a/src/ap/acs.c
+++ b/src/ap/acs.c
@@ -245,6 +245,8 @@
 	ACS_BW40,
 	ACS_BW80,
 	ACS_BW160,
+	ACS_BW320_1,
+	ACS_BW320_2,
 };
 
 struct bw_item {
@@ -286,10 +288,20 @@
 	{ 6435, 6575, 111 }, { 6595, 6735, 143 },
 	{ 6755, 6895, 175 }, { 6915, 7055, 207 }, { -1, -1, -1 }
 };
+static const struct bw_item bw_320_1[] = {
+	{ 5955, 6255, 31 }, { 6275, 6575, 95 }, { 6595, 6895, 159 },
+	{ -1, -1, -1 }
+};
+static const struct bw_item bw_320_2[] = {
+	{ 6115, 6415, 63 }, { 6435, 6735, 127 }, { 6755, 7055, 191 },
+	{ -1, -1, -1 }
+};
 static const struct bw_item *bw_desc[] = {
 	[ACS_BW40] = bw_40,
 	[ACS_BW80] = bw_80,
 	[ACS_BW160] = bw_160,
+	[ACS_BW320_1] = bw_320_1,
+	[ACS_BW320_2] = bw_320_2,
 };
 
 
@@ -768,6 +780,42 @@
 #endif /* CONFIG_IEEE80211BE */
 
 
+static bool
+acs_usable_bw320_chan(struct hostapd_iface *iface,
+		      struct hostapd_channel_data *chan, int *bw320_offset)
+{
+	const char *bw320_str[] = { "320 MHz", "320 MHz-1", "320 MHz-2" };
+	int conf_bw320_offset = hostapd_get_bw320_offset(iface->conf);
+
+	*bw320_offset = 0;
+	switch (conf_bw320_offset) {
+	case 1:
+		if (acs_usable_bw_chan(chan, ACS_BW320_1))
+			*bw320_offset = 1;
+		break;
+	case 2:
+		if (acs_usable_bw_chan(chan, ACS_BW320_2))
+			*bw320_offset = 2;
+		break;
+	case 0:
+	default:
+		conf_bw320_offset = 0;
+		if (acs_usable_bw_chan(chan, ACS_BW320_1))
+			*bw320_offset = 1;
+		else if (acs_usable_bw_chan(chan, ACS_BW320_2))
+			*bw320_offset = 2;
+		break;
+	}
+
+	if (!*bw320_offset)
+		wpa_printf(MSG_DEBUG,
+			   "ACS: Channel %d: not allowed as primary channel for %s bandwidth",
+			   chan->chan, bw320_str[conf_bw320_offset]);
+
+	return *bw320_offset != 0;
+}
+
+
 static void
 acs_find_ideal_chan_mode(struct hostapd_iface *iface,
 			 struct hostapd_hw_modes *mode,
@@ -779,14 +827,18 @@
 	struct hostapd_channel_data *chan, *adj_chan = NULL, *best;
 	long double factor;
 	int i, j;
+	int bw320_offset = 0, ideal_bw320_offset = 0;
 	unsigned int k;
+	int secondary_channel = 1, freq_offset;
+
+	if (is_24ghz_mode(mode->mode))
+		secondary_channel = iface->conf->secondary_channel;
 
 	for (i = 0; i < mode->num_channels; i++) {
-		double total_weight;
+		double total_weight = 0;
 		struct acs_bias *bias, tmp_bias;
-		bool update_best = true;
 
-		best = chan = &mode->channels[i];
+		chan = &mode->channels[i];
 
 		/* Since in the current ACS implementation the first channel is
 		 * always a primary channel, skip channels not available as
@@ -818,7 +870,7 @@
 		    iface->conf->country[2] == 0x4f)
 			continue;
 
-		if (!chan_bw_allowed(chan, bw, 1, 1)) {
+		if (!chan_bw_allowed(chan, bw, secondary_channel != -1, 1)) {
 			wpa_printf(MSG_DEBUG,
 				   "ACS: Channel %d: BW %u is not supported",
 				   chan->chan, bw);
@@ -839,7 +891,8 @@
 		}
 
 		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
-		    (iface->conf->ieee80211ac || iface->conf->ieee80211ax)) {
+		    (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+		     iface->conf->ieee80211be)) {
 			if (hostapd_get_oper_chwidth(iface->conf) ==
 			    CONF_OPER_CHWIDTH_80MHZ &&
 			    !acs_usable_bw_chan(chan, ACS_BW80)) {
@@ -859,13 +912,25 @@
 			}
 		}
 
+		if (mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		    iface->conf->ieee80211be) {
+			if (hostapd_get_oper_chwidth(iface->conf) ==
+			    CONF_OPER_CHWIDTH_320MHZ &&
+			    !acs_usable_bw320_chan(iface, chan, &bw320_offset))
+				continue;
+		}
+
 		factor = 0;
-		if (acs_usable_chan(chan))
+		best = NULL;
+		if (acs_usable_chan(chan)) {
 			factor = chan->interference_factor;
-		total_weight = 1;
+			total_weight = 1;
+			best = chan;
+		}
 
 		for (j = 1; j < n_chans; j++) {
-			adj_chan = acs_find_chan(iface, chan->freq + (j * 20));
+			adj_chan = acs_find_chan(iface, chan->freq +
+						 j * secondary_channel * 20);
 			if (!adj_chan)
 				break;
 
@@ -876,16 +941,14 @@
 				break;
 			}
 
-			if (acs_usable_chan(adj_chan)) {
-				factor += adj_chan->interference_factor;
-				total_weight += 1;
-			} else {
-				update_best = false;
-			}
+			if (!acs_usable_chan(adj_chan))
+				continue;
+
+			factor += adj_chan->interference_factor;
+			total_weight += 1;
 
 			/* find the best channel in this segment */
-			if (update_best &&
-			    adj_chan->interference_factor <
+			if (!best || adj_chan->interference_factor <
 			    best->interference_factor)
 				best = adj_chan;
 		}
@@ -898,8 +961,9 @@
 
 		/* If the AP is in the 5 GHz or 6 GHz band, lets prefer a less
 		 * crowded primary channel if one was found in the segment */
-		if (iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
-		    chan != best) {
+		if (iface->current_mode &&
+		    iface->current_mode->mode == HOSTAPD_MODE_IEEE80211A &&
+		    best && chan != best) {
 			wpa_printf(MSG_DEBUG,
 				   "ACS: promoting channel %d over %d (less interference %Lg/%Lg)",
 				   best->chan, chan->chan,
@@ -912,8 +976,9 @@
 		 * channel interference factor. */
 		if (is_24ghz_mode(mode->mode)) {
 			for (j = 0; j < n_chans; j++) {
+				freq_offset = j * 20 * secondary_channel;
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) - 5);
+							 freq_offset - 5);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -921,7 +986,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) - 10);
+							 freq_offset - 10);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_NEXT_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -929,7 +994,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) + 5);
+							 freq_offset + 5);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -937,7 +1002,7 @@
 				}
 
 				adj_chan = acs_find_chan(iface, chan->freq +
-							 (j * 20) + 10);
+							 freq_offset + 10);
 				if (adj_chan && acs_usable_chan(adj_chan)) {
 					factor += ACS_NEXT_ADJ_WEIGHT *
 						adj_chan->interference_factor;
@@ -946,6 +1011,9 @@
 			}
 		}
 
+		if (total_weight == 0)
+			continue;
+
 		factor /= total_weight;
 
 		bias = NULL;
@@ -983,6 +1051,7 @@
 
 			*ideal_factor = factor;
 			*ideal_chan = chan;
+			ideal_bw320_offset = bw320_offset;
 
 #ifdef CONFIG_IEEE80211BE
 			if (iface->conf->ieee80211be)
@@ -993,9 +1062,13 @@
 		}
 
 		/* This channel would at least be usable */
-		if (!(*rand_chan))
+		if (!(*rand_chan)) {
 			*rand_chan = chan;
+			ideal_bw320_offset = bw320_offset;
+		}
 	}
+
+	hostapd_set_and_check_bw320_offset(iface->conf, ideal_bw320_offset);
 }
 
 
@@ -1022,19 +1095,12 @@
 		goto bw_selected;
 	}
 
-	/* TODO: HT40- support */
-
-	if (iface->conf->ieee80211n &&
-	    iface->conf->secondary_channel == -1) {
-		wpa_printf(MSG_ERROR, "ACS: HT40- is not supported yet. Please try HT40+");
-		return NULL;
-	}
-
 	if (iface->conf->ieee80211n &&
 	    iface->conf->secondary_channel)
 		n_chans = 2;
 
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		switch (hostapd_get_oper_chwidth(iface->conf)) {
 		case CONF_OPER_CHWIDTH_80MHZ:
 			n_chans = 4;
@@ -1042,6 +1108,9 @@
 		case CONF_OPER_CHWIDTH_160MHZ:
 			n_chans = 8;
 			break;
+		case CONF_OPER_CHWIDTH_320MHZ:
+			n_chans = 16;
+			break;
 		default:
 			break;
 		}
@@ -1091,7 +1160,8 @@
 	    acs_find_mode(iface, iface->freq) != HOSTAPD_MODE_IEEE80211A)
 		return;
 
-	wpa_printf(MSG_DEBUG, "ACS: Adjusting HT/VHT/HE secondary frequency");
+	wpa_printf(MSG_DEBUG,
+		   "ACS: Adjusting HT/VHT/HE/EHT secondary frequency");
 
 	for (i = 0; bw_desc[ACS_BW40][i].first != -1; i++) {
 		if (iface->freq == bw_desc[ACS_BW40][i].first)
@@ -1106,7 +1176,7 @@
 {
 	int center;
 
-	wpa_printf(MSG_DEBUG, "ACS: Adjusting VHT center frequency");
+	wpa_printf(MSG_DEBUG, "ACS: Adjusting center frequency");
 
 	switch (hostapd_get_oper_chwidth(iface->conf)) {
 	case CONF_OPER_CHWIDTH_USE_HT:
@@ -1125,11 +1195,28 @@
 	case CONF_OPER_CHWIDTH_160MHZ:
 		center = acs_get_bw_center_chan(iface->freq, ACS_BW160);
 		break;
+	case CONF_OPER_CHWIDTH_320MHZ:
+		switch (hostapd_get_bw320_offset(iface->conf)) {
+		case 1:
+			center = acs_get_bw_center_chan(iface->freq,
+							ACS_BW320_1);
+			break;
+		case 2:
+			center = acs_get_bw_center_chan(iface->freq,
+							ACS_BW320_2);
+			break;
+		default:
+			wpa_printf(MSG_INFO,
+				   "ACS: BW320 offset is not selected");
+			return;
+		}
+
+		break;
 	default:
 		/* TODO: How can this be calculated? Adjust
 		 * acs_find_ideal_chan() */
 		wpa_printf(MSG_INFO,
-			   "ACS: Only VHT20/40/80/160 is supported now");
+			   "ACS: Only VHT20/40/80/160/320 is supported now");
 		return;
 	}
 
@@ -1192,7 +1279,8 @@
 	iface->conf->punct_bitmap = ideal_chan->punct_bitmap;
 #endif /* CONFIG_IEEE80211BE */
 
-	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax) {
+	if (iface->conf->ieee80211ac || iface->conf->ieee80211ax ||
+	    iface->conf->ieee80211be) {
 		acs_adjust_secondary(iface);
 		acs_adjust_center_freq(iface);
 	}
diff --git a/src/ap/airtime_policy.c b/src/ap/airtime_policy.c
index abe817c..6844311 100644
--- a/src/ap/airtime_policy.c
+++ b/src/ap/airtime_policy.c
@@ -232,7 +232,7 @@
 	struct airtime_sta_weight *wt;
 
 	wt = hapd->conf->airtime_weight_list;
-	while (wt && os_memcmp(wt->addr, sta, ETH_ALEN) != 0)
+	while (wt && !ether_addr_equal(wt->addr, sta))
 		wt = wt->next;
 
 	return wt ? wt->weight : hapd->conf->airtime_weight;
diff --git a/src/ap/ap_config.c b/src/ap/ap_config.c
index 60d3566..445d963 100644
--- a/src/ap/ap_config.c
+++ b/src/ap/ap_config.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration helper functions
- * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -165,6 +165,7 @@
 
 #ifdef CONFIG_TESTING_OPTIONS
 	bss->sae_commit_status = -1;
+	bss->test_assoc_comeback_type = -1;
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #ifdef CONFIG_PASN
@@ -283,6 +284,10 @@
 	conf->he_6ghz_max_ampdu_len_exp = 7;
 	conf->he_6ghz_rx_ant_pat = 1;
 	conf->he_6ghz_tx_ant_pat = 1;
+	conf->he_6ghz_reg_pwr_type = HE_REG_INFO_6GHZ_AP_TYPE_VLP;
+	conf->reg_def_cli_eirp_psd = -1;
+	conf->reg_sub_cli_eirp_psd = -1;
+	conf->reg_def_cli_eirp = -1;
 #endif /* CONFIG_IEEE80211AX */
 
 	/* The third octet of the country string uses an ASCII space character
@@ -297,6 +302,8 @@
 	conf->airtime_update_interval = AIRTIME_DEFAULT_UPDATE_INTERVAL;
 #endif /* CONFIG_AIRTIME_POLICY */
 
+	hostapd_set_and_check_bw320_offset(conf, 0);
+
 	return conf;
 }
 
@@ -695,6 +702,33 @@
 }
 
 
+#ifdef CONFIG_IEEE80211R_AP
+
+void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf)
+{
+	struct ft_remote_r0kh *r0kh, *r0kh_prev;
+	struct ft_remote_r1kh *r1kh, *r1kh_prev;
+
+	r0kh = conf->r0kh_list;
+	conf->r0kh_list = NULL;
+	while (r0kh) {
+		r0kh_prev = r0kh;
+		r0kh = r0kh->next;
+		os_free(r0kh_prev);
+	}
+
+	r1kh = conf->r1kh_list;
+	conf->r1kh_list = NULL;
+	while (r1kh) {
+		r1kh_prev = r1kh;
+		r1kh = r1kh->next;
+		os_free(r1kh_prev);
+	}
+}
+
+#endif /* CONFIG_IEEE80211R_AP */
+
+
 static void hostapd_config_free_anqp_elem(struct hostapd_bss_config *conf)
 {
 	struct anqp_element *elem;
@@ -827,26 +861,9 @@
 	os_free(conf->time_zone);
 
 #ifdef CONFIG_IEEE80211R_AP
-	{
-		struct ft_remote_r0kh *r0kh, *r0kh_prev;
-		struct ft_remote_r1kh *r1kh, *r1kh_prev;
-
-		r0kh = conf->r0kh_list;
-		conf->r0kh_list = NULL;
-		while (r0kh) {
-			r0kh_prev = r0kh;
-			r0kh = r0kh->next;
-			os_free(r0kh_prev);
-		}
-
-		r1kh = conf->r1kh_list;
-		conf->r1kh_list = NULL;
-		while (r1kh) {
-			r1kh_prev = r1kh;
-			r1kh = r1kh->next;
-			os_free(r1kh_prev);
-		}
-	}
+	hostapd_config_clear_rxkhs(conf);
+	os_free(conf->rxkh_file);
+	conf->rxkh_file = NULL;
 #endif /* CONFIG_IEEE80211R_AP */
 
 #ifdef CONFIG_WPS
@@ -944,6 +961,8 @@
 	wpabuf_free(conf->rsnxe_override_ft);
 	wpabuf_free(conf->gtk_rsc_override);
 	wpabuf_free(conf->igtk_rsc_override);
+	wpabuf_free(conf->eapol_m1_elements);
+	wpabuf_free(conf->eapol_m3_elements);
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	os_free(conf->no_probe_resp_if_seen_on);
@@ -1131,10 +1150,9 @@
 	for (psk = conf->ssid.wpa_psk; psk != NULL; psk = psk->next) {
 		if (next_ok &&
 		    (psk->group ||
-		     (addr && os_memcmp(psk->addr, addr, ETH_ALEN) == 0) ||
+		     (addr && ether_addr_equal(psk->addr, addr)) ||
 		     (!addr && p2p_dev_addr &&
-		      os_memcmp(psk->p2p_dev_addr, p2p_dev_addr, ETH_ALEN) ==
-		      0))) {
+		      ether_addr_equal(psk->p2p_dev_addr, p2p_dev_addr)))) {
 			if (vlan_id)
 				*vlan_id = psk->vlan_id;
 			return psk->psk;
@@ -1558,6 +1576,10 @@
 			   "Cannot set ieee80211be without ieee80211ax");
 		return -1;
 	}
+
+	if (full_config)
+		hostapd_set_and_check_bw320_offset(conf,
+						   conf->eht_bw320_offset);
 #endif /* CONFIG_IEEE80211BE */
 
 	if (full_config && conf->mbssid && !conf->ieee80211ax) {
@@ -1750,7 +1772,7 @@
 	int i = 0;
 
 	while (i < *num) {
-		if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal((*acl)[i].addr, addr)) {
 			os_remove_in_array(*acl, *num, sizeof(**acl), i);
 			(*num)--;
 		} else {
diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h
index 99a6d18..69db16d 100644
--- a/src/ap/ap_config.h
+++ b/src/ap/ap_config.h
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration definitions and helpers functions
- * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -405,6 +405,7 @@
 	int ft_over_ds;
 	int ft_psk_generate_local;
 	int r1_max_key_lifetime;
+	char *rxkh_file;
 #endif /* CONFIG_IEEE80211R_AP */
 
 	char *ctrl_interface; /* directory for UNIX domain sockets */
@@ -704,6 +705,14 @@
 	unsigned int oci_freq_override_ft_assoc;
 	unsigned int oci_freq_override_fils_assoc;
 	unsigned int oci_freq_override_wnm_sleep;
+	struct wpabuf *eapol_m1_elements;
+	struct wpabuf *eapol_m3_elements;
+	bool eapol_m3_no_encrypt;
+	int test_assoc_comeback_type;
+
+#ifdef CONFIG_IEEE80211BE
+	u16 eht_oper_puncturing_override;
+#endif /* CONFIG_IEEE80211BE */
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #define MESH_ENABLED BIT(0)
@@ -948,6 +957,14 @@
 
 	/* The AP's MLD MAC address within the AP MLD */
 	u8 mld_addr[ETH_ALEN];
+
+#ifdef CONFIG_TESTING_OPTIONS
+	/*
+	 * If set indicate the AP as disabled in the RNR element included in the
+	 * other APs in the AP MLD.
+	 */
+	bool mld_indicate_disabled;
+#endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_IEEE80211BE */
 };
 
@@ -1139,6 +1156,19 @@
 	u8 he_6ghz_rx_ant_pat;
 	u8 he_6ghz_tx_ant_pat;
 	u8 he_6ghz_reg_pwr_type;
+
+	int reg_def_cli_eirp_psd;
+	int reg_sub_cli_eirp_psd;
+
+	/*
+	 * This value should be used when regulatory client EIRP PSD values
+	 * advertised by an AP that is an SP AP or an indoor SP AP are
+	 * insufficient to ensure that regulatory client limits on total EIRP
+	 * are always met for all transmission bandwidths within the bandwidth
+	 * of the AP’s BSS.
+	 */
+	int reg_def_cli_eirp;
+
 	bool require_he;
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1175,6 +1205,8 @@
 	struct eht_phy_capabilities_info eht_phy_capab;
 	u16 punct_bitmap; /* a bitmap of disabled 20 MHz channels */
 	u8 punct_acs_threshold;
+	u8 eht_default_pe_duration;
+	u8 eht_bw320_offset;
 #endif /* CONFIG_IEEE80211BE */
 
 	/* EHT enable/disable config from CHAN_SWITCH */
@@ -1242,7 +1274,8 @@
 #ifdef CONFIG_IEEE80211BE
 	if (conf->ieee80211be)
 		conf->eht_oper_centr_freq_seg0_idx = oper_centr_freq_seg0_idx;
-	if (center_idx_to_bw_6ghz(oper_centr_freq_seg0_idx) == 4)
+	if (is_6ghz_op_class(conf->op_class) &&
+	    center_idx_to_bw_6ghz(oper_centr_freq_seg0_idx) == 4)
 		oper_centr_freq_seg0_idx +=
 			conf->channel > oper_centr_freq_seg0_idx ? 16 : -16;
 #endif /* CONFIG_IEEE80211BE */
@@ -1274,6 +1307,43 @@
 	conf->vht_oper_centr_freq_seg1_idx = oper_centr_freq_seg1_idx;
 }
 
+static inline u8
+hostapd_get_bw320_offset(struct hostapd_config *conf)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) &&
+	    hostapd_get_oper_chwidth(conf) == CONF_OPER_CHWIDTH_320MHZ)
+		return conf->eht_bw320_offset;
+#endif /* CONFIG_IEEE80211BE */
+	return 0;
+}
+
+static inline void
+hostapd_set_and_check_bw320_offset(struct hostapd_config *conf,
+				   u8 bw320_offset)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (conf->ieee80211be && is_6ghz_op_class(conf->op_class) &&
+	    op_class_to_ch_width(conf->op_class) == CONF_OPER_CHWIDTH_320MHZ) {
+		if (conf->channel) {
+			/* If the channel is set, then calculate bw320_offset
+			 * by center frequency segment 0.
+			 */
+			u8 seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf);
+
+			conf->eht_bw320_offset = (seg0 - 31) % 64 ? 2 : 1;
+		} else {
+			/* If the channel is not set, bw320_offset indicates
+			 * preferred offset of 320 MHz.
+			 */
+			conf->eht_bw320_offset = bw320_offset;
+		}
+	} else {
+		conf->eht_bw320_offset = 0;
+	}
+#endif /* CONFIG_IEEE80211BE */
+}
+
 
 int hostapd_mac_comp(const void *a, const void *b);
 struct hostapd_config * hostapd_config_defaults(void);
@@ -1282,6 +1352,7 @@
 void hostapd_config_free_eap_user(struct hostapd_eap_user *user);
 void hostapd_config_free_eap_users(struct hostapd_eap_user *user);
 void hostapd_config_clear_wpa_psk(struct hostapd_wpa_psk **p);
+void hostapd_config_clear_rxkhs(struct hostapd_bss_config *conf);
 void hostapd_config_free_bss(struct hostapd_bss_config *conf);
 void hostapd_config_free(struct hostapd_config *conf);
 int hostapd_maclist_found(struct mac_acl_entry *list, int num_entries,
diff --git a/src/ap/ap_drv_ops.c b/src/ap/ap_drv_ops.c
index 8f9cc5b..60d66e4 100644
--- a/src/ap/ap_drv_ops.c
+++ b/src/ap/ap_drv_ops.c
@@ -265,9 +265,35 @@
 }
 
 
+static bool hostapd_sta_is_link_sta(struct hostapd_data *hapd,
+				    struct sta_info *sta)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta) &&
+	    sta->mld_assoc_link_id != hapd->mld_link_id)
+		return true;
+#endif /* CONFIG_IEEE80211BE */
+
+	return false;
+}
+
+
 int hostapd_set_authorized(struct hostapd_data *hapd,
 			   struct sta_info *sta, int authorized)
 {
+	/*
+	 * The WPA_STA_AUTHORIZED flag is relevant only for the MLD station and
+	 * not to the link stations (as the authorization is done between the
+	 * MLD peers). Thus, do not propagate the change to the driver for the
+	 * link stations.
+	 */
+	if (hostapd_sta_is_link_sta(hapd, sta)) {
+		wpa_printf(MSG_DEBUG,
+			   "%s: Do not update link station flags (" MACSTR ")",
+			   __func__, MAC2STR(sta->addr));
+		return 0;
+	}
+
 	if (authorized) {
 		return hostapd_sta_set_flags(hapd, sta->addr,
 					     hostapd_sta_flags_to_drv(
@@ -285,11 +311,24 @@
 {
 	int set_flags, total_flags, flags_and, flags_or;
 	total_flags = hostapd_sta_flags_to_drv(sta->flags);
-	set_flags = WPA_STA_SHORT_PREAMBLE | WPA_STA_WMM | WPA_STA_MFP;
-	if (((!hapd->conf->ieee802_1x && !hapd->conf->wpa) ||
-	     sta->auth_alg == WLAN_AUTH_FT) &&
-	    sta->flags & WLAN_STA_AUTHORIZED)
-		set_flags |= WPA_STA_AUTHORIZED;
+	set_flags = WPA_STA_SHORT_PREAMBLE | WPA_STA_WMM | WPA_STA_MFP |
+		WPA_STA_AUTHORIZED;
+
+	/*
+	 * All the station flags other than WPA_STA_SHORT_PREAMBLE are relevant
+	 * only for the MLD station and not to the link stations (as these flags
+	 * are related to the MLD state and not the link state). As for the
+	 * WPA_STA_SHORT_PREAMBLE, since the station is an EHT station, it must
+	 * support short preamble. Thus, do not propagate the change to the
+	 * driver for the link stations.
+	 */
+	if (hostapd_sta_is_link_sta(hapd, sta)) {
+		wpa_printf(MSG_DEBUG,
+			   "%s: Do not update link station flags (" MACSTR ")",
+			   __func__, MAC2STR(sta->addr));
+		return 0;
+	}
+
 	flags_or = total_flags & set_flags;
 	flags_and = total_flags | ~set_flags;
 	return hostapd_sta_set_flags(hapd, sta->addr, total_flags,
@@ -793,15 +832,21 @@
 			   const u8 *addr, int reason)
 {
 	int link_id = -1;
+	const u8 *own_addr = hapd->own_addr;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap)
+	if (hapd->conf->mld_ap) {
+		struct sta_info *sta = ap_get_sta(hapd, addr);
+
 		link_id = hapd->mld_link_id;
+		if (ap_sta_is_mld(hapd, sta))
+			own_addr = hapd->mld_addr;
+	}
 #endif /* CONFIG_IEEE80211BE */
 
 	if (!hapd->driver || !hapd->driver->sta_deauth || !hapd->drv_priv)
 		return 0;
-	return hapd->driver->sta_deauth(hapd->drv_priv, hapd->own_addr, addr,
+	return hapd->driver->sta_deauth(hapd->drv_priv, own_addr, addr,
 					reason, link_id);
 }
 
@@ -809,9 +854,20 @@
 int hostapd_drv_sta_disassoc(struct hostapd_data *hapd,
 			     const u8 *addr, int reason)
 {
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap) {
+		struct sta_info *sta = ap_get_sta(hapd, addr);
+
+		if (ap_sta_is_mld(hapd, sta))
+			own_addr = hapd->mld_addr;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (!hapd->driver || !hapd->driver->sta_disassoc || !hapd->drv_priv)
 		return 0;
-	return hapd->driver->sta_disassoc(hapd->drv_priv, hapd->own_addr, addr,
+	return hapd->driver->sta_disassoc(hapd->drv_priv, own_addr, addr,
 					  reason);
 }
 
@@ -826,22 +882,22 @@
 }
 
 
-int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
-			    unsigned int wait, const u8 *dst, const u8 *data,
-			    size_t len)
+static int hapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
+				unsigned int wait, const u8 *dst,
+				const u8 *data, size_t len, bool addr3_ap)
 {
+	const u8 *own_addr = hapd->own_addr;
 	const u8 *bssid;
 	const u8 wildcard_bssid[ETH_ALEN] = {
 		0xff, 0xff, 0xff, 0xff, 0xff, 0xff
 	};
+	struct sta_info *sta;
 
 	if (!hapd->driver || !hapd->driver->send_action || !hapd->drv_priv)
 		return 0;
 	bssid = hapd->own_addr;
-	if (!is_multicast_ether_addr(dst) &&
+	if (!addr3_ap && !is_multicast_ether_addr(dst) &&
 	    len > 0 && data[0] == WLAN_ACTION_PUBLIC) {
-		struct sta_info *sta;
-
 		/*
 		 * Public Action frames to a STA that is not a member of the BSS
 		 * shall use wildcard BSSID value.
@@ -849,7 +905,7 @@
 		sta = ap_get_sta(hapd, dst);
 		if (!sta || !(sta->flags & WLAN_STA_ASSOC))
 			bssid = wildcard_bssid;
-	} else if (is_broadcast_ether_addr(dst) &&
+	} else if (!addr3_ap && is_broadcast_ether_addr(dst) &&
 		   len > 0 && data[0] == WLAN_ACTION_PUBLIC) {
 		/*
 		 * The only current use case of Public Action frames with
@@ -858,9 +914,27 @@
 		 * so have to use the wildcard BSSID value.
 		 */
 		bssid = wildcard_bssid;
+#ifdef CONFIG_IEEE80211BE
+	} else if (hapd->conf->mld_ap) {
+		sta = ap_get_sta(hapd, dst);
+
+		if (ap_sta_is_mld(hapd, sta)) {
+			own_addr = hapd->mld_addr;
+			bssid = own_addr;
+		}
+#endif /* CONFIG_IEEE80211BE */
 	}
+
 	return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst,
-					 hapd->own_addr, bssid, data, len, 0);
+					 own_addr, bssid, data, len, 0);
+}
+
+
+int hostapd_drv_send_action(struct hostapd_data *hapd, unsigned int freq,
+			    unsigned int wait, const u8 *dst, const u8 *data,
+			    size_t len)
+{
+	return hapd_drv_send_action(hapd, freq, wait, dst, data, len, false);
 }
 
 
@@ -869,11 +943,7 @@
 				     unsigned int wait, const u8 *dst,
 				     const u8 *data, size_t len)
 {
-	if (hapd->driver == NULL || hapd->driver->send_action == NULL)
-		return 0;
-	return hapd->driver->send_action(hapd->drv_priv, freq, wait, dst,
-					 hapd->own_addr, hapd->own_addr, data,
-					 len, 0);
+	return hapd_drv_send_action(hapd, freq, wait, dst, data, len, true);
 }
 
 
@@ -1025,6 +1095,12 @@
 
 	os_memset(&params, 0, sizeof(params));
 	params.hw_mode = hapd->iface->conf->hw_mode;
+	params.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && hapd->iconf->ieee80211be &&
+	    !hapd->conf->disable_11be)
+		params.link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	/*
 	 * If no chanlist config parameter is provided, include all enabled
diff --git a/src/ap/ap_list.c b/src/ap/ap_list.c
index 20be7f8..13facab 100644
--- a/src/ap/ap_list.c
+++ b/src/ap/ap_list.c
@@ -55,7 +55,7 @@
 	struct ap_info *s;
 
 	s = iface->ap_hash[STA_HASH(ap)];
-	while (s != NULL && os_memcmp(s->addr, ap, ETH_ALEN) != 0)
+	while (s != NULL && !ether_addr_equal(s->addr, ap))
 		s = s->hnext;
 	return s;
 }
@@ -100,13 +100,13 @@
 
 	s = iface->ap_hash[STA_HASH(ap->addr)];
 	if (s == NULL) return;
-	if (os_memcmp(s->addr, ap->addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(s->addr, ap->addr)) {
 		iface->ap_hash[STA_HASH(ap->addr)] = s->hnext;
 		return;
 	}
 
 	while (s->hnext != NULL &&
-	       os_memcmp(s->hnext->addr, ap->addr, ETH_ALEN) != 0)
+	       !ether_addr_equal(s->hnext->addr, ap->addr))
 		s = s->hnext;
 	if (s->hnext != NULL)
 		s->hnext = s->hnext->hnext;
diff --git a/src/ap/beacon.c b/src/ap/beacon.c
index 1b5cea9..e50f0a0 100644
--- a/src/ap/beacon.c
+++ b/src/ap/beacon.c
@@ -239,12 +239,10 @@
 			continue; /* can use same entry */
 		}
 
-		if (start && prev) {
+		if (start && prev)
 			pos = hostapd_eid_country_add(hapd, pos, end,
 						      chan_spacing,
 						      start, prev);
-			start = NULL;
-		}
 
 		/* Start new group */
 		start = prev = chan;
@@ -565,19 +563,78 @@
 }
 
 
-static u8 * hostapd_gen_probe_resp(struct hostapd_data *hapd,
-				   const struct ieee80211_mgmt *req,
-				   int is_p2p, size_t *resp_len,
-				   const u8 *known_bss, u8 known_bss_len)
+static size_t he_elem_len(struct hostapd_data *hapd)
 {
+	size_t len = 0;
+
+#ifdef CONFIG_IEEE80211AX
+	if (!hapd->iconf->ieee80211ax || hapd->conf->disable_11ax)
+		return len;
+
+	len += 3 + sizeof(struct ieee80211_he_capabilities) +
+		3 + sizeof(struct ieee80211_he_operation) +
+		3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
+		3 + sizeof(struct ieee80211_spatial_reuse);
+	if (is_6ghz_op_class(hapd->iconf->op_class)) {
+		len += sizeof(struct ieee80211_he_6ghz_oper_info) +
+			3 + sizeof(struct ieee80211_he_6ghz_band_cap);
+		/* An additional Transmit Power Envelope element for
+		 * subordinate client */
+		if (he_reg_is_indoor(hapd->iconf->he_6ghz_reg_pwr_type))
+			len += 4;
+
+		/* An additional Transmit Power Envelope element for
+		 * default client with unit interpretation of regulatory
+		 * client EIRP */
+		if (hapd->iconf->reg_def_cli_eirp != -1 &&
+		    he_reg_is_sp(hapd->iconf->he_6ghz_reg_pwr_type))
+			len += 4;
+	}
+#endif /* CONFIG_IEEE80211AX */
+
+	return len;
+}
+
+
+struct probe_resp_params {
+	const struct ieee80211_mgmt *req;
+	bool is_p2p;
+
+	/* Generated IEs will be included inside an ML element */
+	bool is_ml_sta_info;
+	struct hostapd_data *mld_ap;
+	struct mld_info *mld_info;
+
 	struct ieee80211_mgmt *resp;
-	u8 *pos, *epos, *csa_pos;
-	size_t buflen;
+	size_t resp_len;
+	u8 *csa_pos;
+	u8 *ecsa_pos;
+	const u8 *known_bss;
+	u8 known_bss_len;
 
-	hapd = hostapd_mbssid_get_tx_bss(hapd);
+#ifdef CONFIG_IEEE80211AX
+	u8 *cca_pos;
+#endif /* CONFIG_IEEE80211AX */
+};
 
-#define MAX_PROBERESP_LEN 768
-	buflen = MAX_PROBERESP_LEN;
+
+static void hostapd_free_probe_resp_params(struct probe_resp_params *params)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (!params)
+		return;
+	ap_sta_free_sta_profile(params->mld_info);
+	os_free(params->mld_info);
+	params->mld_info = NULL;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+static size_t hostapd_probe_resp_elems_len(struct hostapd_data *hapd,
+					   struct probe_resp_params *params)
+{
+	size_t buflen = 0;
+
 #ifdef CONFIG_WPS
 	if (hapd->wps_probe_resp_ie)
 		buflen += wpabuf_len(hapd->wps_probe_resp_ie);
@@ -597,23 +654,7 @@
 			2 + sizeof(struct ieee80211_vht_operation);
 	}
 
-#ifdef CONFIG_IEEE80211AX
-	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
-		buflen += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation) +
-			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
-			3 + sizeof(struct ieee80211_spatial_reuse);
-		if (is_6ghz_op_class(hapd->iconf->op_class)) {
-			buflen += sizeof(struct ieee80211_he_6ghz_oper_info) +
-				3 + sizeof(struct ieee80211_he_6ghz_band_cap);
-			 /* An additional Transmit Power Envelope element for
-			  * subordinate client */
-			if (hapd->iconf->he_6ghz_reg_pwr_type ==
-			    HE_6GHZ_INDOOR_AP)
-				buflen += 4;
-		}
-	}
-#endif /* CONFIG_IEEE80211AX */
+	buflen += he_elem_len(hapd);
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
@@ -622,58 +663,45 @@
 		if (hapd->iconf->punct_bitmap)
 			buflen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
 
-		/*
-		 * TODO: Multi-Link element has variable length and can be
-		 * long based on the common info and number of per
-		 * station profiles. For now use 256.
-		 */
-		if (hapd->conf->mld_ap)
-			buflen += 256;
+		if (!params->is_ml_sta_info && hapd->conf->mld_ap) {
+			struct hostapd_data *ml_elem_ap =
+				params->mld_ap ? params->mld_ap : hapd;
+
+			buflen += hostapd_eid_eht_ml_beacon_len(
+				ml_elem_ap, params->mld_info, !!params->mld_ap);
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
 	buflen += hostapd_eid_mbssid_len(hapd, WLAN_FC_STYPE_PROBE_RESP, NULL,
-					 known_bss, known_bss_len, NULL);
-	buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP);
+					 params->known_bss,
+					 params->known_bss_len, NULL);
+	if (!params->is_ml_sta_info)
+		buflen += hostapd_eid_rnr_len(hapd, WLAN_FC_STYPE_PROBE_RESP);
 	buflen += hostapd_mbo_ie_len(hapd);
 	buflen += hostapd_eid_owe_trans_len(hapd);
 	buflen += hostapd_eid_dpp_cc_len(hapd);
 
-	resp = os_zalloc(buflen);
-	if (resp == NULL)
-		return NULL;
+	return buflen;
+}
 
-	epos = ((u8 *) resp) + MAX_PROBERESP_LEN;
 
-	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
-					   WLAN_FC_STYPE_PROBE_RESP);
-	/* Unicast the response to all requests on bands other than 6 GHz. For
-	 * the 6 GHz, unicast is used only if the actual SSID is not included in
-	 * the Beacon frames. Otherwise, broadcast response is used per IEEE
-	 * Std 802.11ax-2021, 26.17.2.3.2. Broadcast address is also used for
-	 * the Probe Response frame template for the unsolicited (i.e., not as
-	 * a response to a specific request) case. */
-	if (req && (!is_6ghz_op_class(hapd->iconf->op_class) ||
-		    hapd->conf->ignore_broadcast_ssid))
-		os_memcpy(resp->da, req->sa, ETH_ALEN);
-	else
-		os_memset(resp->da, 0xff, ETH_ALEN);
+static u8 * hostapd_probe_resp_fill_elems(struct hostapd_data *hapd,
+					  struct probe_resp_params *params,
+					  u8 *pos, size_t len)
+{
+	u8 *csa_pos;
+	u8 *epos;
 
-	os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN);
+	epos = pos + len;
 
-	os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN);
-	resp->u.probe_resp.beacon_int =
-		host_to_le16(hapd->iconf->beacon_int);
-
-	/* hardware or low-level driver will setup seq_ctrl and timestamp */
-	resp->u.probe_resp.capab_info =
-		host_to_le16(hostapd_own_capab_info(hapd));
-
-	pos = resp->u.probe_resp.variable;
-	*pos++ = WLAN_EID_SSID;
-	*pos++ = hapd->conf->ssid.ssid_len;
-	os_memcpy(pos, hapd->conf->ssid.ssid, hapd->conf->ssid.ssid_len);
-	pos += hapd->conf->ssid.ssid_len;
+	if (!params->is_ml_sta_info) {
+		*pos++ = WLAN_EID_SSID;
+		*pos++ = hapd->conf->ssid.ssid_len;
+		os_memcpy(pos, hapd->conf->ssid.ssid,
+			  hapd->conf->ssid.ssid_len);
+		pos += hapd->conf->ssid.ssid_len;
+	}
 
 	/* Supported rates */
 	pos = hostapd_eid_supp_rates(hapd, pos);
@@ -686,11 +714,18 @@
 	/* Power Constraint element */
 	pos = hostapd_eid_pwr_constraint(hapd, pos);
 
-	/* CSA IE */
-	csa_pos = hostapd_eid_csa(hapd, pos);
-	if (csa_pos != pos)
-		hapd->cs_c_off_proberesp = csa_pos - (u8 *) resp - 1;
-	pos = csa_pos;
+	/*
+	 * CSA IE
+	 * TODO: This should be included inside the ML sta profile
+	 */
+	if (!params->is_ml_sta_info) {
+		csa_pos = hostapd_eid_csa(hapd, pos);
+		if (csa_pos != pos)
+			params->csa_pos = csa_pos - 1;
+		else
+			params->csa_pos = NULL;
+		pos = csa_pos;
+	}
 
 	/* ERP Information element */
 	pos = hostapd_eid_erp_info(hapd, pos);
@@ -701,16 +736,23 @@
 	pos = hostapd_get_rsne(hapd, pos, epos - pos);
 	pos = hostapd_eid_bss_load(hapd, pos, epos - pos);
 	pos = hostapd_eid_mbssid(hapd, pos, epos, WLAN_FC_STYPE_PROBE_RESP, 0,
-				 NULL, known_bss, known_bss_len, NULL, NULL,
-				 NULL, 0);
+				 NULL, params->known_bss, params->known_bss_len,
+				 NULL, NULL, NULL, 0);
 	pos = hostapd_eid_rm_enabled_capab(hapd, pos, epos - pos);
 	pos = hostapd_get_mde(hapd, pos, epos - pos);
 
-	/* eCSA IE */
-	csa_pos = hostapd_eid_ecsa(hapd, pos);
-	if (csa_pos != pos)
-		hapd->cs_c_off_ecsa_proberesp = csa_pos - (u8 *) resp - 1;
-	pos = csa_pos;
+	/*
+	 * eCSA IE
+	 * TODO: This should be included inside the ML sta profile
+	 */
+	if (!params->is_ml_sta_info) {
+		csa_pos = hostapd_eid_ecsa(hapd, pos);
+		if (csa_pos != pos)
+			params->ecsa_pos = csa_pos - 1;
+		else
+			params->ecsa_pos = NULL;
+		pos = csa_pos;
+	}
 
 	pos = hostapd_eid_supported_op_classes(hapd, pos);
 	pos = hostapd_eid_ht_capabilities(hapd, pos);
@@ -720,7 +762,7 @@
 	 * when a list of known BSSes is included in the Probe Request frame. */
 	pos = hostapd_eid_ext_capab(hapd, pos,
 				    hapd->iconf->mbssid >= MBSSID_ENABLED &&
-				    !known_bss_len);
+				    !params->known_bss_len);
 
 	pos = hostapd_eid_time_adv(hapd, pos);
 	pos = hostapd_eid_time_zone(hapd, pos);
@@ -754,7 +796,8 @@
 
 	pos = hostapd_eid_wb_chsw_wrapper(hapd, pos);
 
-	pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP);
+	if (!params->is_ml_sta_info)
+		pos = hostapd_eid_rnr(hapd, pos, WLAN_FC_STYPE_PROBE_RESP);
 	pos = hostapd_eid_fils_indic(hapd, pos, 0);
 	pos = hostapd_get_rsnxe(hapd, pos, epos - pos);
 
@@ -768,7 +811,9 @@
 		/* BSS Color Change Announcement element */
 		cca_pos = hostapd_eid_cca(hapd, pos);
 		if (cca_pos != pos)
-			hapd->cca_c_off_proberesp = cca_pos - (u8 *) resp - 2;
+			params->cca_pos = cca_pos - 2;
+		else
+			params->cca_pos = NULL;
 		pos = cca_pos;
 
 		pos = hostapd_eid_spatial_reuse(hapd, pos);
@@ -779,8 +824,14 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
-		if (hapd->conf->mld_ap)
-			pos = hostapd_eid_eht_basic_ml(hapd, pos, NULL, true);
+		struct hostapd_data *ml_elem_ap =
+			params->mld_ap ? params->mld_ap : hapd;
+
+		if (ml_elem_ap->conf->mld_ap)
+			pos = hostapd_eid_eht_ml_beacon(
+				ml_elem_ap, params->mld_info,
+				pos, !!params->mld_ap);
+
 		pos = hostapd_eid_eht_capab(hapd, pos, IEEE80211_MODE_AP);
 		pos = hostapd_eid_eht_operation(hapd, pos);
 	}
@@ -807,7 +858,7 @@
 #endif /* CONFIG_WPS */
 
 #ifdef CONFIG_P2P
-	if ((hapd->conf->p2p & P2P_ENABLED) && is_p2p &&
+	if ((hapd->conf->p2p & P2P_ENABLED) && params->is_p2p &&
 	    hapd->p2p_probe_resp_ie) {
 		os_memcpy(pos, wpabuf_head(hapd->p2p_probe_resp_ie),
 			  wpabuf_len(hapd->p2p_probe_resp_ie));
@@ -824,9 +875,9 @@
 	pos = hostapd_eid_hs20_indication(hapd, pos);
 #endif /* CONFIG_HS20 */
 
-	pos = hostapd_eid_mbo(hapd, pos, (u8 *) resp + buflen - pos);
-	pos = hostapd_eid_owe_trans(hapd, pos, (u8 *) resp + buflen - pos);
-	pos = hostapd_eid_dpp_cc(hapd, pos, (u8 *) resp + buflen - pos);
+	pos = hostapd_eid_mbo(hapd, pos, epos - pos);
+	pos = hostapd_eid_owe_trans(hapd, pos, epos - pos);
+	pos = hostapd_eid_dpp_cc(hapd, pos, epos - pos);
 
 	if (hapd->conf->vendor_elements) {
 		os_memcpy(pos, wpabuf_head(hapd->conf->vendor_elements),
@@ -834,11 +885,171 @@
 		pos += wpabuf_len(hapd->conf->vendor_elements);
 	}
 
-	*resp_len = pos - (u8 *) resp;
-	return (u8 *) resp;
+	return pos;
 }
 
 
+static void hostapd_gen_probe_resp(struct hostapd_data *hapd,
+				   struct probe_resp_params *params)
+{
+	u8 *pos;
+	size_t buflen;
+
+	hapd = hostapd_mbssid_get_tx_bss(hapd);
+
+#define MAX_PROBERESP_LEN 768
+	buflen = MAX_PROBERESP_LEN;
+	buflen += hostapd_probe_resp_elems_len(hapd, params);
+	params->resp = os_zalloc(buflen);
+	if (!params->resp) {
+		params->resp_len = 0;
+		return;
+	}
+
+	params->resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
+						   WLAN_FC_STYPE_PROBE_RESP);
+	/* Unicast the response to all requests on bands other than 6 GHz. For
+	 * the 6 GHz, unicast is used only if the actual SSID is not included in
+	 * the Beacon frames. Otherwise, broadcast response is used per IEEE
+	 * Std 802.11ax-2021, 26.17.2.3.2. Broadcast address is also used for
+	 * the Probe Response frame template for the unsolicited (i.e., not as
+	 * a response to a specific request) case. */
+	if (params->req && (!is_6ghz_op_class(hapd->iconf->op_class) ||
+		    hapd->conf->ignore_broadcast_ssid))
+		os_memcpy(params->resp->da, params->req->sa, ETH_ALEN);
+	else
+		os_memset(params->resp->da, 0xff, ETH_ALEN);
+	os_memcpy(params->resp->sa, hapd->own_addr, ETH_ALEN);
+
+	os_memcpy(params->resp->bssid, hapd->own_addr, ETH_ALEN);
+	params->resp->u.probe_resp.beacon_int =
+		host_to_le16(hapd->iconf->beacon_int);
+
+	/* hardware or low-level driver will setup seq_ctrl and timestamp */
+	params->resp->u.probe_resp.capab_info =
+		host_to_le16(hostapd_own_capab_info(hapd));
+
+	pos = hostapd_probe_resp_fill_elems(hapd, params,
+					    params->resp->u.probe_resp.variable,
+					    buflen);
+
+	params->resp_len = pos - (u8 *) params->resp;
+}
+
+
+#ifdef CONFIG_IEEE80211BE
+static void hostapd_fill_probe_resp_ml_params(struct hostapd_data *hapd,
+					      struct probe_resp_params *params,
+					      const struct ieee80211_mgmt *mgmt,
+					      int mld_id, u16 links)
+{
+	struct probe_resp_params sta_info_params;
+	struct hostapd_data *link;
+	unsigned int probed_mld_id, i, j;
+
+	params->mld_ap = NULL;
+	params->mld_info = os_zalloc(sizeof(*params->mld_info));
+	if (!params->mld_info)
+		return;
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Got ML probe request with AP MLD ID %d for links %04x",
+		   mld_id, links);
+
+	/*
+	 * We want to include the AP MLD ID in the response if it was
+	 * included in the request.
+	 */
+	probed_mld_id = mld_id != -1 ? mld_id : hapd->conf->mld_id;
+
+	for_each_mld_link(link, i, j, hapd->iface->interfaces,
+			  probed_mld_id) {
+		struct mld_link_info *link_info;
+		size_t buflen;
+		u8 mld_link_id = link->mld_link_id;
+		u8 *epos;
+		u8 buf[EHT_ML_MAX_STA_PROF_LEN];
+
+		/*
+		 * Set mld_ap iff the ML probe request explicitly
+		 * requested a specific MLD ID. In that case, the targeted
+		 * AP may have been a nontransmitted BSSID on the same
+		 * interface.
+		 */
+		if (mld_id != -1 && link->iface == hapd->iface)
+			params->mld_ap = link;
+
+		/* Never duplicate main Probe Response frame body */
+		if (link == hapd)
+			continue;
+
+		/* Only include requested links */
+		if (!(BIT(mld_link_id) & links))
+			continue;
+
+		link_info = &params->mld_info->links[mld_link_id];
+
+		sta_info_params.req = params->req;
+		sta_info_params.is_p2p = false;
+		sta_info_params.is_ml_sta_info = true;
+		sta_info_params.mld_ap = NULL;
+		sta_info_params.mld_info = NULL;
+
+		buflen = MAX_PROBERESP_LEN;
+		buflen += hostapd_probe_resp_elems_len(link, &sta_info_params);
+
+		if (buflen > EHT_ML_MAX_STA_PROF_LEN) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Not including link %d in ML probe response (%zu bytes is too long)",
+				   mld_link_id, buflen);
+			goto fail;
+		}
+
+		/*
+		 * NOTE: This does not properly handle inheritance and
+		 * various other things.
+		 */
+		link_info->valid = true;
+		epos = buf;
+
+		/* Capabilities is the only fixed parameter */
+		WPA_PUT_LE16(epos, hostapd_own_capab_info(hapd));
+		epos += 2;
+
+		epos = hostapd_probe_resp_fill_elems(
+			link, &sta_info_params, epos,
+			EHT_ML_MAX_STA_PROF_LEN - 2);
+		link_info->resp_sta_profile_len = epos - buf;
+		os_free(link_info->resp_sta_profile);
+		link_info->resp_sta_profile = os_memdup(
+			buf, link_info->resp_sta_profile_len);
+		if (!link_info->resp_sta_profile)
+			link_info->resp_sta_profile_len = 0;
+		os_memcpy(link_info->local_addr, link->own_addr, ETH_ALEN);
+
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe response includes link sta info for %d: %u bytes (estimate %zu)",
+			   mld_link_id, link_info->resp_sta_profile_len,
+			   buflen);
+	}
+
+	if (mld_id != -1 && !params->mld_ap) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: No nontransmitted BSSID for MLD ID %d",
+			   mld_id);
+		goto fail;
+	}
+
+	return;
+
+fail:
+	hostapd_free_probe_resp_params(params);
+	params->mld_ap = NULL;
+	params->mld_info = NULL;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 enum ssid_match_result {
 	NO_SSID_MATCH,
 	EXACT_SSID_MATCH,
@@ -953,7 +1164,7 @@
 	struct hostapd_sta_info *info;
 
 	dl_list_for_each(info, &iface->sta_seen, struct hostapd_sta_info, list)
-		if (os_memcmp(addr, info->addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, info->addr))
 			return info;
 
 	return NULL;
@@ -1037,21 +1248,109 @@
 #endif /* CONFIG_TAXONOMY */
 
 
+#ifdef CONFIG_IEEE80211BE
+static bool parse_ml_probe_req(const struct ieee80211_eht_ml *ml, size_t ml_len,
+			       int *mld_id, u16 *links)
+{
+	u16 ml_control;
+	const struct element *sub;
+	const u8 *pos;
+	size_t len;
+
+	*mld_id = -1;
+	*links = 0xffff;
+
+	if (ml_len < sizeof(struct ieee80211_eht_ml))
+		return false;
+
+	ml_control = le_to_host16(ml->ml_control);
+	if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_PROBE_REQ) {
+		wpa_printf(MSG_DEBUG, "MLD: Not an ML probe req");
+		return false;
+	}
+
+	if (sizeof(struct ieee80211_eht_ml) + 1 > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: ML probe req too short");
+		return false;
+	}
+
+	pos = ml->variable;
+	len = pos[0];
+	if (len < 1 || sizeof(struct ieee80211_eht_ml) + len > ml_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe request with invalid length");
+		return false;
+	}
+
+	if (ml_control & EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID) {
+		if (len < 2) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: ML probe req too short for MLD ID");
+			return false;
+		}
+
+		*mld_id = pos[1];
+	}
+	pos += len;
+
+	/* Parse subelements (if there are any) */
+	len = ml_len - len - sizeof(struct ieee80211_eht_ml);
+	for_each_element_id(sub, 0, pos, len) {
+		const struct ieee80211_eht_per_sta_profile *sta;
+		u16 sta_control;
+
+		if (*links == 0xffff)
+			*links = 0;
+
+		if (sub->datalen <
+		    sizeof(struct ieee80211_eht_per_sta_profile)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: ML probe req %d too short for sta profile",
+				   sub->datalen);
+			return false;
+		}
+
+		sta = (struct ieee80211_eht_per_sta_profile *) sub->data;
+
+		/*
+		 * Extract the link ID, do not return whether a complete or
+		 * partial profile was requested.
+		 */
+		sta_control = le_to_host16(sta->sta_control);
+		*links |= BIT(sta_control & EHT_PER_STA_CTRL_LINK_ID_MSK);
+	}
+
+	if (!for_each_element_completed(sub, pos, len)) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: ML probe req sub-elements parsing error");
+		return false;
+	}
+
+	return true;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 void handle_probe_req(struct hostapd_data *hapd,
 		      const struct ieee80211_mgmt *mgmt, size_t len,
 		      int ssi_signal)
 {
-	u8 *resp;
 	struct ieee802_11_elems elems;
 	const u8 *ie;
 	size_t ie_len;
-	size_t i, resp_len;
+	size_t i;
 	int noack;
 	enum ssid_match_result res;
 	int ret;
 	u16 csa_offs[2];
 	size_t csa_offs_len;
 	struct radius_sta rad_info;
+	struct probe_resp_params params;
+#ifdef CONFIG_IEEE80211BE
+	int mld_id;
+	u16 links;
+#endif /* CONFIG_IEEE80211BE */
 
 	if (hapd->iconf->rssi_ignore_probe_request && ssi_signal &&
 	    ssi_signal < hapd->iconf->rssi_ignore_probe_request)
@@ -1217,7 +1516,7 @@
 		else
 			hessid = elems.interworking + 1 + 2;
 		if (!is_broadcast_ether_addr(hessid) &&
-		    os_memcmp(hessid, hapd->conf->hessid, ETH_ALEN) != 0) {
+		    !ether_addr_equal(hessid, hapd->conf->hessid)) {
 			wpa_printf(MSG_MSGDUMP, "Probe Request from " MACSTR
 				   " for mismatching HESSID " MACSTR
 				   " ignored",
@@ -1283,10 +1582,28 @@
 	wpa_msg_ctrl(hapd->msg_ctx, MSG_INFO, RX_PROBE_REQUEST "sa=" MACSTR
 		     " signal=%d", MAC2STR(mgmt->sa), ssi_signal);
 
-	resp = hostapd_gen_probe_resp(hapd, mgmt, elems.p2p != NULL,
-				      &resp_len, elems.mbssid_known_bss,
-				      elems.mbssid_known_bss_len);
-	if (resp == NULL)
+	os_memset(&params, 0, sizeof(params));
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && elems.probe_req_mle &&
+	    parse_ml_probe_req((struct ieee80211_eht_ml *) elems.probe_req_mle,
+			       elems.probe_req_mle_len, &mld_id, &links)) {
+		hostapd_fill_probe_resp_ml_params(hapd, &params, mgmt,
+						  mld_id, links);
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	params.req = mgmt;
+	params.is_p2p = !!elems.p2p;
+	params.known_bss = elems.mbssid_known_bss;
+	params.known_bss_len = elems.mbssid_known_bss_len;
+	params.is_ml_sta_info = false;
+
+	hostapd_gen_probe_resp(hapd, &params);
+
+	hostapd_free_probe_resp_params(&params);
+
+	if (!params.resp)
 		return;
 
 	/*
@@ -1298,24 +1615,23 @@
 
 	csa_offs_len = 0;
 	if (hapd->csa_in_progress) {
-		if (hapd->cs_c_off_proberesp)
+		if (params.csa_pos)
 			csa_offs[csa_offs_len++] =
-				hapd->cs_c_off_proberesp;
+				params.csa_pos - (u8 *) params.resp;
 
-		if (hapd->cs_c_off_ecsa_proberesp)
+		if (params.ecsa_pos)
 			csa_offs[csa_offs_len++] =
-				hapd->cs_c_off_ecsa_proberesp;
+				params.ecsa_pos - (u8 *) params.resp;
 	}
 
-	ret = hostapd_drv_send_mlme(hostapd_mbssid_get_tx_bss(hapd), resp,
-				    resp_len, noack,
+	ret = hostapd_drv_send_mlme(hapd, params.resp, params.resp_len, noack,
 				    csa_offs_len ? csa_offs : NULL,
 				    csa_offs_len, 0);
 
 	if (ret < 0)
 		wpa_printf(MSG_INFO, "handle_probe_req: send failed");
 
-	os_free(resp);
+	os_free(params.resp);
 
 	wpa_printf(MSG_EXCESSIVE, "STA " MACSTR " sent probe request for %s "
 		   "SSID", MAC2STR(mgmt->sa),
@@ -1326,6 +1642,8 @@
 static u8 * hostapd_probe_resp_offloads(struct hostapd_data *hapd,
 					size_t *resp_len)
 {
+	struct probe_resp_params params;
+
 	/* check probe response offloading caps and print warnings */
 	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_PROBE_RESP_OFFLOAD))
 		return NULL;
@@ -1355,7 +1673,32 @@
 			   "this");
 
 	/* Generate a Probe Response template for the non-P2P case */
-	return hostapd_gen_probe_resp(hapd, NULL, 0, resp_len, NULL, 0);
+	os_memset(&params, 0, sizeof(params));
+	params.req = NULL;
+	params.is_p2p = false;
+	params.known_bss = NULL;
+	params.known_bss_len = 0;
+	params.is_ml_sta_info = false;
+	params.mld_ap = NULL;
+	params.mld_info = NULL;
+
+	hostapd_gen_probe_resp(hapd, &params);
+	*resp_len = params.resp_len;
+	if (!params.resp)
+		return NULL;
+
+	/* TODO: Avoid passing these through struct hostapd_data */
+	if (params.csa_pos)
+		hapd->cs_c_off_proberesp = params.csa_pos - (u8 *) params.resp;
+	if (params.ecsa_pos)
+		hapd->cs_c_off_ecsa_proberesp = params.ecsa_pos -
+			(u8 *) params.resp;
+#ifdef CONFIG_IEEE80211AX
+	if (params.cca_pos)
+		hapd->cca_c_off_proberesp = params.cca_pos - (u8 *) params.resp;
+#endif /* CONFIG_IEEE80211AX */
+
+	return (u8 *) params.resp;
 }
 
 #endif /* NEED_AP_MLME */
@@ -1366,15 +1709,26 @@
 static u8 * hostapd_unsol_bcast_probe_resp(struct hostapd_data *hapd,
 					   struct wpa_driver_ap_params *params)
 {
+	struct probe_resp_params probe_params;
+
 	if (!is_6ghz_op_class(hapd->iconf->op_class))
 		return NULL;
 
 	params->unsol_bcast_probe_resp_interval =
 		hapd->conf->unsol_bcast_probe_resp_interval;
 
-	return hostapd_gen_probe_resp(hapd, NULL, 0,
-				      &params->unsol_bcast_probe_resp_tmpl_len,
-				      NULL, 0);
+	os_memset(&probe_params, 0, sizeof(probe_params));
+	probe_params.req = NULL;
+	probe_params.is_p2p = false;
+	probe_params.known_bss = NULL;
+	probe_params.known_bss_len = 0;
+	probe_params.is_ml_sta_info = false;
+	probe_params.mld_ap = NULL;
+	probe_params.mld_info = NULL;
+
+	hostapd_gen_probe_resp(hapd, &probe_params);
+	params->unsol_bcast_probe_resp_tmpl_len = probe_params.resp_len;
+	return (u8 *) probe_params.resp;
 }
 #endif /* CONFIG_IEEE80211AX */
 
@@ -1606,14 +1960,9 @@
 	buf_len = pos - buf;
 	total_len += buf_len;
 
-#ifdef CONFIG_IEEE80211AX
-	/* Transmit Power Envelope element(s) */
-	if (is_6ghz_op_class(hapd->iconf->op_class)) {
-		total_len += 4;
-		if (hapd->iconf->he_6ghz_reg_pwr_type == HE_6GHZ_INDOOR_AP)
-			total_len += 4;
-	}
-#endif /* CONFIG_IEEE80211AX */
+	/* he_elem_len() may return too large a value for FD frame, but that is
+	 * fine here since this is used as the maximum length of the buffer. */
+	total_len += he_elem_len(hapd);
 
 	head = os_zalloc(total_len);
 	if (!head)
@@ -1763,23 +2112,7 @@
 	}
 #endif /* CONFIG_IEEE80211AC */
 
-#ifdef CONFIG_IEEE80211AX
-	if (hapd->iconf->ieee80211ax && !hapd->conf->disable_11ax) {
-		tail_len += 3 + sizeof(struct ieee80211_he_capabilities) +
-			3 + sizeof(struct ieee80211_he_operation) +
-			3 + sizeof(struct ieee80211_he_mu_edca_parameter_set) +
-			3 + sizeof(struct ieee80211_spatial_reuse);
-		if (is_6ghz_op_class(hapd->iconf->op_class)) {
-			tail_len += sizeof(struct ieee80211_he_6ghz_oper_info) +
-				3 + sizeof(struct ieee80211_he_6ghz_band_cap);
-			 /* An additional Transmit Power Envelope element for
-			  * subordinate client */
-			if (hapd->iconf->he_6ghz_reg_pwr_type ==
-			    HE_6GHZ_INDOOR_AP)
-				tail_len += 4;
-		}
-	}
-#endif /* CONFIG_IEEE80211AX */
+	tail_len += he_elem_len(hapd);
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
@@ -1966,8 +2299,8 @@
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
 		if (hapd->conf->mld_ap)
-			tailpos = hostapd_eid_eht_basic_ml(hapd, tailpos, NULL,
-							   true);
+			tailpos = hostapd_eid_eht_ml_beacon(hapd, NULL,
+							    tailpos, false);
 		tailpos = hostapd_eid_eht_capab(hapd, tailpos,
 						IEEE80211_MODE_AP);
 		tailpos = hostapd_eid_eht_operation(hapd, tailpos);
diff --git a/src/ap/ctrl_iface_ap.c b/src/ap/ctrl_iface_ap.c
index a6fcb7e..5378671 100644
--- a/src/ap/ctrl_iface_ap.c
+++ b/src/ap/ctrl_iface_ap.c
@@ -830,6 +830,17 @@
 		if (os_snprintf_error(buflen - len, ret))
 			return len;
 		len += ret;
+
+		if (is_6ghz_op_class(iface->conf->op_class) &&
+		    hostapd_get_oper_chwidth(iface->conf) ==
+		    CONF_OPER_CHWIDTH_320MHZ) {
+			ret = os_snprintf(buf + len, buflen - len,
+					  "eht_bw320_offset=%d\n",
+					  iface->conf->eht_bw320_offset);
+			if (os_snprintf_error(buflen - len, ret))
+				return len;
+			len += ret;
+		}
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1094,7 +1105,7 @@
 		return -1;
 
 	return wpa_auth_pmksa_add2(hapd->wpa_auth, spa, pmk, pmk_len,
-				   pmkid, expiration, akmp);
+				   pmkid, expiration, akmp, NULL);
 }
 
 
@@ -1315,6 +1326,8 @@
 		req_mode |= WNM_BSS_TM_REQ_ABRIDGED;
 	if (os_strstr(cmd, " disassoc_imminent=1"))
 		req_mode |= WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
+	if (os_strstr(cmd, " link_removal_imminent=1"))
+		req_mode |= WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT;
 
 #ifdef CONFIG_MBO
 	pos = os_strstr(cmd, "mbo=");
diff --git a/src/ap/dfs.c b/src/ap/dfs.c
index 9a5d3c8..5e4c810 100644
--- a/src/ap/dfs.c
+++ b/src/ap/dfs.c
@@ -188,7 +188,7 @@
 	 * If it's not allowed to use the first channel as primary, decline the
 	 * whole channel range. */
 	if (!chan_pri_allowed(first_chan)) {
-		wpa_printf(MSG_DEBUG, "DFS: primary chanenl not allowed");
+		wpa_printf(MSG_DEBUG, "DFS: primary channel not allowed");
 		return 0;
 	}
 
@@ -551,6 +551,8 @@
 	if (os_get_random((u8 *) &_rand, sizeof(_rand)) < 0)
 		return NULL;
 	chan_idx = _rand % num_available_chandefs;
+	wpa_printf(MSG_DEBUG, "DFS: Picked random entry from the list: %d/%d",
+		   chan_idx, num_available_chandefs);
 	dfs_find_channel(iface, &chan, chan_idx, type);
 	if (!chan) {
 		wpa_printf(MSG_DEBUG, "DFS: no random channel found");
@@ -983,6 +985,11 @@
 	os_memset(&csa_settings, 0, sizeof(csa_settings));
 	csa_settings.cs_count = 5;
 	csa_settings.block_tx = 1;
+	csa_settings.link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (iface->bss[0]->conf->mld_ap)
+		csa_settings.link_id = iface->bss[0]->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 #ifdef CONFIG_MESH
 	if (iface->mconf)
 		ieee80211_mode = IEEE80211_MODE_MESH;
@@ -1044,7 +1051,7 @@
 }
 
 
-static void hostpad_dfs_update_background_chain(struct hostapd_iface *iface)
+static void hostapd_dfs_update_background_chain(struct hostapd_iface *iface)
 {
 	int sec = 0;
 	enum dfs_channel_type channel_type = DFS_NO_CAC_YET;
@@ -1119,7 +1126,7 @@
 	hostapd_set_oper_centr_freq_seg1_idx(
 		iface->conf, iface->radar_background.centr_freq_seg1_idx);
 
-	hostpad_dfs_update_background_chain(iface);
+	hostapd_dfs_update_background_chain(iface);
 
 	return hostapd_dfs_request_channel_switch(
 		iface, iface->conf->channel, iface->freq,
@@ -1183,7 +1190,7 @@
 		}
 	} else if (hostapd_dfs_is_background_event(iface, freq)) {
 		iface->radar_background.cac_started = 0;
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 	}
 
 	return 0;
@@ -1317,7 +1324,7 @@
 		 * Just select a new random channel according to the
 		 * regulations for monitoring.
 		 */
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 		return 0;
 	}
 
@@ -1479,7 +1486,7 @@
 	} else if (dfs_use_radar_background(iface) &&
 		   iface->radar_background.channel == -1) {
 		/* Reset radar background chain if disabled */
-		hostpad_dfs_update_background_chain(iface);
+		hostapd_dfs_update_background_chain(iface);
 	}
 
 	return 0;
diff --git a/src/ap/dpp_hostapd.c b/src/ap/dpp_hostapd.c
index 7a8ea4e..3f89bc2 100644
--- a/src/ap/dpp_hostapd.c
+++ b/src/ap/dpp_hostapd.c
@@ -539,8 +539,15 @@
 		return;
 	}
 
-	if (hapd->dpp_auth_ok_on_ack)
+	if (hapd->dpp_auth_ok_on_ack) {
 		hostapd_dpp_auth_success(hapd, 1);
+		if (!hapd->dpp_auth) {
+			/* The authentication session could have been removed in
+			 * some error cases, e.g., when starting GAS client and
+			 * failing to send the initial request. */
+			return;
+		}
+	}
 
 	if (!is_broadcast_ether_addr(dst) && !ok) {
 		wpa_printf(MSG_DEBUG,
@@ -1413,7 +1420,7 @@
 	}
 
 	if (!is_zero_ether_addr(auth->peer_mac_addr) &&
-	    os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1463,7 +1470,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1572,7 +1579,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -1858,7 +1865,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2133,7 +2140,7 @@
 	else
 		expiration = 0;
 
-	if (wpa_auth_pmksa_add3(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
+	if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
 				intro.pmkid, expiration,
 				WPA_KEY_MGMT_DPP, pkhash) < 0) {
 		wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry");
@@ -2907,7 +2914,7 @@
 	else
 		expiration = 0;
 
-	if (wpa_auth_pmksa_add3(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
+	if (wpa_auth_pmksa_add2(hapd->wpa_auth, src, intro.pmk, intro.pmk_len,
 				intro.pmkid, expiration,
 				WPA_KEY_MGMT_DPP, pkhash) < 0) {
 		wpa_printf(MSG_ERROR, "DPP: Failed to add PMKSA cache entry");
@@ -3073,7 +3080,7 @@
 
 	wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR, MAC2STR(sa));
 	if (!auth || (!auth->auth_success && !auth->reconfig_success) ||
-	    os_memcmp(sa, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sa, auth->peer_mac_addr)) {
 #ifdef CONFIG_DPP2
 		if (dpp_relay_rx_gas_req(hapd->iface->interfaces->dpp, sa, data,
 				     data_len) == 0) {
@@ -3094,6 +3101,13 @@
 		 * exchange. */
 		dpp_notify_auth_success(hapd->dpp_auth, 1);
 		hapd->dpp_auth_ok_on_ack = 0;
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) {
+			wpa_printf(MSG_INFO,
+				   "DPP: TESTING - stop at Authentication Confirm");
+			return NULL;
+		}
+#endif /* CONFIG_TESTING_OPTIONS */
 	}
 
 	wpa_hexdump(MSG_DEBUG,
@@ -3948,11 +3962,25 @@
 	ifaces->dpp_pb_time.usec = 0;
 	dpp_pkex_free(hapd->dpp_pkex);
 	hapd->dpp_pkex = NULL;
+	hapd->dpp_pkex_bi = NULL;
 	os_free(hapd->dpp_pkex_auth_cmd);
 	hapd->dpp_pkex_auth_cmd = NULL;
 
 	if (ifaces->dpp_pb_bi) {
 		char id[20];
+		size_t i;
+
+		for (i = 0; i < ifaces->count; i++) {
+			struct hostapd_iface *iface = ifaces->iface[i];
+			size_t j;
+
+			for (j = 0; iface && j < iface->num_bss; j++) {
+				struct hostapd_data *h = iface->bss[j];
+
+				if (h->dpp_pkex_bi == ifaces->dpp_pb_bi)
+					h->dpp_pkex_bi = NULL;
+			}
+		}
 
 		os_snprintf(id, sizeof(id), "%u", ifaces->dpp_pb_bi->id);
 		dpp_bootstrap_remove(ifaces->dpp, id);
diff --git a/src/ap/drv_callbacks.c b/src/ap/drv_callbacks.c
index 98794c2..533cc54 100644
--- a/src/ap/drv_callbacks.c
+++ b/src/ap/drv_callbacks.c
@@ -42,6 +42,7 @@
 #include "dpp_hostapd.h"
 #include "fils_hlp.h"
 #include "neighbor_db.h"
+#include "nan_usd_ap.h"
 
 
 #ifdef CONFIG_FILS
@@ -52,6 +53,7 @@
 	struct ieee802_11_elems elems;
 	u8 buf[IEEE80211_MAX_MMPDU_SIZE], *p = buf;
 	int new_assoc;
+	bool updated;
 
 	wpa_printf(MSG_DEBUG, "%s FILS: Finish association with " MACSTR,
 		   __func__, MAC2STR(sta->addr));
@@ -76,11 +78,13 @@
 				      sta->fils_pending_assoc_is_reassoc,
 				      WLAN_STATUS_SUCCESS,
 				      buf, p - buf);
-	ap_sta_set_authorized(hapd, sta, 1);
+	updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 	wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FILS);
 	ieee802_1x_notify_port_enabled(sta->eapol_sm, 1);
 	hostapd_new_assoc_sta(hapd, sta, !new_assoc);
@@ -158,7 +162,7 @@
 		return -1;
 	}
 
-	mlebuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlebuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlebuf) {
 		wpa_printf(MSG_ERROR,
 			   "MLO: Basic Multi-Link element not found in (Re)Association Response frame");
@@ -263,6 +267,7 @@
 #ifdef CONFIG_OWE
 	struct hostapd_iface *iface = hapd->iface;
 #endif /* CONFIG_OWE */
+	bool updated = false;
 
 	if (addr == NULL) {
 		/*
@@ -279,7 +284,7 @@
 
 	if (is_multicast_ether_addr(addr) ||
 	    is_zero_ether_addr(addr) ||
-	    os_memcmp(addr, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(addr, hapd->own_addr)) {
 		/* Do not process any frames with unexpected/invalid SA so that
 		 * we do not add any state for unexpected STA addresses or end
 		 * up sending out frames to unexpected destination. */
@@ -358,7 +363,7 @@
 		int i, num_valid_links = 0;
 		u8 link_id = hapd->mld_link_id;
 
-		info->mld_sta = true;
+		ap_sta_set_mld(sta, true);
 		sta->mld_assoc_link_id = link_id;
 		os_memcpy(info->common_info.mld_addr, addr, ETH_ALEN);
 		info->links[link_id].valid = true;
@@ -509,7 +514,7 @@
 			return -1;
 		}
 #ifdef CONFIG_IEEE80211BE
-		if (sta->mld_info.mld_sta) {
+		if (ap_sta_is_mld(hapd, sta)) {
 			wpa_printf(MSG_DEBUG,
 				   "MLD: Set ML info in RSN Authenticator");
 			wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
@@ -845,18 +850,30 @@
 	    sta->auth_alg == WLAN_AUTH_FILS_SK ||
 	    sta->auth_alg == WLAN_AUTH_FILS_SK_PFS ||
 	    sta->auth_alg == WLAN_AUTH_FILS_PK)
-		ap_sta_set_authorized(hapd, sta, 1);
+		updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 #else /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 	/* Keep compiler silent about unused variables */
 	if (status) {
 	}
 #endif /* CONFIG_IEEE80211R_AP || CONFIG_FILS */
 
+#ifdef CONFIG_IEEE80211BE
+	if (hostapd_process_assoc_ml_info(hapd, sta, req_ies, req_ies_len,
+					  !!reassoc, WLAN_STATUS_SUCCESS,
+					  true)) {
+		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+		reason = WLAN_REASON_UNSPECIFIED;
+		goto fail;
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	new_assoc = (sta->flags & WLAN_STA_ASSOC) == 0;
 	sta->flags |= WLAN_STA_AUTH | WLAN_STA_ASSOC;
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 
 	if (reassoc && (sta->auth_alg == WLAN_AUTH_FT))
 		wpa_auth_sm_event(sta->wpa_sm, WPA_ASSOC_FT);
@@ -1164,6 +1181,8 @@
 	hostapd_set_oper_chwidth(hapd->iconf, chwidth);
 	hostapd_set_oper_centr_freq_seg0_idx(hapd->iconf, seg0_idx);
 	hostapd_set_oper_centr_freq_seg1_idx(hapd->iconf, seg1_idx);
+	/* Auto-detect new bw320_offset */
+	hostapd_set_and_check_bw320_offset(hapd->iconf, 0);
 #ifdef CONFIG_IEEE80211BE
 	hapd->iconf->punct_bitmap = punct_bitmap;
 #endif /* CONFIG_IEEE80211BE */
@@ -1270,6 +1289,18 @@
 	int err = 0;
 	struct hostapd_channel_data *pri_chan;
 
+#ifdef CONFIG_IEEE80211BE
+	if (acs_res->link_id != -1) {
+		hapd = hostapd_mld_get_link_bss(hapd, acs_res->link_id);
+		if (!hapd) {
+			wpa_printf(MSG_ERROR,
+				   "MLD: Failed to get link BSS for EVENT_ACS_CHANNEL_SELECTED link_id=%d",
+				   acs_res->link_id);
+			return;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	if (hapd->iconf->channel) {
 		wpa_printf(MSG_INFO, "ACS: Channel was already set to %d",
 			   hapd->iconf->channel);
@@ -1576,6 +1607,7 @@
 #endif /* CONFIG_FST */
 #ifdef CONFIG_DPP
 	if (plen >= 2 + 4 &&
+	    mgmt->u.action.category == WLAN_ACTION_PUBLIC &&
 	    mgmt->u.action.u.vs_public_action.action ==
 	    WLAN_PA_VENDOR_SPECIFIC &&
 	    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
@@ -1591,6 +1623,23 @@
 		return;
 	}
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+	if (mgmt->u.action.category == WLAN_ACTION_PUBLIC && plen >= 5 &&
+	    mgmt->u.action.u.vs_public_action.action ==
+	    WLAN_PA_VENDOR_SPECIFIC &&
+	    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
+	    OUI_WFA &&
+	    mgmt->u.action.u.vs_public_action.variable[0] == NAN_OUI_TYPE) {
+		const u8 *pos, *end;
+
+		pos = mgmt->u.action.u.vs_public_action.variable;
+		end = drv_mgmt->frame + drv_mgmt->frame_len;
+		pos++;
+		hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, drv_mgmt->freq,
+				       pos, end - pos);
+		return;
+	}
+#endif /* CONFIG_NAN_USD */
 }
 #endif /* NEED_AP_MLME */
 
@@ -1628,7 +1677,7 @@
 		return HAPD_BROADCAST;
 
 	for (i = 0; i < iface->num_bss; i++) {
-		if (os_memcmp(bssid, iface->bss[i]->own_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(bssid, iface->bss[i]->own_addr))
 			return iface->bss[i];
 	}
 
@@ -1682,7 +1731,7 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->conf->mld_ap &&
-	    os_memcmp(hapd->mld_addr, bssid, ETH_ALEN) == 0)
+	    ether_addr_equal(hapd->mld_addr, bssid))
 		is_mld = true;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1754,8 +1803,7 @@
 		hapd = tmp_hapd;
 #ifdef CONFIG_IEEE80211BE
 	} else if (hapd->conf->mld_ap &&
-		   os_memcmp(hapd->mld_addr, get_hdr_bssid(hdr, len),
-			     ETH_ALEN) == 0) {
+		   ether_addr_equal(hapd->mld_addr, get_hdr_bssid(hdr, len))) {
 		/* AP MLD address match - use hapd pointer as-is */
 #endif /* CONFIG_IEEE80211BE */
 	} else {
@@ -1803,14 +1851,15 @@
 
 
 static struct hostapd_data * hostapd_find_by_sta(struct hostapd_iface *iface,
-						 const u8 *src)
+						 const u8 *src, bool rsn)
 {
 	struct sta_info *sta;
 	unsigned int j;
 
 	for (j = 0; j < iface->num_bss; j++) {
 		sta = ap_get_sta(iface->bss[j], src);
-		if (sta && sta->flags & WLAN_STA_ASSOC)
+		if (sta && (sta->flags & WLAN_STA_ASSOC) &&
+		    (!rsn || sta->wpa_sm))
 			return iface->bss[j];
 	}
 
@@ -1818,6 +1867,40 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static bool search_mld_sta(struct hostapd_data **p_hapd, const u8 *src)
+{
+	struct hostapd_data *hapd = *p_hapd;
+	unsigned int i;
+
+	/* Search for STA on other MLO BSSs */
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		struct hostapd_iface *h =
+			hapd->iface->interfaces->iface[i];
+		struct hostapd_data *h_hapd = h->bss[0];
+		struct hostapd_bss_config *hconf = h_hapd->conf;
+
+		if (!hconf->mld_ap ||
+		    hconf->mld_id != hapd->conf->mld_id)
+			continue;
+
+		h_hapd = hostapd_find_by_sta(h, src, false);
+		if (h_hapd) {
+			struct sta_info *sta = ap_get_sta(h_hapd, src);
+
+			if (sta && sta->mld_info.mld_sta &&
+			    sta->mld_assoc_link_id != h_hapd->mld_link_id)
+				continue;
+			*p_hapd = h_hapd;
+			return true;
+		}
+	}
+
+	return false;
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 static void hostapd_event_eapol_rx(struct hostapd_data *hapd, const u8 *src,
 				   const u8 *data, size_t data_len,
 				   enum frame_encryption encrypted,
@@ -1830,36 +1913,24 @@
 		struct hostapd_data *h_hapd;
 
 		hapd = switch_link_hapd(hapd, link_id);
-		h_hapd = hostapd_find_by_sta(hapd->iface, src);
+		h_hapd = hostapd_find_by_sta(hapd->iface, src, true);
 		if (!h_hapd)
-			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src);
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src,
+						     true);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(hapd->iface, src, false);
+		if (!h_hapd)
+			h_hapd = hostapd_find_by_sta(orig_hapd->iface, src,
+						     false);
 		if (h_hapd)
 			hapd = h_hapd;
 	} else if (hapd->conf->mld_ap) {
-		unsigned int i;
-
-		/* Search for STA on other MLO BSSs */
-		for (i = 0; i < hapd->iface->interfaces->count; i++) {
-			struct hostapd_iface *h =
-				hapd->iface->interfaces->iface[i];
-			struct hostapd_data *h_hapd = h->bss[0];
-			struct hostapd_bss_config *hconf = h_hapd->conf;
-
-			if (!hconf->mld_ap ||
-			    hconf->mld_id != hapd->conf->mld_id)
-				continue;
-
-			h_hapd = hostapd_find_by_sta(h, src);
-			if (h_hapd) {
-				hapd = h_hapd;
-				break;
-			}
-		}
+		search_mld_sta(&hapd, src);
 	} else {
-		hapd = hostapd_find_by_sta(hapd->iface, src);
+		hapd = hostapd_find_by_sta(hapd->iface, src, false);
 	}
 #else /* CONFIG_IEEE80211BE */
-	hapd = hostapd_find_by_sta(hapd->iface, src);
+	hapd = hostapd_find_by_sta(hapd->iface, src, false);
 #endif /* CONFIG_IEEE80211BE */
 
 	if (!hapd) {
@@ -2164,8 +2235,8 @@
 		struct mld_info *info = &sta->mld_info;
 		u8 link_id = hapd->mld_link_id;
 
-		info->mld_sta = true;
-		sta->mld_assoc_link_id = link_id;;
+		ap_sta_set_mld(sta, true);
+		sta->mld_assoc_link_id = link_id;
 		os_memcpy(info->common_info.mld_addr, peer, ETH_ALEN);
 		info->links[link_id].valid = true;
 		os_memcpy(info->links[link_id].local_addr, hapd->own_addr,
@@ -2364,6 +2435,18 @@
 	case EVENT_CH_SWITCH:
 		if (!data)
 			break;
+#ifdef CONFIG_IEEE80211BE
+		if (data->ch_switch.link_id != -1) {
+			hapd = hostapd_mld_get_link_bss(
+				hapd, data->ch_switch.link_id);
+			if (!hapd) {
+				wpa_printf(MSG_ERROR,
+					   "MLD: Failed to get link (ID %d) BSS for EVENT_CH_SWITCH/EVENT_CH_SWITCH_STARTED",
+					   data->ch_switch.link_id);
+				break;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
 		hostapd_event_ch_switch(hapd, data->ch_switch.freq,
 					data->ch_switch.ht_enabled,
 					data->ch_switch.ch_offset,
@@ -2390,26 +2473,31 @@
 	case EVENT_DFS_RADAR_DETECTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_radar_detected(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_PRE_CAC_EXPIRED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_pre_cac_expired(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_CAC_FINISHED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_finished(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_CAC_ABORTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_aborted(hapd, &data->dfs_event);
 		break;
 	case EVENT_DFS_NOP_FINISHED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_nop_finished(hapd, &data->dfs_event);
 		break;
 	case EVENT_CHANNEL_LIST_CHANGED:
@@ -2423,6 +2511,7 @@
 	case EVENT_DFS_CAC_STARTED:
 		if (!data)
 			break;
+		hapd = switch_link_hapd(hapd, data->dfs_event.link_id);
 		hostapd_event_dfs_cac_started(hapd, &data->dfs_event);
 		break;
 #endif /* NEED_AP_MLME */
diff --git a/src/ap/fils_hlp.c b/src/ap/fils_hlp.c
index d64fb8c..a34b5ba 100644
--- a/src/ap/fils_hlp.c
+++ b/src/ap/fils_hlp.c
@@ -546,7 +546,7 @@
 		   " src=" MACSTR " len=%u)",
 		   MAC2STR(sta->addr), MAC2STR(pos), MAC2STR(pos + ETH_ALEN),
 		   (unsigned int) len);
-	if (os_memcmp(sta->addr, pos + ETH_ALEN, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sta->addr, pos + ETH_ALEN)) {
 		wpa_printf(MSG_DEBUG,
 			   "FILS: Ignore HLP request with unexpected source address"
 			   MACSTR, MAC2STR(pos + ETH_ALEN));
diff --git a/src/ap/gas_query_ap.c b/src/ap/gas_query_ap.c
index 3d94407..a471c79 100644
--- a/src/ap/gas_query_ap.c
+++ b/src/ap/gas_query_ap.c
@@ -185,7 +185,7 @@
 {
 	struct gas_query_pending *q;
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(q->addr, addr) &&
 		    q->dialog_token == dialog_token)
 			return q;
 	}
@@ -223,7 +223,7 @@
 	wpa_printf(MSG_DEBUG, "GAS: TX status: dst=" MACSTR
 		   " ok=%d query=%p dialog_token=%u dur=%d ms",
 		   MAC2STR(dst), ok, query, query->dialog_token, dur);
-	if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(dst, query->addr)) {
 		wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination");
 		return;
 	}
@@ -618,7 +618,7 @@
 {
 	struct gas_query_pending *q;
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(dst, q->addr) &&
 		    dialog_token == q->dialog_token)
 			return 0;
 	}
diff --git a/src/ap/hostapd.c b/src/ap/hostapd.c
index 236381f..ddbcabc 100644
--- a/src/ap/hostapd.c
+++ b/src/ap/hostapd.c
@@ -35,6 +35,7 @@
 #include "wpa_auth.h"
 #include "wps_hostapd.h"
 #include "dpp_hostapd.h"
+#include "nan_usd_ap.h"
 #include "gas_query_ap.h"
 #include "hw_features.h"
 #include "wpa_auth_glue.h"
@@ -413,6 +414,61 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+#ifdef CONFIG_TESTING_OPTIONS
+
+#define TU_TO_USEC(_val) ((_val) * 1024)
+
+static void hostapd_link_remove_timeout_handler(void *eloop_data,
+						void *user_ctx)
+{
+	struct hostapd_data *hapd = (struct hostapd_data *) eloop_data;
+
+	if (hapd->eht_mld_link_removal_count == 0)
+		return;
+	hapd->eht_mld_link_removal_count--;
+
+	wpa_printf(MSG_DEBUG, "MLD: Remove link_id=%u in %u beacons",
+		   hapd->mld_link_id,
+		   hapd->eht_mld_link_removal_count);
+
+	ieee802_11_set_beacon(hapd);
+
+	if (!hapd->eht_mld_link_removal_count) {
+		hostapd_disable_iface(hapd->iface);
+		return;
+	}
+
+	eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int),
+			       hostapd_link_remove_timeout_handler,
+			       hapd, NULL);
+}
+
+
+int hostapd_link_remove(struct hostapd_data *hapd, u32 count)
+{
+	if (!hapd->conf->mld_ap)
+		return -1;
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Remove link_id=%u in %u beacons",
+		   hapd->mld_link_id, count);
+
+	hapd->eht_mld_link_removal_count = count;
+	hapd->eht_mld_bss_param_change++;
+
+	eloop_register_timeout(0, TU_TO_USEC(hapd->iconf->beacon_int),
+			       hostapd_link_remove_timeout_handler,
+			       hapd, NULL);
+
+	ieee802_11_set_beacon(hapd);
+	return 0;
+}
+
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+
 void hostapd_free_hapd_data(struct hostapd_data *hapd)
 {
 	os_free(hapd->probereq_cb);
@@ -441,6 +497,24 @@
 	hostapd_acl_deinit(hapd);
 #ifndef CONFIG_NO_RADIUS
 	if (!hapd->mld_first_bss) {
+		struct hapd_interfaces *ifaces = hapd->iface->interfaces;
+		size_t i;
+
+		for (i = 0; i < ifaces->count; i++) {
+			struct hostapd_iface *iface = ifaces->iface[i];
+			size_t j;
+
+			for (j = 0; iface && j < iface->num_bss; j++) {
+				struct hostapd_data *h = iface->bss[j];
+
+				if (hapd == h)
+					continue;
+				if (h->radius == hapd->radius)
+					h->radius = NULL;
+				if (h->radius_das == hapd->radius_das)
+					h->radius_das = NULL;
+			}
+		}
 		radius_client_deinit(hapd->radius);
 		radius_das_deinit(hapd->radius_das);
 	}
@@ -455,6 +529,9 @@
 	gas_query_ap_deinit(hapd->gas);
 	hapd->gas = NULL;
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+	hostapd_nan_usd_deinit(hapd);
+#endif /* CONFIG_NAN_USD */
 
 	authsrv_deinit(hapd);
 
@@ -502,7 +579,9 @@
 	hapd->setup_complete_cb = NULL;
 #endif /* CONFIG_MESH */
 
+#ifndef CONFIG_NO_RRM
 	hostapd_clean_rrm(hapd);
+#endif /* CONFIG_NO_RRM */
 	fils_hlp_deinit(hapd);
 
 #ifdef CONFIG_OCV
@@ -525,6 +604,12 @@
 
 #ifdef CONFIG_IEEE80211AX
 	eloop_cancel_timeout(hostapd_switch_color_timeout_handler, hapd, NULL);
+#ifdef CONFIG_TESTING_OPTIONS
+#ifdef CONFIG_IEEE80211BE
+	eloop_cancel_timeout(hostapd_link_remove_timeout_handler, hapd, NULL);
+#endif /* CONFIG_IEEE80211BE */
+#endif /* CONFIG_TESTING_OPTIONS */
+
 #endif /* CONFIG_IEEE80211AX */
 }
 
@@ -1435,6 +1520,11 @@
 		return -1;
 #endif /* CONFIG_DPP */
 
+#ifdef CONFIG_NAN_USD
+	if (hostapd_nan_usd_init(hapd) < 0)
+		return -1;
+#endif /* CONFIG_NAN_USD */
+
 	if (authsrv_init(hapd) < 0)
 		return -1;
 
@@ -3117,6 +3207,31 @@
 		return -1;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (hapd_iface->bss[0]->conf->mld_ap &&
+	    !hapd_iface->bss[0]->mld_first_bss) {
+		/* Do not allow mld_first_bss disabling before other BSSs */
+		for (j = 0; j < hapd_iface->interfaces->count; ++j) {
+			struct hostapd_iface *h_iface =
+				hapd_iface->interfaces->iface[j];
+			struct hostapd_data *h_hapd = h_iface->bss[0];
+			struct hostapd_bss_config *h_conf = h_hapd->conf;
+
+			if (!h_conf->mld_ap ||
+			    h_conf->mld_id !=
+			    hapd_iface->bss[0]->conf->mld_id ||
+			    h_iface == hapd_iface)
+				continue;
+
+			if (h_iface->state != HAPD_IFACE_DISABLED) {
+				wpa_printf(MSG_INFO,
+					   "Do not allow disable mld_first_bss first");
+				return -1;
+			}
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 	wpa_msg(hapd_iface->bss[0]->msg_ctx, MSG_INFO, AP_EVENT_DISABLED);
 	driver = hapd_iface->bss[0]->driver;
 	drv_priv = hapd_iface->bss[0]->drv_priv;
@@ -3536,7 +3651,7 @@
 	}
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	if (ap_sta_is_mld(hapd, sta) &&
 	    sta->mld_assoc_link_id != hapd->mld_link_id)
 		return;
 #endif /* CONFIG_IEEE80211BE */
@@ -3768,7 +3883,7 @@
 				      struct hostapd_freq_params *old_params)
 {
 	int channel;
-	u8 seg0, seg1;
+	u8 seg0 = 0, seg1 = 0;
 	struct hostapd_hw_modes *mode;
 
 	if (!params->channel) {
@@ -3844,10 +3959,14 @@
 	conf->ieee80211n = params->ht_enabled;
 	conf->ieee80211ac = params->vht_enabled;
 	conf->secondary_channel = params->sec_channel_offset;
-	ieee80211_freq_to_chan(params->center_freq1,
-			       &seg0);
-	ieee80211_freq_to_chan(params->center_freq2,
-			       &seg1);
+	if (params->center_freq1 &&
+	    ieee80211_freq_to_chan(params->center_freq1, &seg0) ==
+	    NUM_HOSTAPD_MODES)
+		return -1;
+	if (params->center_freq2 &&
+	    ieee80211_freq_to_chan(params->center_freq2,
+				   &seg1) == NUM_HOSTAPD_MODES)
+		return -1;
 	hostapd_set_oper_centr_freq_seg0_idx(conf, seg0);
 	hostapd_set_oper_centr_freq_seg1_idx(conf, seg1);
 
@@ -3945,6 +4064,11 @@
 	settings->counter_offset_presp[0] = hapd->cs_c_off_proberesp;
 	settings->counter_offset_beacon[1] = hapd->cs_c_off_ecsa_beacon;
 	settings->counter_offset_presp[1] = hapd->cs_c_off_ecsa_proberesp;
+	settings->link_id = -1;
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap)
+		settings->link_id = hapd->mld_link_id;
+#endif /* CONFIG_IEEE80211BE */
 
 	return 0;
 }
@@ -4040,13 +4164,17 @@
 		bw = CONF_OPER_CHWIDTH_USE_HT;
 		break;
 	case 80:
-		if (freq_params->center_freq2)
+		if (freq_params->center_freq2) {
 			bw = CONF_OPER_CHWIDTH_80P80MHZ;
-		else
+			iface->conf->vht_capab |=
+				VHT_CAP_SUPP_CHAN_WIDTH_160_80PLUS80MHZ;
+		} else {
 			bw = CONF_OPER_CHWIDTH_80MHZ;
+		}
 		break;
 	case 160:
 		bw = CONF_OPER_CHWIDTH_160MHZ;
+		iface->conf->vht_capab |= VHT_CAP_SUPP_CHAN_WIDTH_160MHZ;
 		break;
 	case 320:
 		bw = CONF_OPER_CHWIDTH_320MHZ;
diff --git a/src/ap/hostapd.h b/src/ap/hostapd.h
index 7f703be..bcf980f 100644
--- a/src/ap/hostapd.h
+++ b/src/ap/hostapd.h
@@ -469,6 +469,17 @@
 #ifdef CONFIG_CTRL_IFACE_UDP
        unsigned char ctrl_iface_cookie[CTRL_IFACE_COOKIE_LEN];
 #endif /* CONFIG_CTRL_IFACE_UDP */
+
+#ifdef CONFIG_IEEE80211BE
+	u8 eht_mld_bss_param_change;
+#ifdef CONFIG_TESTING_OPTIONS
+	u8 eht_mld_link_removal_count;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_IEEE80211BE */
+
+#ifdef CONFIG_NAN_USD
+	struct nan_de *nan_de;
+#endif /* CONFIG_NAN_USD */
 };
 
 
@@ -771,5 +782,25 @@
 int hostapd_mbssid_get_bss_index(struct hostapd_data *hapd);
 struct hostapd_data * hostapd_mld_get_link_bss(struct hostapd_data *hapd,
 					       u8 link_id);
+int hostapd_link_remove(struct hostapd_data *hapd, u32 count);
+
+#ifdef CONFIG_IEEE80211BE
+#define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
+	for (_iface_idx = 0;						\
+	     _iface_idx < (_ifaces)->count;				\
+	     _iface_idx++)						\
+		for (_bss_idx = 0;					\
+		     _bss_idx <						\
+			(_ifaces)->iface[_iface_idx]->num_bss;		\
+		     _bss_idx++)					\
+			for (_link =					\
+			     (_ifaces)->iface[_iface_idx]->bss[_bss_idx]; \
+			    _link && _link->conf->mld_ap &&		\
+				_link->conf->mld_id == _mld_id;		\
+			    _link = NULL)
+#else /* CONFIG_IEEE80211BE */
+#define for_each_mld_link(_link, _bss_idx, _iface_idx, _ifaces, _mld_id) \
+	if (false)
+#endif /* CONFIG_IEEE80211BE */
 
 #endif /* HOSTAPD_H */
diff --git a/src/ap/hw_features.c b/src/ap/hw_features.c
index 9edbb5a..596f2f0 100644
--- a/src/ap/hw_features.c
+++ b/src/ap/hw_features.c
@@ -1001,7 +1001,7 @@
 {
 	int secondary_freq;
 	struct hostapd_channel_data *pri_chan;
-	int err;
+	int err, err2;
 
 	if (!iface->current_mode)
 		return 0;
@@ -1044,15 +1044,15 @@
 
 	/* Both HT40+ and HT40- are set, pick a valid secondary channel */
 	secondary_freq = iface->freq + 20;
-	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
-	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
+	err2 = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40P)) {
 		iface->conf->secondary_channel = 1;
 		return 1;
 	}
 
 	secondary_freq = iface->freq - 20;
-	err = hostapd_is_usable_chan(iface, secondary_freq, 0);
-	if (err > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
+	err2 = hostapd_is_usable_chan(iface, secondary_freq, 0);
+	if (err2 > 0 && (pri_chan->allowed_bw & HOSTAPD_CHAN_WIDTH_40M)) {
 		iface->conf->secondary_channel = -1;
 		return 1;
 	}
diff --git a/src/ap/ieee802_11.c b/src/ap/ieee802_11.c
index 1f39107..8b8c1f0 100644
--- a/src/ap/ieee802_11.c
+++ b/src/ap/ieee802_11.c
@@ -56,6 +56,7 @@
 #include "dpp_hostapd.h"
 #include "gas_query_ap.h"
 #include "comeback_token.h"
+#include "nan_usd_ap.h"
 #include "pasn/pasn_common.h"
 
 
@@ -407,7 +408,7 @@
 	 * the MLD MAC address. Thus, use the MLD address instead of translating
 	 * the addresses.
 	 */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		sa = hapd->mld_addr;
 
 		ml_resp = hostapd_ml_auth_resp(hapd);
@@ -556,7 +557,7 @@
 	for (pw = hapd->conf->sae_passwords; pw; pw = pw->next) {
 		if (!is_broadcast_ether_addr(pw->peer_addr) &&
 		    (!sta ||
-		     os_memcmp(pw->peer_addr, sta->addr, ETH_ALEN) != 0))
+		     !ether_addr_equal(pw->peer_addr, sta->addr)))
 			continue;
 		if ((rx_id && !pw->identifier) || (!rx_id && pw->identifier))
 			continue;
@@ -608,7 +609,7 @@
 	const u8 *own_addr = hapd->own_addr;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		own_addr = hapd->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -877,7 +878,7 @@
 	params.status = status;
 
 #ifdef CONFIG_IEEE80211BE
-	if (sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		params.bssid =
 			sta->mld_info.links[sta->mld_assoc_link_id].peer_addr;
 #endif /* CONFIG_IEEE80211BE */
@@ -902,23 +903,27 @@
 			   " to VLAN ID %d",
 			   MAC2STR(sta->addr), sta->sae->tmp->vlan_id);
 
-		os_memset(&vlan_desc, 0, sizeof(vlan_desc));
-		vlan_desc.notempty = 1;
-		vlan_desc.untagged = sta->sae->tmp->vlan_id;
-		if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
-			wpa_printf(MSG_INFO,
-				   "Invalid VLAN ID %d in sae_password",
-				   sta->sae->tmp->vlan_id);
-			return;
-		}
+		if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) {
+			os_memset(&vlan_desc, 0, sizeof(vlan_desc));
+			vlan_desc.notempty = 1;
+			vlan_desc.untagged = sta->sae->tmp->vlan_id;
+			if (!hostapd_vlan_valid(hapd->conf->vlan, &vlan_desc)) {
+				wpa_printf(MSG_INFO,
+					   "Invalid VLAN ID %d in sae_password",
+					   sta->sae->tmp->vlan_id);
+				return;
+			}
 
-		if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 ||
-		    ap_sta_bind_vlan(hapd, sta) < 0) {
-			wpa_printf(MSG_INFO,
-				   "Failed to assign VLAN ID %d from sae_password to "
-				   MACSTR, sta->sae->tmp->vlan_id,
-				   MAC2STR(sta->addr));
-			return;
+			if (ap_sta_set_vlan(hapd, sta, &vlan_desc) < 0 ||
+			    ap_sta_bind_vlan(hapd, sta) < 0) {
+				wpa_printf(MSG_INFO,
+					   "Failed to assign VLAN ID %d from sae_password to "
+					   MACSTR, sta->sae->tmp->vlan_id,
+					   MAC2STR(sta->addr));
+				return;
+			}
+		} else {
+			sta->vlan_id = sta->sae->tmp->vlan_id;
 		}
 	}
 #endif /* CONFIG_NO_VLAN */
@@ -1273,7 +1278,8 @@
 		pos = mgmt->u.auth.variable;
 		end = ((const u8 *) mgmt) + len;
 		resp = status_code;
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp, pos, end - pos,
 				"auth-sae-reflection-attack");
 		goto remove_sta;
@@ -1281,7 +1287,8 @@
 
 	if (hapd->conf->sae_commit_override && auth_transaction == 1) {
 		wpa_printf(MSG_DEBUG, "SAE: TESTING - commit override");
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp,
 				wpabuf_head(hapd->conf->sae_commit_override),
 				wpabuf_len(hapd->conf->sae_commit_override),
@@ -1552,7 +1559,8 @@
 			data = wpabuf_alloc_copy(pos, 2);
 
 		sae_sme_send_external_auth_status(hapd, sta, resp);
-		send_auth_reply(hapd, sta, mgmt->sa, mgmt->bssid, WLAN_AUTH_SAE,
+		send_auth_reply(hapd, sta, sta->addr, mgmt->bssid,
+				WLAN_AUTH_SAE,
 				auth_transaction, resp,
 				data ? wpabuf_head(data) : (u8 *) "",
 				data ? wpabuf_len(data) : 0, "auth-sae");
@@ -1664,7 +1672,7 @@
 	dl_list_for_each(q2, &hapd->sae_commit_queue,
 			 struct hostapd_sae_commit_queue, list) {
 		mgmt2 = (const struct ieee80211_mgmt *) q2->msg;
-		if (os_memcmp(mgmt->sa, mgmt2->sa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(mgmt->sa, mgmt2->sa) &&
 		    mgmt->u.auth.auth_transaction ==
 		    mgmt2->u.auth.auth_transaction) {
 			wpa_printf(MSG_DEBUG,
@@ -1695,7 +1703,7 @@
 	dl_list_for_each(q, &hapd->sae_commit_queue,
 			 struct hostapd_sae_commit_queue, list) {
 		mgmt = (const struct ieee80211_mgmt *) q->msg;
-		if (os_memcmp(addr, mgmt->sa, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, mgmt->sa))
 			return 1;
 	}
 
@@ -2021,7 +2029,7 @@
 		}
 
 		os_memcpy(ie_buf, ie, ielen);
-		if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid) < 0) {
+		if (wpa_insert_pmkid(ie_buf, &ielen, pmksa->pmkid, true) < 0) {
 			*resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			goto fail;
 		}
@@ -2149,7 +2157,8 @@
 				    pmk, pmk_len,
 				    sta->fils_erp_pmkid,
 				    session_timeout,
-				    wpa_auth_sta_key_mgmt(sta->wpa_sm)) < 0) {
+				    wpa_auth_sta_key_mgmt(sta->wpa_sm),
+				    NULL) < 0) {
 				wpa_printf(MSG_ERROR,
 					   "FILS: Failed to add PMKSA cache entry based on ERP");
 			}
@@ -2520,8 +2529,8 @@
 		    FILS_SESSION_LEN);
 	os_memcpy(fils->session, elems.fils_session, FILS_SESSION_LEN);
 
-	fils_wd = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION,
-				    WLAN_EID_EXT_WRAPPED_DATA);
+	fils_wd = ieee802_11_defrag(elems.wrapped_data, elems.wrapped_data_len,
+				    true);
 
 	if (!fils_wd) {
 		wpa_printf(MSG_DEBUG, "PASN: FILS: Missing wrapped data");
@@ -2612,7 +2621,7 @@
 		return -1;
 	}
 
-	if (os_memcmp(entry->own_addr, own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(entry->own_addr, own_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "PASN: own addr " MACSTR " and PTKSA entry own addr "
 			   MACSTR " differ",
@@ -2639,8 +2648,10 @@
 	struct pasn_data *pasn = sta->pasn;
 	struct ieee802_11_elems elems;
 	struct wpa_ie_data rsn_data;
+#ifdef CONFIG_FILS
 	struct wpa_pasn_params_data pasn_params;
 	struct wpabuf *wrapped_data = NULL;
+#endif /* CONFIG_FILS */
 
 	if (ieee802_11_parse_elems(mgmt->u.auth.variable,
 				   len - offsetof(struct ieee80211_mgmt,
@@ -2690,8 +2701,8 @@
 		return;
 	}
 	if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) {
-		wrapped_data = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION,
-						 WLAN_EID_EXT_WRAPPED_DATA);
+		wrapped_data = ieee802_11_defrag(elems.wrapped_data,
+						 elems.wrapped_data_len, true);
 		if (!wrapped_data) {
 			wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data");
 			return;
@@ -2906,7 +2917,7 @@
 		goto fail;
 	}
 
-	if (os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(mgmt->sa, hapd->own_addr)) {
 		wpa_printf(MSG_INFO, "Station " MACSTR " not allowed to authenticate",
 			   MAC2STR(sa));
 		resp = WLAN_STATUS_UNSPECIFIED_FAILURE;
@@ -2914,8 +2925,8 @@
 	}
 
 	if (mld_sta &&
-	    (os_memcmp(sa, hapd->own_addr, ETH_ALEN) == 0 ||
-	     os_memcmp(sa, hapd->mld_addr, ETH_ALEN) == 0)) {
+	    (ether_addr_equal(sa, hapd->own_addr) ||
+	     ether_addr_equal(sa, hapd->mld_addr))) {
 		wpa_printf(MSG_INFO,
 			   "Station " MACSTR " not allowed to authenticate",
 			   MAC2STR(sa));
@@ -3068,12 +3079,13 @@
 
 #ifdef CONFIG_IEEE80211BE
 	if (auth_transaction == 1) {
+		ap_sta_free_sta_profile(&sta->mld_info);
 		os_memset(&sta->mld_info, 0, sizeof(sta->mld_info));
 
 		if (mld_sta) {
 			u8 link_id = hapd->mld_link_id;
 
-			sta->mld_info.mld_sta = true;
+			ap_sta_set_mld(sta, true);
 			sta->mld_assoc_link_id = link_id;
 
 			/*
@@ -3236,7 +3248,7 @@
 	  * the MLD MAC address. It is the responsibility of the driver to
 	  * handle the translations.
 	  */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		dst = sta->addr;
 		bssid = hapd->mld_addr;
 	}
@@ -3281,7 +3293,7 @@
 
 	/* Do not assign an AID that is in use on any of the affiliated links
 	 * when finding an AID for a non-AP MLD. */
-	if (hapd->conf->mld_ap) {
+	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
 		int j;
 
 		for (j = 0; j < MAX_NUM_MLD_LINKS; j++) {
@@ -3655,7 +3667,7 @@
 	wpa_hexdump_key(MSG_DEBUG, "OWE: PMK", sta->owe_pmk, sta->owe_pmk_len);
 	wpa_hexdump(MSG_DEBUG, "OWE: PMKID", pmkid, PMKID_LEN);
 	wpa_auth_pmksa_add2(hapd->wpa_auth, sta->addr, sta->owe_pmk,
-			    sta->owe_pmk_len, pmkid, 0, WPA_KEY_MGMT_OWE);
+			    sta->owe_pmk_len, pmkid, 0, WPA_KEY_MGMT_OWE, NULL);
 
 	return WLAN_STATUS_SUCCESS;
 }
@@ -3727,7 +3739,7 @@
 		goto end;
 	}
 #ifdef CONFIG_IEEE80211BE
-	if (sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta))
 		wpa_auth_set_ml_info(sta->wpa_sm, hapd->mld_addr,
 				     sta->mld_assoc_link_id, &sta->mld_info);
 #endif /* CONFIG_IEEE80211BE */
@@ -4009,7 +4021,7 @@
 			}
 
 #ifdef CONFIG_IEEE80211BE
-			if (info->mld_sta) {
+			if (ap_sta_is_mld(hapd, sta)) {
 				wpa_printf(MSG_DEBUG,
 					   "MLD: Set ML info in RSN Authenticator");
 				wpa_auth_set_ml_info(sta->wpa_sm,
@@ -4309,22 +4321,23 @@
 
 #ifdef CONFIG_IEEE80211BE
 
-static size_t ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
-					    u16 status_code,
-					    u8 *buf, size_t buflen)
+static void ieee80211_ml_build_assoc_resp(struct hostapd_data *hapd,
+					  struct mld_link_info *link)
 {
+	u8 buf[EHT_ML_MAX_STA_PROF_LEN];
 	u8 *p = buf;
+	size_t buflen = sizeof(buf);
 
 	/* Capability Info */
 	WPA_PUT_LE16(p, hostapd_own_capab_info(hapd));
 	p += 2;
 
 	/* Status Code */
-	WPA_PUT_LE16(p, status_code);
+	WPA_PUT_LE16(p, link->status);
 	p += 2;
 
-	if (status_code != WLAN_STATUS_SUCCESS)
-		return p - buf;
+	if (link->status != WLAN_STATUS_SUCCESS)
+		goto out;
 
 	/* AID is not included */
 	p = hostapd_eid_supp_rates(hapd, p);
@@ -4362,20 +4375,24 @@
 		p += wpabuf_len(hapd->conf->assocresp_elements);
 	}
 
-	return p - buf;
+out:
+	os_free(link->resp_sta_profile);
+	link->resp_sta_profile = os_memdup(buf, p - buf);
+	link->resp_sta_profile_len = link->resp_sta_profile ? p - buf : 0;
 }
 
 
-static void ieee80211_ml_process_link(struct hostapd_data *hapd,
-				      struct sta_info *origin_sta,
-				      struct mld_link_info *link,
-				      const u8 *ies, size_t ies_len,
-				      bool reassoc)
+static int ieee80211_ml_process_link(struct hostapd_data *hapd,
+				     struct sta_info *origin_sta,
+				     struct mld_link_info *link,
+				     const u8 *ies, size_t ies_len,
+				     bool reassoc, bool offload)
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *mlbuf = NULL;
 	struct sta_info *sta = NULL;
 	u16 status = WLAN_STATUS_SUCCESS;
+	int i;
 
 	wpa_printf(MSG_DEBUG, "MLD: link: link_id=%u, peer=" MACSTR,
 		   hapd->mld_link_id, MAC2STR(link->peer_addr));
@@ -4401,7 +4418,7 @@
 		goto out;
 	}
 
-	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlbuf)
 		goto out;
 
@@ -4421,25 +4438,33 @@
 		goto out;
 	}
 
-	sta->mld_info.mld_sta = true;
+	ap_sta_set_mld(sta, true);
 	sta->mld_assoc_link_id = origin_sta->mld_assoc_link_id;
 
 	os_memcpy(&sta->mld_info, &origin_sta->mld_info, sizeof(sta->mld_info));
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		struct mld_link_info *li = &sta->mld_info.links[i];
 
-	/*
-	 * Get the AID from the station on which the association was performed,
-	 * and mark it as used.
-	 */
-	sta->aid = origin_sta->aid;
-	if (sta->aid == 0) {
-		wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned");
-		status = WLAN_STATUS_UNSPECIFIED_FAILURE;
-		goto out;
+		li->resp_sta_profile = NULL;
+		li->resp_sta_profile_len = 0;
 	}
-	hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32);
-	sta->listen_interval = origin_sta->listen_interval;
-	if (update_ht_state(hapd, sta) > 0)
-		ieee802_11_update_beacons(hapd->iface);
+
+	if (!offload) {
+		/*
+		 * Get the AID from the station on which the association was
+		 * performed, and mark it as used.
+		 */
+		sta->aid = origin_sta->aid;
+		if (sta->aid == 0) {
+			wpa_printf(MSG_DEBUG, "MLD: link: No AID assigned");
+			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
+			goto out;
+		}
+		hapd->sta_aid[(sta->aid - 1) / 32] |= BIT((sta->aid - 1) % 32);
+		sta->listen_interval = origin_sta->listen_interval;
+		if (update_ht_state(hapd, sta) > 0)
+			ieee802_11_update_beacons(hapd->iface);
+	}
 
 	/* RSN Authenticator should always be the one on the original station */
 	wpa_auth_sta_deinit(sta->wpa_sm);
@@ -4465,20 +4490,23 @@
 
 	/* TODO: What other processing is required? */
 
-	if (add_associated_sta(hapd, sta, reassoc))
+	if (!offload && add_associated_sta(hapd, sta, reassoc))
 		status = WLAN_STATUS_AP_UNABLE_TO_HANDLE_NEW_STA;
 out:
 	wpabuf_free(mlbuf);
 	link->status = status;
 
-	wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
-	if (sta && status != WLAN_STATUS_SUCCESS)
-		ap_free_sta(hapd, sta);
+	if (!offload)
+		ieee80211_ml_build_assoc_resp(hapd, link);
 
-	link->resp_sta_profile_len =
-		ieee80211_ml_build_assoc_resp(hapd, link->status,
-					      link->resp_sta_profile,
-					      sizeof(link->resp_sta_profile));
+	wpa_printf(MSG_DEBUG, "MLD: link: status=%u", status);
+	if (status != WLAN_STATUS_SUCCESS) {
+		if (sta)
+			ap_free_sta(hapd, sta);
+		return -1;
+	}
+
+	return 0;
 }
 
 
@@ -4497,16 +4525,17 @@
 #endif /* CONFIG_IEEE80211BE */
 
 
-static void hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
-					  struct sta_info *sta,
-					  const u8 *ies, size_t ies_len,
-					  bool reassoc)
+int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
+				  struct sta_info *sta,
+				  const u8 *ies, size_t ies_len,
+				  bool reassoc, int tx_link_status,
+				  bool offload)
 {
 #ifdef CONFIG_IEEE80211BE
 	unsigned int i, j;
 
 	if (!hostapd_is_mld_ap(hapd))
-		return;
+		return 0;
 
 	/*
 	 * This is not really needed, but make the interaction with the RSN
@@ -4536,22 +4565,29 @@
 				break;
 		}
 
-		if (!iface || j == hapd->iface->interfaces->count) {
+		if (!iface || j == hapd->iface->interfaces->count ||
+		    TEST_FAIL()) {
 			wpa_printf(MSG_DEBUG,
 				   "MLD: No link match for link_id=%u", i);
 
 			link->status = WLAN_STATUS_UNSPECIFIED_FAILURE;
-			link->resp_sta_profile_len =
-				ieee80211_ml_build_assoc_resp(
-					hapd, link->status,
-					link->resp_sta_profile,
-					sizeof(link->resp_sta_profile));
+			if (!offload)
+				ieee80211_ml_build_assoc_resp(hapd, link);
+		} else if (tx_link_status != WLAN_STATUS_SUCCESS) {
+			/* TX link rejected the connection */
+			link->status = WLAN_STATUS_DENIED_TX_LINK_NOT_ACCEPTED;
+			if (!offload)
+				ieee80211_ml_build_assoc_resp(hapd, link);
 		} else {
-			ieee80211_ml_process_link(iface->bss[0], sta, link,
-						  ies, ies_len, reassoc);
+			if (ieee80211_ml_process_link(iface->bss[0], sta, link,
+						      ies, ies_len, reassoc,
+						      offload))
+				return -1;
 		}
 	}
 #endif /* CONFIG_IEEE80211BE */
+
+	return 0;
 }
 
 
@@ -4589,7 +4625,7 @@
 	bool mld_link_sta = false;
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		u8 mld_link_id = hapd->mld_link_id;
 
 		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
@@ -4700,7 +4736,7 @@
 static u16 send_assoc_resp(struct hostapd_data *hapd, struct sta_info *sta,
 			   const u8 *addr, u16 status_code, int reassoc,
 			   const u8 *ies, size_t ies_len, int rssi,
-			   int omit_rsnxe)
+			   int omit_rsnxe, bool allow_mld_addr_trans)
 {
 	int send_len;
 	u8 *buf;
@@ -4750,7 +4786,7 @@
 	 * Once a non-AP MLD is added to the driver, the addressing should use
 	 * MLD MAC address.
 	 */
-	if (hapd->conf->mld_ap && sta && sta->mld_info.mld_sta)
+	if (ap_sta_is_mld(hapd, sta) && allow_mld_addr_trans)
 		sa = hapd->mld_addr;
 #endif /* CONFIG_IEEE80211BE */
 
@@ -4893,7 +4929,7 @@
 #ifdef CONFIG_IEEE80211BE
 	if (hapd->iconf->ieee80211be && !hapd->conf->disable_11be) {
 		if (hapd->conf->mld_ap)
-			p = hostapd_eid_eht_basic_ml(hapd, p, sta, false);
+			p = hostapd_eid_eht_ml_assoc(hapd, sta, p);
 		p = hostapd_eid_eht_capab(hapd, p, IEEE80211_MODE_AP);
 		p = hostapd_eid_eht_operation(hapd, p);
 	}
@@ -5116,7 +5152,8 @@
 	reply_res = send_assoc_resp(hapd, sta, sta->addr, WLAN_STATUS_SUCCESS,
 				    sta->fils_pending_assoc_is_reassoc,
 				    sta->fils_pending_assoc_req,
-				    sta->fils_pending_assoc_req_len, 0, 0);
+				    sta->fils_pending_assoc_req_len, 0, 0,
+				    true);
 	os_free(sta->fils_pending_assoc_req);
 	sta->fils_pending_assoc_req = NULL;
 	sta->fils_pending_assoc_req_len = 0;
@@ -5151,6 +5188,48 @@
 #endif /* CONFIG_FILS */
 
 
+#ifdef CONFIG_IEEE80211BE
+static struct sta_info * handle_mlo_translate(struct hostapd_data *hapd,
+					      const struct ieee80211_mgmt *mgmt,
+					      size_t len, bool reassoc,
+					      struct hostapd_data **assoc_hapd)
+{
+	struct sta_info *sta;
+	struct ieee802_11_elems elems;
+	u8 mld_addr[ETH_ALEN];
+	const u8 *pos;
+
+	if (!hapd->iconf->ieee80211be || hapd->conf->disable_11be)
+		return NULL;
+
+	if (reassoc) {
+		len -= IEEE80211_HDRLEN + sizeof(mgmt->u.reassoc_req);
+		pos = mgmt->u.reassoc_req.variable;
+	} else {
+		len -= IEEE80211_HDRLEN + sizeof(mgmt->u.assoc_req);
+		pos = mgmt->u.assoc_req.variable;
+	}
+
+	if (ieee802_11_parse_elems(pos, len, &elems, 1) == ParseFailed)
+		return NULL;
+
+	if (hostapd_process_ml_assoc_req_addr(hapd, elems.basic_mle,
+					      elems.basic_mle_len,
+					      mld_addr))
+		return NULL;
+
+	sta = ap_get_sta(hapd, mld_addr);
+	if (!sta)
+		return NULL;
+
+	wpa_printf(MSG_DEBUG, "MLD: assoc: mld=" MACSTR ", link=" MACSTR,
+		   MAC2STR(mld_addr), MAC2STR(mgmt->sa));
+
+	return hostapd_ml_get_assoc_sta(hapd, sta, assoc_hapd);
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 static void handle_assoc(struct hostapd_data *hapd,
 			 const struct ieee80211_mgmt *mgmt, size_t len,
 			 int reassoc, int rssi)
@@ -5167,6 +5246,7 @@
 #endif /* CONFIG_FILS */
 	int omit_rsnxe = 0;
 	bool set_beacon = false;
+	bool mld_addrs_not_translated = false;
 
 	if (len < IEEE80211_HDRLEN + (reassoc ? sizeof(mgmt->u.reassoc_req) :
 				      sizeof(mgmt->u.assoc_req))) {
@@ -5224,6 +5304,28 @@
 	}
 
 	sta = ap_get_sta(hapd, mgmt->sa);
+
+#ifdef CONFIG_IEEE80211BE
+	/*
+	 * It is possible that the association frame is from an associated
+	 * non-AP MLD station, that tries to re-associate using different link
+	 * addresses. In such a case, try to find the station based on the AP
+	 * MLD MAC address.
+	 */
+	if (!sta) {
+		struct hostapd_data *assoc_hapd;
+
+		sta = handle_mlo_translate(hapd, mgmt, len, reassoc,
+					   &assoc_hapd);
+		if (sta) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Switching to assoc hapd/station");
+			hapd = assoc_hapd;
+			mld_addrs_not_translated = true;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_IEEE80211R_AP
 	if (sta && sta->auth_alg == WLAN_AUTH_FT &&
 	    (sta->flags & WLAN_STA_AUTH) == 0) {
@@ -5500,8 +5602,9 @@
 	 *    issues with processing other non-Data Class 3 frames during this
 	 *    window.
 	 */
-	if (resp == WLAN_STATUS_SUCCESS)
-		hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc);
+	if (sta)
+		hostapd_process_assoc_ml_info(hapd, sta, pos, left, reassoc,
+					      resp, false);
 
 	if (resp == WLAN_STATUS_SUCCESS && sta &&
 	    add_associated_sta(hapd, sta, reassoc))
@@ -5545,8 +5648,12 @@
 #endif /* CONFIG_FILS */
 
 	if (resp >= 0)
-		reply_res = send_assoc_resp(hapd, sta, mgmt->sa, resp, reassoc,
-					    pos, left, rssi, omit_rsnxe);
+		reply_res = send_assoc_resp(hapd,
+					    mld_addrs_not_translated ?
+					    NULL : sta,
+					    mgmt->sa, resp, reassoc,
+					    pos, left, rssi, omit_rsnxe,
+					    !mld_addrs_not_translated);
 	os_free(tmp);
 
 	/*
@@ -5637,44 +5744,6 @@
 }
 
 
-#ifdef CONFIG_IEEE80211BE
-static struct sta_info *
-hostapd_ml_get_assoc_sta(struct hostapd_data *hapd, struct sta_info *sta,
-			 struct hostapd_data **assoc_hapd)
-{
-	struct hostapd_data *other_hapd = NULL;
-	struct sta_info *tmp_sta;
-
-	*assoc_hapd = hapd;
-
-	/* The station is the one on which the association was performed */
-	if (sta->mld_assoc_link_id == hapd->mld_link_id)
-		return sta;
-
-	other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id);
-	if (!other_hapd) {
-		wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u",
-			   sta->mld_assoc_link_id);
-		return sta;
-	}
-
-	/*
-	 * Iterate over the stations and find the one with the matching link ID
-	 * and association ID.
-	 */
-	for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) {
-		if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id &&
-		    tmp_sta->aid == sta->aid) {
-			*assoc_hapd = other_hapd;
-			return tmp_sta;
-		}
-	}
-
-	return sta;
-}
-#endif /* CONFIG_IEEE80211BE */
-
-
 static bool hostapd_ml_handle_disconnect(struct hostapd_data *hapd,
 					 struct sta_info *sta,
 					 const struct ieee80211_mgmt *mgmt,
@@ -5693,6 +5762,8 @@
 	 * the information about all the other links.
 	 */
 	assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+	if (!assoc_sta)
+		return false;
 
 	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
 		for (i = 0; i < assoc_hapd->iface->interfaces->count; i++) {
@@ -5964,6 +6035,25 @@
 				return 1;
 		}
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+		if (mgmt->u.action.category == WLAN_ACTION_PUBLIC &&
+		    len >= IEEE80211_HDRLEN + 5 &&
+		    mgmt->u.action.u.vs_public_action.action ==
+		    WLAN_PA_VENDOR_SPECIFIC &&
+		    WPA_GET_BE24(mgmt->u.action.u.vs_public_action.oui) ==
+		    OUI_WFA &&
+		    mgmt->u.action.u.vs_public_action.variable[0] ==
+		    NAN_OUI_TYPE) {
+			const u8 *pos, *end;
+
+			pos = mgmt->u.action.u.vs_public_action.variable;
+			end = ((const u8 *) mgmt) + len;
+			pos++;
+			hostapd_nan_usd_rx_sdf(hapd, mgmt->sa, freq,
+					       pos, end - pos);
+			return 1;
+		}
+#endif /* CONFIG_NAN_USD */
 		if (hapd->public_action_cb) {
 			hapd->public_action_cb(hapd->public_action_cb_ctx,
 					       (u8 *) mgmt, len, freq);
@@ -5982,9 +6072,11 @@
 				return 1;
 		}
 		break;
+#ifndef CONFIG_NO_RRM
 	case WLAN_ACTION_RADIO_MEASUREMENT:
 		hostapd_handle_radio_measurement(hapd, (const u8 *) mgmt, len);
 		return 1;
+#endif /* CONFIG_NO_RRM */
 	}
 
 	hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
@@ -6069,6 +6161,10 @@
 	int ret = 0;
 	unsigned int freq;
 	int ssi_signal = fi ? fi->ssi_signal : 0;
+#ifdef CONFIG_NAN_USD
+	static const u8 nan_network_id[ETH_ALEN] =
+		{ 0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00 };
+#endif /* CONFIG_NAN_USD */
 
 	if (len < 24)
 		return 0;
@@ -6084,7 +6180,7 @@
 
 	if (is_multicast_ether_addr(mgmt->sa) ||
 	    is_zero_ether_addr(mgmt->sa) ||
-	    os_memcmp(mgmt->sa, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(mgmt->sa, hapd->own_addr)) {
 		/* Do not process any frames with unexpected/invalid SA so that
 		 * we do not add any state for unexpected STA addresses or end
 		 * up sending out frames to unexpected destination. */
@@ -6110,9 +6206,9 @@
 #endif /* CONFIG_MESH */
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
-	    os_memcmp(mgmt->bssid, hapd->own_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(mgmt->bssid, hapd->own_addr)) {
 		wpa_printf(MSG_INFO, "MGMT: BSSID=" MACSTR " not our address",
 			   MAC2STR(mgmt->bssid));
 		return 0;
@@ -6133,9 +6229,12 @@
 	     stype != WLAN_FC_STYPE_ACTION) &&
 #ifdef CONFIG_IEEE80211BE
 	    !(hapd->conf->mld_ap &&
-	      os_memcmp(hapd->mld_addr, mgmt->bssid, ETH_ALEN) == 0) &&
+	      ether_addr_equal(hapd->mld_addr, mgmt->bssid)) &&
 #endif /* CONFIG_IEEE80211BE */
-	    os_memcmp(mgmt->da, hapd->own_addr, ETH_ALEN) != 0) {
+#ifdef CONFIG_NAN_USD
+	    !ether_addr_equal(mgmt->da, nan_network_id) &&
+#endif /* CONFIG_NAN_USD */
+	    !ether_addr_equal(mgmt->da, hapd->own_addr)) {
 		hostapd_logger(hapd, mgmt->sa, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
 			       "MGMT: DA=" MACSTR " not our address",
@@ -6285,6 +6384,8 @@
 					   struct mld_link_info *link,
 					   bool ok)
 {
+	bool updated = false;
+
 	if (!ok) {
 		hostapd_logger(hapd, link->peer_addr, HOSTAPD_MODULE_IEEE80211,
 			       HOSTAPD_LEVEL_DEBUG,
@@ -6305,9 +6406,11 @@
 	sta->flags &= ~WLAN_STA_WNM_SLEEP_MODE;
 
 	if (!hapd->conf->ieee802_1x && !hapd->conf->wpa)
-		ap_sta_set_authorized(hapd, sta, 1);
+		updated = ap_sta_set_authorized_flag(hapd, sta, 1);
 
 	hostapd_set_sta_flags(hapd, sta);
+	if (updated)
+		ap_sta_set_authorized_event(hapd, sta, 1);
 
 	/*
 	 * TODOs:
@@ -6340,7 +6443,7 @@
 			struct hostapd_data *tmp_hapd =
 				hapd->iface->interfaces->iface[i]->bss[0];
 
-			if (tmp_hapd->conf->mld_ap ||
+			if (!tmp_hapd->conf->mld_ap ||
 			    hapd->conf->mld_id != tmp_hapd->conf->mld_id)
 				continue;
 
@@ -6379,7 +6482,7 @@
 	}
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta &&
+	if (ap_sta_is_mld(hapd, sta) &&
 	    hapd->mld_link_id != sta->mld_assoc_link_id) {
 		/* See ieee80211_ml_link_sta_assoc_cb() for the MLD case */
 		wpa_printf(MSG_DEBUG,
@@ -6589,7 +6692,9 @@
 			     size_t len, int ok)
 {
 	struct sta_info *sta;
+#ifndef CONFIG_NO_RRM
 	const struct rrm_measurement_report_element *report;
+#endif /* CONFIG_NO_RRM */
 
 #ifdef CONFIG_DPP
 	if (len >= IEEE80211_HDRLEN + 6 &&
@@ -6643,6 +6748,7 @@
 	}
 #endif /* CONFIG_HS20 */
 
+#ifndef CONFIG_NO_RRM
 	if (len < 24 + 5 + sizeof(*report))
 		return;
 	report = (const struct rrm_measurement_report_element *)
@@ -6653,6 +6759,7 @@
 	    report->len >= 3 &&
 	    report->type == MEASURE_TYPE_BEACON)
 		hostapd_rrm_beacon_req_tx_status(hapd, mgmt, len, ok);
+#endif /* CONFIG_NO_RRM */
 }
 
 
@@ -6864,7 +6971,7 @@
 	wpa_printf(MSG_DEBUG, "Data/PS-poll frame from not associated STA "
 		   MACSTR, MAC2STR(src));
 	if (is_multicast_ether_addr(src) || is_zero_ether_addr(src) ||
-	    os_memcmp(src, hapd->own_addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(src, hapd->own_addr)) {
 		/* Broadcast bit set in SA or unexpected SA?! Ignore the frame
 		 * silently. */
 		return;
@@ -6963,21 +7070,35 @@
 		tx_pwr_intrpn = REGULATORY_CLIENT_EIRP_PSD;
 
 		/* Default Transmit Power Envelope for Global Operating Class */
-		tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2;
+		if (hapd->iconf->reg_def_cli_eirp_psd != -1)
+			tx_pwr = hapd->iconf->reg_def_cli_eirp_psd;
+		else
+			tx_pwr = REG_PSD_MAX_TXPOWER_FOR_DEFAULT_CLIENT * 2;
+
 		eid = hostapd_add_tpe_info(eid, tx_pwr_count, tx_pwr_intrpn,
 					   REG_DEFAULT_CLIENT, tx_pwr);
 
 		/* Indoor Access Point must include an additional TPE for
 		 * subordinate devices */
-		if (iconf->he_6ghz_reg_pwr_type == HE_6GHZ_INDOOR_AP) {
+		if (he_reg_is_indoor(iconf->he_6ghz_reg_pwr_type)) {
 			/* TODO: Extract PSD limits from channel data */
-			tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2;
+			if (hapd->iconf->reg_sub_cli_eirp_psd != -1)
+				tx_pwr = hapd->iconf->reg_sub_cli_eirp_psd;
+			else
+				tx_pwr = REG_PSD_MAX_TXPOWER_FOR_SUBORDINATE_CLIENT * 2;
 			eid = hostapd_add_tpe_info(eid, tx_pwr_count,
 						   tx_pwr_intrpn,
 						   REG_SUBORDINATE_CLIENT,
 						   tx_pwr);
 		}
 
+		if (iconf->reg_def_cli_eirp != -1 &&
+		    he_reg_is_sp(iconf->he_6ghz_reg_pwr_type))
+			eid = hostapd_add_tpe_info(
+				eid, tx_pwr_count, REGULATORY_CLIENT_EIRP,
+				REG_DEFAULT_CLIENT,
+				hapd->iconf->reg_def_cli_eirp);
+
 		return eid;
 	}
 #endif /* CONFIG_IEEE80211AX */
@@ -7392,7 +7513,7 @@
 		/* BSS parameters */
 		*eid++ = nr->bss_parameters;
 		/* 20 MHz PSD */
-		*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
+		*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER;
 		len += RNR_TBTT_INFO_LEN;
 		*size_offset = (eid - size_offset) - 1;
 	}
@@ -7402,17 +7523,98 @@
 }
 
 
+static bool hostapd_eid_rnr_bss(struct hostapd_data *hapd,
+				struct hostapd_data *reporting_hapd,
+				struct mbssid_ie_profiles *skip_profiles,
+				size_t i, u8 *tbtt_count, size_t *len,
+				u8 **pos)
+{
+	struct hostapd_iface *iface = hapd->iface;
+	struct hostapd_data *bss = iface->bss[i];
+	u8 bss_param = 0;
+	bool ap_mld = false;
+	u8 *eid = *pos;
+
+#ifdef CONFIG_IEEE80211BE
+	ap_mld = !!hapd->conf->mld_ap;
+#endif /* CONFIG_IEEE80211BE */
+
+	if (!bss || !bss->conf || !bss->started ||
+	    bss == reporting_hapd || bss->conf->ignore_broadcast_ssid)
+		return false;
+
+	if (skip_profiles
+	    && i >= skip_profiles->start && i < skip_profiles->end)
+		return false;
+
+	if (*len + RNR_TBTT_INFO_LEN > 255 ||
+	    *tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
+		return true;
+
+	*eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN;
+	os_memcpy(eid, bss->own_addr, ETH_ALEN);
+	eid += ETH_ALEN;
+	os_memcpy(eid, &bss->conf->ssid.short_ssid, 4);
+	eid += 4;
+	if (bss->conf->ssid.short_ssid == reporting_hapd->conf->ssid.short_ssid)
+		bss_param |= RNR_BSS_PARAM_SAME_SSID;
+
+	if (iface->conf->mbssid != MBSSID_DISABLED && iface->num_bss > 1) {
+		bss_param |= RNR_BSS_PARAM_MULTIPLE_BSSID;
+		if (bss == hostapd_mbssid_get_tx_bss(hapd))
+			bss_param |= RNR_BSS_PARAM_TRANSMITTED_BSSID;
+	}
+
+	if (is_6ghz_op_class(hapd->iconf->op_class) &&
+	    bss->conf->unsol_bcast_probe_resp_interval)
+		bss_param |= RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE;
+
+	bss_param |= RNR_BSS_PARAM_CO_LOCATED;
+
+	*eid++ = bss_param;
+	*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER;
+
+	if (!ap_mld) {
+		*len += RNR_TBTT_INFO_LEN;
+	} else {
+#ifdef CONFIG_IEEE80211BE
+		u8 param_ch = hapd->eht_mld_bss_param_change;
+
+		if (reporting_hapd->conf->mld_ap &&
+		    bss->conf->mld_id == reporting_hapd->conf->mld_id)
+			*eid++ = 0;
+		else
+			*eid++ = hapd->conf->mld_id;
+
+		*eid++ = hapd->mld_link_id | ((param_ch & 0xF) << 4);
+		*eid = (param_ch >> 4) & 0xF;
+#ifdef CONFIG_TESTING_OPTIONS
+		if (hapd->conf->mld_indicate_disabled)
+			*eid |= RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED;
+#endif /* CONFIG_TESTING_OPTIONS */
+		eid++;
+
+		*len += RNR_TBTT_INFO_MLD_LEN;
+#endif /* CONFIG_IEEE80211BE */
+	}
+
+	(*tbtt_count)++;
+	*pos = eid;
+
+	return false;
+}
+
+
 static u8 * hostapd_eid_rnr_iface(struct hostapd_data *hapd,
 				  struct hostapd_data *reporting_hapd,
 				  u8 *eid, size_t *current_len,
 				  struct mbssid_ie_profiles *skip_profiles)
 {
-	struct hostapd_data *bss;
 	struct hostapd_iface *iface = hapd->iface;
 	size_t i, start = 0;
 	size_t len = *current_len;
 	u8 *tbtt_count_pos, *eid_start = eid, *size_offset = (eid - len) + 1;
-	u8 tbtt_count = 0, op_class, channel, bss_param;
+	u8 tbtt_count = 0, op_class, channel;
 	bool ap_mld = false;
 
 #ifdef CONFIG_IEEE80211BE
@@ -7447,62 +7649,10 @@
 		len += RNR_TBTT_HEADER_LEN;
 
 		for (i = start; i < iface->num_bss; i++) {
-			bss_param = 0;
-			bss = iface->bss[i];
-			if (!bss || !bss->conf || !bss->started)
-				continue;
-
-			if (bss == reporting_hapd ||
-			    bss->conf->ignore_broadcast_ssid)
-				continue;
-
-			if (skip_profiles &&
-			    i >= skip_profiles->start && i < skip_profiles->end)
-				continue;
-
-			if (len + RNR_TBTT_INFO_LEN > 255 ||
-			    tbtt_count >= RNR_TBTT_INFO_COUNT_MAX)
+			if (hostapd_eid_rnr_bss(hapd, reporting_hapd,
+						skip_profiles, i,
+						&tbtt_count, &len, &eid))
 				break;
-
-			*eid++ = RNR_NEIGHBOR_AP_OFFSET_UNKNOWN;
-			os_memcpy(eid, bss->own_addr, ETH_ALEN);
-			eid += ETH_ALEN;
-			os_memcpy(eid, &bss->conf->ssid.short_ssid, 4);
-			eid += 4;
-			if (bss->conf->ssid.short_ssid ==
-			    reporting_hapd->conf->ssid.short_ssid)
-				bss_param |= RNR_BSS_PARAM_SAME_SSID;
-
-			if (iface->conf->mbssid != MBSSID_DISABLED &&
-			    iface->num_bss > 1) {
-				bss_param |= RNR_BSS_PARAM_MULTIPLE_BSSID;
-				if (bss == hostapd_mbssid_get_tx_bss(hapd))
-					bss_param |=
-						RNR_BSS_PARAM_TRANSMITTED_BSSID;
-			}
-
-			if (is_6ghz_op_class(hapd->iconf->op_class) &&
-			    bss->conf->unsol_bcast_probe_resp_interval)
-				bss_param |=
-					RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE;
-
-			bss_param |= RNR_BSS_PARAM_CO_LOCATED;
-
-			*eid++ = bss_param;
-			*eid++ = RNR_20_MHZ_PSD_MAX_TXPOWER - 1;
-
-			if (!ap_mld) {
-				len += RNR_TBTT_INFO_LEN;
-			} else {
-#ifdef CONFIG_IEEE80211BE
-				*eid++ = hapd->conf->mld_id;
-				*eid++ = hapd->mld_link_id | (1 << 4);
-				*eid++ = 0;
-				len += RNR_TBTT_INFO_MLD_LEN;
-#endif /* CONFIG_IEEE80211BE */
-			}
-
-			tbtt_count += 1;
 		}
 
 		start = i;
@@ -7579,7 +7729,7 @@
 
 	case WLAN_FC_STYPE_ACTION:
 		if (hapd->iface->num_bss > 1 && mode == STANDALONE_6GHZ)
-			eid = hostapd_eid_rnr_iface(hapd, hapd,	eid,
+			eid = hostapd_eid_rnr_iface(hapd, hapd, eid,
 						    &current_len, NULL);
 		break;
 
@@ -7610,7 +7760,18 @@
 					  size_t known_bss_len)
 {
 	struct hostapd_data *tx_bss = hostapd_mbssid_get_tx_bss(hapd);
-	size_t len = 3, i;
+	size_t len, i;
+
+	/* Element ID: 1 octet
+	 * Length: 1 octet
+	 * MaxBSSID Indicator: 1 octet
+	 * Optional Subelements: vatiable
+	 *
+	 * Total fixed length: 3 octets
+	 *
+	 * 1 octet in len for the MaxBSSID Indicator field.
+	 */
+	len = 1;
 
 	for (i = *bss_index; i < hapd->iface->num_bss; i++) {
 		struct hostapd_data *bss = hapd->iface->bss[i];
@@ -7663,7 +7824,9 @@
 	}
 
 	*bss_index = i;
-	return len;
+
+	/* Add 2 octets to get the full size of the element */
+	return len + 2;
 }
 
 
@@ -7792,13 +7955,14 @@
 				eid += 2 + rsnx[1];
 			}
 		}
+		/* List of Element ID values in increasing order */
 		if (!rsn && hostapd_wpa_ie(tx_bss, WLAN_EID_RSN))
 			non_inherit_ie[ie_count++] = WLAN_EID_RSN;
-		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
-			non_inherit_ie[ie_count++] = WLAN_EID_RSNX;
 		if (hapd->conf->xrates_supported &&
 		    !bss->conf->xrates_supported)
 			non_inherit_ie[ie_count++] = WLAN_EID_EXT_SUPP_RATES;
+		if (!rsnx && hostapd_wpa_ie(tx_bss, WLAN_EID_RSNX))
+			non_inherit_ie[ie_count++] = WLAN_EID_RSNX;
 		if (ie_count) {
 			*eid++ = WLAN_EID_EXTENSION;
 			*eid++ = 2 + ie_count + 1;
diff --git a/src/ap/ieee802_11.h b/src/ap/ieee802_11.h
index 4b58fee..5fd380a 100644
--- a/src/ap/ieee802_11.h
+++ b/src/ap/ieee802_11.h
@@ -23,6 +23,7 @@
 struct sae_pk;
 struct sae_pt;
 struct sae_password_entry;
+struct mld_info;
 
 int ieee802_11_mgmt(struct hostapd_data *hapd, const u8 *buf, size_t len,
 		    struct hostapd_frame_info *fi);
@@ -88,8 +89,14 @@
 			   const struct ieee80211_eht_capabilities *src,
 			   struct ieee80211_eht_capabilities *dest,
 			   size_t len);
-u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
-			      struct sta_info *info, bool include_mld_id);
+u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd,
+			       struct mld_info *mld_info,
+			       u8 *eid, bool include_mld_id);
+u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info,
+			      u8 *eid);
+size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd,
+				     struct mld_info *info,
+				     bool include_mld_id);
 struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd);
 const u8 * hostapd_process_ml_auth(struct hostapd_data *hapd,
 				   const struct ieee80211_mgmt *mgmt,
@@ -97,6 +104,9 @@
 u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
 				 struct ieee802_11_elems *elems,
 				 struct sta_info *sta);
+int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd,
+				      const u8 *basic_mle, size_t basic_mle_len,
+				      u8 *mld_addr);
 int hostapd_get_aid(struct hostapd_data *hapd, struct sta_info *sta);
 u16 copy_sta_ht_capab(struct hostapd_data *hapd, struct sta_info *sta,
 		      const u8 *ht_capab);
@@ -245,5 +255,13 @@
 			      struct sta_info *sta, const char *rx_id,
 			      struct sae_password_entry **pw_entry,
 			      struct sae_pt **s_pt, const struct sae_pk **s_pk);
+struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct hostapd_data **assoc_hapd);
+int hostapd_process_assoc_ml_info(struct hostapd_data *hapd,
+				  struct sta_info *sta,
+				  const u8 *ies, size_t ies_len,
+				  bool reassoc, int tx_link_status,
+				  bool offload);
 
 #endif /* IEEE802_11_H */
diff --git a/src/ap/ieee802_11_auth.c b/src/ap/ieee802_11_auth.c
index 4277d82..e723ae7 100644
--- a/src/ap/ieee802_11_auth.c
+++ b/src/ap/ieee802_11_auth.c
@@ -84,7 +84,7 @@
 	os_get_reltime(&now);
 
 	for (entry = hapd->acl_cache; entry; entry = entry->next) {
-		if (os_memcmp(entry->addr, addr, ETH_ALEN) != 0)
+		if (!ether_addr_equal(entry->addr, addr))
 			continue;
 
 		if (os_reltime_expired(&now, &entry->timestamp,
@@ -281,7 +281,7 @@
 
 		query = hapd->acl_queries;
 		while (query) {
-			if (os_memcmp(query->addr, addr, ETH_ALEN) == 0) {
+			if (ether_addr_equal(query->addr, addr)) {
 				/* pending query in RADIUS retransmit queue;
 				 * do not generate a new one */
 				return HOSTAPD_ACL_PENDING;
diff --git a/src/ap/ieee802_11_eht.c b/src/ap/ieee802_11_eht.c
index c1ccda4..840fc20 100644
--- a/src/ap/ieee802_11_eht.c
+++ b/src/ap/ieee802_11_eht.c
@@ -44,12 +44,13 @@
 
 
 static u8 ieee80211_eht_mcs_set_size(enum hostapd_hw_mode mode, u8 opclass,
-				     u8 he_oper_chwidth, const u8 *he_phy_cap,
+				     int he_oper_chwidth, const u8 *he_phy_cap,
 				     const u8 *eht_phy_cap)
 {
 	u8 sz = EHT_PHYCAP_MCS_NSS_LEN_20MHZ_PLUS;
 	bool band24, band5, band6;
 	u8 he_phy_cap_chwidth = ~HE_PHYCAP_CHANNEL_WIDTH_MASK;
+	u8 cap_chwidth;
 
 	switch (he_oper_chwidth) {
 	case CONF_OPER_CHWIDTH_80P80MHZ:
@@ -66,7 +67,11 @@
 		break;
 	}
 
-	he_phy_cap_chwidth &= he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+	cap_chwidth = he_phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
+	if (he_oper_chwidth != -1)
+		he_phy_cap_chwidth &= cap_chwidth;
+	else
+		he_phy_cap_chwidth = cap_chwidth;
 
 	band24 = mode == HOSTAPD_MODE_IEEE80211B ||
 		mode == HOSTAPD_MODE_IEEE80211G ||
@@ -199,12 +204,33 @@
 	struct ieee80211_eht_operation *oper;
 	u8 *pos = eid, seg0 = 0, seg1 = 0;
 	enum oper_chan_width chwidth;
-	size_t elen = 1 + 4 + 3;
+	size_t elen = 1 + 4;
+	bool eht_oper_info_present;
+	u16 punct_bitmap = conf->punct_bitmap;
 
 	if (!hapd->iface->current_mode)
 		return eid;
 
-	if (hapd->iconf->punct_bitmap)
+#ifdef CONFIG_TESTING_OPTIONS
+	if (!punct_bitmap && hapd->conf->eht_oper_puncturing_override) {
+		wpa_printf(MSG_DEBUG, "EHT: Puncturing mask override=0x%x",
+			   hapd->conf->eht_oper_puncturing_override);
+		punct_bitmap = hapd->conf->eht_oper_puncturing_override;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (is_6ghz_op_class(conf->op_class))
+		chwidth = op_class_to_ch_width(conf->op_class);
+	else
+		chwidth = conf->eht_oper_chwidth;
+
+	eht_oper_info_present = chwidth == CONF_OPER_CHWIDTH_320MHZ ||
+		punct_bitmap;
+
+	if (eht_oper_info_present)
+		elen += 3;
+
+	if (punct_bitmap)
 		elen += EHT_OPER_DISABLED_SUBCHAN_BITMAP_SIZE;
 
 	*pos++ = WLAN_EID_EXTENSION;
@@ -212,7 +238,10 @@
 	*pos++ = WLAN_EID_EXT_EHT_OPERATION;
 
 	oper = (struct ieee80211_eht_operation *) pos;
-	oper->oper_params = EHT_OPER_INFO_PRESENT;
+	oper->oper_params = 0;
+
+	if (hapd->iconf->eht_default_pe_duration)
+		oper->oper_params |= EHT_OPER_DEFAULT_PE_DURATION;
 
 	/* TODO: Fill in appropriate EHT-MCS max Nss information */
 	oper->basic_eht_mcs_nss_set[0] = 0x11;
@@ -220,11 +249,10 @@
 	oper->basic_eht_mcs_nss_set[2] = 0x00;
 	oper->basic_eht_mcs_nss_set[3] = 0x00;
 
-	if (is_6ghz_op_class(conf->op_class))
-		chwidth = op_class_to_ch_width(conf->op_class);
-	else
-		chwidth = conf->eht_oper_chwidth;
+	if (!eht_oper_info_present)
+		return pos + elen;
 
+	oper->oper_params |= EHT_OPER_INFO_PRESENT;
 	seg0 = hostapd_get_oper_centr_freq_seg0_idx(conf);
 
 	switch (chwidth) {
@@ -258,10 +286,10 @@
 	oper->oper_info.ccfs0 = seg0 ? seg0 : hapd->iconf->channel;
 	oper->oper_info.ccfs1 = seg1;
 
-	if (hapd->iconf->punct_bitmap) {
+	if (punct_bitmap) {
 		oper->oper_params |= EHT_OPER_DISABLED_SUBCHAN_BITMAP_PRESENT;
 		oper->oper_info.disabled_chan_bitmap =
-			host_to_le16(hapd->iconf->punct_bitmap);
+			host_to_le16(punct_bitmap);
 	}
 
 	return pos + elen;
@@ -338,9 +366,8 @@
 
 
 static bool ieee80211_invalid_eht_cap_size(enum hostapd_hw_mode mode,
-					   u8 opclass, u8 he_oper_chwidth,
-					   const u8 *he_cap, const u8 *eht_cap,
-					   size_t len)
+					   u8 opclass, const u8 *he_cap,
+					   const u8 *eht_cap, size_t len)
 {
 	const struct ieee80211_he_capabilities *he_capab;
 	struct ieee80211_eht_capabilities *cap;
@@ -355,8 +382,8 @@
 	if (len < cap_len)
 		return true;
 
-	cap_len += ieee80211_eht_mcs_set_size(mode, opclass, he_oper_chwidth,
-					      he_phy_cap, cap->phy_cap);
+	cap_len += ieee80211_eht_mcs_set_size(mode, opclass, -1, he_phy_cap,
+					      cap->phy_cap);
 	if (len < cap_len)
 		return true;
 
@@ -380,7 +407,6 @@
 	    !he_capab || he_capab_len < IEEE80211_HE_CAPAB_MIN_LEN ||
 	    !eht_capab ||
 	    ieee80211_invalid_eht_cap_size(mode, hapd->iconf->op_class,
-					   hapd->iconf->he_oper_chwidth,
 					   he_capab, eht_capab,
 					   eht_capab_len) ||
 	    !check_valid_eht_mcs(hapd, eht_capab, opmode)) {
@@ -421,8 +447,9 @@
 }
 
 
-u8 * hostapd_eid_eht_basic_ml(struct hostapd_data *hapd, u8 *eid,
-			      struct sta_info *info, bool include_mld_id)
+static u8 * hostapd_eid_eht_basic_ml_common(struct hostapd_data *hapd,
+					    u8 *eid, struct mld_info *mld_info,
+					    bool include_mld_id)
 {
 	struct wpabuf *buf;
 	u16 control;
@@ -454,7 +481,8 @@
 	 * BSS Parameters Change Count (1) + EML Capabilities (2) +
 	 * MLD Capabilities and Operations (2)
 	 */
-	common_info_len = 13;
+#define EHT_ML_COMMON_INFO_LEN 13
+	common_info_len = EHT_ML_COMMON_INFO_LEN;
 
 	if (include_mld_id) {
 		/* AP MLD ID */
@@ -489,12 +517,12 @@
 		wpabuf_put_u8(buf, hapd->conf->mld_id);
 	}
 
-	if (!info)
+	if (!mld_info)
 		goto out;
 
 	/* Add link info for the other links */
 	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		struct mld_link_info *link = &info->mld_info.links[link_id];
+		struct mld_link_info *link = &mld_info->links[link_id];
 		struct hostapd_data *link_bss;
 
 		/*
@@ -502,8 +530,9 @@
 		 * beacon interval (2) + TSF offset (8) + DTIM info (2) + BSS
 		 * parameters change counter (1) + station profile length.
 		 */
-		const size_t fixed_len = 22;
-		size_t total_len = fixed_len + link->resp_sta_profile_len;
+#define EHT_ML_STA_INFO_LEN 22
+		size_t total_len = EHT_ML_STA_INFO_LEN +
+			link->resp_sta_profile_len;
 
 		/* Skip the local one */
 		if (link_id == hapd->mld_link_id || !link->valid)
@@ -537,7 +566,7 @@
 		/* STA Info */
 
 		/* STA Info Length */
-		wpabuf_put_u8(buf, fixed_len - 2);
+		wpabuf_put_u8(buf, EHT_ML_STA_INFO_LEN - 2);
 		wpabuf_put_data(buf, link->local_addr, ETH_ALEN);
 		wpabuf_put_le16(buf, link_bss->iconf->beacon_int);
 
@@ -552,9 +581,10 @@
 		wpabuf_put_le16(buf, link_bss->conf->dtim_period);
 
 		/* BSS Parameters Change Count */
-		/* TODO: Currently hard code the BSS Parameters Change Count to
-		 * 0x1 */
-		wpabuf_put_u8(buf, 0x1);
+		wpabuf_put_u8(buf, hapd->eht_mld_bss_param_change);
+
+		if (!link->resp_sta_profile)
+			continue;
 
 		/* Fragment the sub element if needed */
 		if (total_len <= 255) {
@@ -564,7 +594,7 @@
 			ptr = link->resp_sta_profile;
 			len = link->resp_sta_profile_len;
 
-			slice_len = 255 - fixed_len;
+			slice_len = 255 - EHT_ML_STA_INFO_LEN;
 
 			wpabuf_put_data(buf, ptr, slice_len);
 			len -= slice_len;
@@ -625,6 +655,151 @@
 }
 
 
+static u8 * hostapd_eid_eht_reconf_ml(struct hostapd_data *hapd, u8 *eid)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	struct hostapd_data *other_hapd;
+	u16 control;
+	u8 *pos = eid;
+	unsigned int i;
+
+	wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML");
+
+	/* First check if the element needs to be added */
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+
+		wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: %u",
+			   other_hapd->eht_mld_link_removal_count);
+
+		if (other_hapd->eht_mld_link_removal_count)
+			break;
+	}
+
+	/* No link is going to be removed */
+	if (i == hapd->iface->interfaces->count)
+		return eid;
+
+	wpa_printf(MSG_DEBUG, "MLD: Reconfiguration ML: Adding element");
+
+	/* The length will be set at the end */
+	*pos++ = WLAN_EID_EXTENSION;
+	*pos++ = 0;
+	*pos++ = WLAN_EID_EXT_MULTI_LINK;
+
+	/* Set the Multi-Link Control field */
+	control = MULTI_LINK_CONTROL_TYPE_RECONF;
+	WPA_PUT_LE16(pos, control);
+	pos += 2;
+
+	/* Common Info doesn't include any information */
+	*pos++ = 1;
+
+	/* Add the per station profiles */
+	for (i = 0; i < hapd->iface->interfaces->count; i++) {
+		other_hapd = hapd->iface->interfaces->iface[i]->bss[0];
+		if (!other_hapd->eht_mld_link_removal_count)
+			continue;
+
+		/* Subelement ID is 0 */
+		*pos++ = 0;
+		*pos++ = 5;
+
+		control = other_hapd->mld_link_id |
+			EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER;
+
+		WPA_PUT_LE16(pos, control);
+		pos += 2;
+
+		/* STA profile length */
+		*pos++ = 3;
+
+		WPA_PUT_LE16(pos, other_hapd->eht_mld_link_removal_count);
+		pos += 2;
+	}
+
+	eid[1] = pos - eid - 2;
+
+	wpa_hexdump(MSG_DEBUG, "MLD: Reconfiguration ML", eid, eid[1] + 2);
+	return pos;
+#else /* CONFIG_TESTING_OPTIONS */
+	return eid;
+#endif /* CONFIG_TESTING_OPTIONS */
+}
+
+
+static size_t hostapd_eid_eht_ml_len(struct mld_info *info,
+				     bool include_mld_id)
+{
+	size_t len = 0;
+	size_t eht_ml_len = 2 + EHT_ML_COMMON_INFO_LEN;
+	u8 link_id;
+
+	if (include_mld_id)
+		eht_ml_len++;
+
+	for (link_id = 0; info && link_id < ARRAY_SIZE(info->links);
+	     link_id++) {
+		struct mld_link_info *link;
+		size_t sta_len = EHT_ML_STA_INFO_LEN;
+
+		link = &info->links[link_id];
+		if (!link->valid)
+			continue;
+
+		sta_len += link->resp_sta_profile_len;
+
+		/* Element data and (fragmentation) headers */
+		eht_ml_len += sta_len;
+		eht_ml_len += 2 + sta_len / 255 * 2;
+	}
+
+	/* Element data */
+	len += eht_ml_len;
+
+	/* First header (254 bytes of data) */
+	len += 3;
+
+	/* Fragmentation headers; +1 for shorter first chunk */
+	len += (eht_ml_len + 1) / 255 * 2;
+
+	return len;
+}
+#undef EHT_ML_COMMON_INFO_LEN
+#undef EHT_ML_STA_INFO_LEN
+
+
+u8 * hostapd_eid_eht_ml_beacon(struct hostapd_data *hapd,
+			       struct mld_info *info,
+			       u8 *eid, bool include_mld_id)
+{
+	eid = hostapd_eid_eht_basic_ml_common(hapd, eid, info, include_mld_id);
+	return hostapd_eid_eht_reconf_ml(hapd, eid);
+}
+
+
+
+u8 * hostapd_eid_eht_ml_assoc(struct hostapd_data *hapd, struct sta_info *info,
+			      u8 *eid)
+{
+	if (!ap_sta_is_mld(hapd, info))
+		return eid;
+
+	eid = hostapd_eid_eht_basic_ml_common(hapd, eid, &info->mld_info,
+					      false);
+	ap_sta_free_sta_profile(&info->mld_info);
+	return hostapd_eid_eht_reconf_ml(hapd, eid);
+}
+
+
+size_t hostapd_eid_eht_ml_beacon_len(struct hostapd_data *hapd,
+				     struct mld_info *info,
+				     bool include_mld_id)
+{
+	return hostapd_eid_eht_ml_len(info, include_mld_id);
+}
+
+
 struct wpabuf * hostapd_ml_auth_resp(struct hostapd_data *hapd)
 {
 	struct wpabuf *buf = wpabuf_alloc(12);
@@ -841,11 +1016,12 @@
 
 
 static int hostapd_mld_validate_assoc_info(struct hostapd_data *hapd,
-					   struct mld_info *info)
+					   struct sta_info *sta)
 {
 	u8 i, link_id;
+	struct mld_info *info = &sta->mld_info;
 
-	if (!info->mld_sta) {
+	if (!ap_sta_is_mld(hapd, sta)) {
 		wpa_printf(MSG_DEBUG, "MLD: Not a non-AP MLD");
 		return 0;
 	}
@@ -894,6 +1070,85 @@
 }
 
 
+int hostapd_process_ml_assoc_req_addr(struct hostapd_data *hapd,
+				      const u8 *basic_mle, size_t basic_mle_len,
+				      u8 *mld_addr)
+{
+	struct wpabuf *mlbuf = ieee802_11_defrag(basic_mle, basic_mle_len,
+						 true);
+	struct ieee80211_eht_ml *ml;
+	struct eht_ml_basic_common_info *common_info;
+	size_t ml_len, common_info_len;
+	int ret = -1;
+	u16 ml_control;
+
+	if (!mlbuf)
+		return WLAN_STATUS_SUCCESS;
+
+	ml = (struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+	ml_len = wpabuf_len(mlbuf);
+
+	if (ml_len < sizeof(*ml))
+		goto out;
+
+	ml_control = le_to_host16(ml->ml_control);
+	if ((ml_control & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_BASIC) {
+		wpa_printf(MSG_DEBUG, "MLD: Invalid ML type=%u",
+			   ml_control & MULTI_LINK_CONTROL_TYPE_MASK);
+		goto out;
+	}
+
+	/* Common Info Length and MLD MAC Address must always be present */
+	common_info_len = 1 + ETH_ALEN;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_LINK_ID) {
+		wpa_printf(MSG_DEBUG, "MLD: Link ID Info not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: BSS Parameters Change Count not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Medium Synchronization Delay Information not expected");
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA)
+		common_info_len += 2;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA)
+		common_info_len += 2;
+
+	if (sizeof(*ml) + common_info_len > ml_len) {
+		wpa_printf(MSG_DEBUG, "MLD: Not enough bytes for common info");
+		goto out;
+	}
+
+	common_info = (struct eht_ml_basic_common_info *) ml->variable;
+
+	/* Common information length includes the length octet */
+	if (common_info->len != common_info_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Invalid common info len=%u", common_info->len);
+		goto out;
+	}
+
+	/* Get the MLD MAC Address */
+	os_memcpy(mld_addr, common_info->mld_addr, ETH_ALEN);
+	ret = 0;
+
+out:
+	wpabuf_free(mlbuf);
+	return ret;
+}
+
+
 u16 hostapd_process_ml_assoc_req(struct hostapd_data *hapd,
 				 struct ieee802_11_elems *elems,
 				 struct sta_info *sta)
@@ -908,7 +1163,7 @@
 	int ret = -1;
 	u16 ml_control;
 
-	mlbuf = ieee802_11_defrag_mle(elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlbuf = ieee802_11_defrag(elems->basic_mle, elems->basic_mle_len, true);
 	if (!mlbuf)
 		return WLAN_STATUS_SUCCESS;
 
@@ -990,8 +1245,8 @@
 		   info->common_info.eml_capa, info->common_info.mld_capa);
 
 	/* Check the MLD MAC Address */
-	if (os_memcmp(info->common_info.mld_addr, common_info->mld_addr,
-		      ETH_ALEN) != 0) {
+	if (!ether_addr_equal(info->common_info.mld_addr,
+			      common_info->mld_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: MLD address mismatch between authentication ("
 			   MACSTR ") and association (" MACSTR ")",
@@ -1000,7 +1255,7 @@
 		goto out;
 	}
 
-	info->links[hapd->mld_link_id].valid = true;
+	info->links[hapd->mld_link_id].valid = 1;
 
 	/* Parse the link info field */
 	ml_len -= sizeof(*ml) + common_info_len;
@@ -1031,9 +1286,11 @@
 
 		if (*pos != MULTI_LINK_SUB_ELEM_ID_PER_STA_PROFILE) {
 			wpa_printf(MSG_DEBUG,
-				   "MLD: Unexpected Multi-Link element subelement ID=%u",
+				   "MLD: Skip unknown Multi-Link element subelement ID=%u",
 				   *pos);
-			goto out;
+			pos += 2 + sub_elem_len;
+			ml_len -= 2 + sub_elem_len;
+			continue;
 		}
 
 		/* Skip the subelement ID and the length */
@@ -1138,7 +1395,7 @@
 		goto out;
 	}
 
-	ret = hostapd_mld_validate_assoc_info(hapd, info);
+	ret = hostapd_mld_validate_assoc_info(hapd, sta);
 out:
 	wpabuf_free(mlbuf);
 	if (ret) {
diff --git a/src/ap/ieee802_11_he.c b/src/ap/ieee802_11_he.c
index 548a448..4b693a7 100644
--- a/src/ap/ieee802_11_he.c
+++ b/src/ap/ieee802_11_he.c
@@ -254,16 +254,15 @@
 			control = 3;
 		else
 			control = center_idx_to_bw_6ghz(seg0);
-		if (hapd->iconf->he_6ghz_reg_pwr_type == 1)
-			control |= HE_6GHZ_STANDARD_POWER_AP <<
-				HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
-		else
-			control |= HE_6GHZ_INDOOR_AP <<
-				HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
+
+		control |= hapd->iconf->he_6ghz_reg_pwr_type <<
+			HE_6GHZ_OPER_INFO_CTRL_REG_INFO_SHIFT;
+
 		*pos++ = control;
 
 		/* Channel Center Freq Seg0/Seg1 */
-		if (oper_chwidth == 2) {
+		if (oper_chwidth == CONF_OPER_CHWIDTH_160MHZ ||
+		    oper_chwidth == CONF_OPER_CHWIDTH_320MHZ) {
 			/*
 			 * Seg 0 indicates the channel center frequency index of
 			 * the 160 MHz channel.
diff --git a/src/ap/ieee802_11_shared.c b/src/ap/ieee802_11_shared.c
index 31dfb62..0c38483 100644
--- a/src/ap/ieee802_11_shared.c
+++ b/src/ap/ieee802_11_shared.c
@@ -1,6 +1,6 @@
 /*
  * hostapd / IEEE 802.11 Management
- * Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2002-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -21,16 +21,25 @@
 #include "ieee802_11.h"
 
 
+static u8 * hostapd_eid_timeout_interval(u8 *pos, u8 type, u32 value)
+{
+	*pos++ = WLAN_EID_TIMEOUT_INTERVAL;
+	*pos++ = 5;
+	*pos++ = type;
+	WPA_PUT_LE32(pos, value);
+	pos += 4;
+
+	return pos;
+}
+
+
 u8 * hostapd_eid_assoc_comeback_time(struct hostapd_data *hapd,
 				     struct sta_info *sta, u8 *eid)
 {
-	u8 *pos = eid;
 	u32 timeout, tu;
 	struct os_reltime now, passed;
+	u8 type = WLAN_TIMEOUT_ASSOC_COMEBACK;
 
-	*pos++ = WLAN_EID_TIMEOUT_INTERVAL;
-	*pos++ = 5;
-	*pos++ = WLAN_TIMEOUT_ASSOC_COMEBACK;
 	os_get_reltime(&now);
 	os_reltime_sub(&now, &sta->sa_query_start, &passed);
 	tu = (passed.sec * 1000000 + passed.usec) / 1024;
@@ -40,10 +49,12 @@
 		timeout = 0;
 	if (timeout < hapd->conf->assoc_sa_query_max_timeout)
 		timeout++; /* add some extra time for local timers */
-	WPA_PUT_LE32(pos, timeout);
-	pos += 4;
 
-	return pos;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (hapd->conf->test_assoc_comeback_type != -1)
+		type = hapd->conf->test_assoc_comeback_type;
+#endif /* CONFIG_TESTING_OPTIONS */
+	return hostapd_eid_timeout_interval(eid, type, timeout);
 }
 
 
@@ -51,13 +62,14 @@
 void ieee802_11_send_sa_query_req(struct hostapd_data *hapd,
 				  const u8 *addr, const u8 *trans_id)
 {
-#ifdef CONFIG_OCV
-	struct sta_info *sta;
-#endif /* CONFIG_OCV */
+#if defined(CONFIG_OCV) || defined(CONFIG_IEEE80211BE)
+	struct sta_info *sta = ap_get_sta(hapd, addr);
+#endif /* CONFIG_OCV || CONFIG_IEEE80211BE */
 	struct ieee80211_mgmt *mgmt;
 	u8 *oci_ie = NULL;
 	u8 oci_ie_len = 0;
 	u8 *end;
+	const u8 *own_addr = hapd->own_addr;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Request to "
 		   MACSTR, MAC2STR(addr));
@@ -65,7 +77,6 @@
 		    trans_id, WLAN_SA_QUERY_TR_ID_LEN);
 
 #ifdef CONFIG_OCV
-	sta = ap_get_sta(hapd, addr);
 	if (sta && wpa_auth_uses_ocv(sta->wpa_sm)) {
 		struct wpa_channel_info ci;
 
@@ -108,11 +119,16 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_SA_QUERY;
 	mgmt->u.action.u.sa_query_req.action = WLAN_SA_QUERY_REQUEST;
 	os_memcpy(mgmt->u.action.u.sa_query_req.trans_id, trans_id,
@@ -141,6 +157,7 @@
 	u8 *oci_ie = NULL;
 	u8 oci_ie_len = 0;
 	u8 *end;
+	const u8 *own_addr = hapd->own_addr;
 
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Received SA Query Request from "
 		   MACSTR, MAC2STR(sa));
@@ -200,11 +217,16 @@
 	wpa_printf(MSG_DEBUG, "IEEE 802.11: Sending SA Query Response to "
 		   MACSTR, MAC2STR(sa));
 
+#ifdef CONFIG_IEEE80211BE
+	if (ap_sta_is_mld(hapd, sta))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
 	resp->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(resp->da, sa, ETH_ALEN);
-	os_memcpy(resp->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(resp->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(resp->sa, own_addr, ETH_ALEN);
+	os_memcpy(resp->bssid, own_addr, ETH_ALEN);
 	resp->u.action.category = WLAN_ACTION_SA_QUERY;
 	resp->u.action.u.sa_query_req.action = WLAN_SA_QUERY_RESPONSE;
 	os_memcpy(resp->u.action.u.sa_query_req.trans_id, trans_id,
@@ -1137,3 +1159,44 @@
 
 	return WLAN_STATUS_SUCCESS;
 }
+
+
+struct sta_info * hostapd_ml_get_assoc_sta(struct hostapd_data *hapd,
+					   struct sta_info *sta,
+					   struct hostapd_data **assoc_hapd)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *other_hapd = NULL;
+	struct sta_info *tmp_sta;
+
+	if (!ap_sta_is_mld(hapd, sta))
+		return NULL;
+
+	*assoc_hapd = hapd;
+
+	/* The station is the one on which the association was performed */
+	if (sta->mld_assoc_link_id == hapd->mld_link_id)
+		return sta;
+
+	other_hapd = hostapd_mld_get_link_bss(hapd, sta->mld_assoc_link_id);
+	if (!other_hapd) {
+		wpa_printf(MSG_DEBUG, "MLD: No link match for link_id=%u",
+			   sta->mld_assoc_link_id);
+		return sta;
+	}
+
+	/*
+	 * Iterate over the stations and find the one with the matching link ID
+	 * and association ID.
+	 */
+	for (tmp_sta = other_hapd->sta_list; tmp_sta; tmp_sta = tmp_sta->next) {
+		if (tmp_sta->mld_assoc_link_id == sta->mld_assoc_link_id &&
+		    tmp_sta->aid == sta->aid) {
+			*assoc_hapd = other_hapd;
+			return tmp_sta;
+		}
+	}
+#endif /* CONFIG_IEEE80211BE */
+
+	return sta;
+}
diff --git a/src/ap/ieee802_1x.c b/src/ap/ieee802_1x.c
index 052231e..f13c60a 100644
--- a/src/ap/ieee802_1x.c
+++ b/src/ap/ieee802_1x.c
@@ -114,12 +114,15 @@
 				      bool authorized, bool mld)
 {
 	int res;
+	bool update;
 
 	if (sta->flags & WLAN_STA_PREAUTH)
 		return;
 
-	ap_sta_set_authorized(hapd, sta, authorized);
+	update = ap_sta_set_authorized_flag(hapd, sta, authorized);
 	res = hostapd_set_authorized(hapd, sta, authorized);
+	if (update)
+		ap_sta_set_authorized_event(hapd, sta, authorized);
 	hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE8021X,
 		       HOSTAPD_LEVEL_DEBUG, "%sauthorizing port",
 		       authorized ? "" : "un");
diff --git a/src/ap/nan_usd_ap.c b/src/ap/nan_usd_ap.c
new file mode 100644
index 0000000..52a967a
--- /dev/null
+++ b/src/ap/nan_usd_ap.c
@@ -0,0 +1,267 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/wpa_ctrl.h"
+#include "common/nan_de.h"
+#include "hostapd.h"
+#include "ap_drv_ops.h"
+#include "nan_usd_ap.h"
+
+
+static int hostapd_nan_de_tx(void *ctx, unsigned int freq,
+			     unsigned int wait_time,
+			     const u8 *dst, const u8 *src, const u8 *bssid,
+			     const struct wpabuf *buf)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_printf(MSG_DEBUG, "NAN: TX NAN SDF A1=" MACSTR " A2=" MACSTR
+		   " A3=" MACSTR " len=%zu",
+		   MAC2STR(dst), MAC2STR(src), MAC2STR(bssid),
+		   wpabuf_len(buf));
+
+	/* TODO: Force use of OFDM */
+	return hostapd_drv_send_action(hapd, hapd->iface->freq, 0, dst,
+				       wpabuf_head(buf), wpabuf_len(buf));
+}
+
+
+static int hostapd_nan_de_listen(void *ctx, unsigned int freq,
+			      unsigned int duration)
+{
+	return 0;
+}
+
+
+static void
+hostapd_nan_de_discovery_result(void *ctx, int subscribe_id,
+				enum nan_service_protocol_type srv_proto_type,
+				const u8 *ssi, size_t ssi_len,
+				int peer_publish_id, const u8 *peer_addr,
+				bool fsd, bool fsd_gas)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_DISCOVERY_RESULT
+		"subscribe_id=%d publish_id=%d address=" MACSTR
+		" fsd=%d fsd_gas=%d srv_proto_type=%u ssi=%s",
+		subscribe_id, peer_publish_id, MAC2STR(peer_addr),
+		fsd, fsd_gas, srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static void
+hostapd_nan_de_replied(void *ctx, int publish_id, const u8 *peer_addr,
+		       int peer_subscribe_id,
+		       enum nan_service_protocol_type srv_proto_type,
+		       const u8 *ssi, size_t ssi_len)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_REPLIED
+		"publish_id=%d address=" MACSTR
+		" subscribe_id=%d srv_proto_type=%u ssi=%s",
+		publish_id, MAC2STR(peer_addr), peer_subscribe_id,
+		srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static const char * nan_reason_txt(enum nan_de_reason reason)
+{
+	switch (reason) {
+	case NAN_DE_REASON_TIMEOUT:
+		return "timeout";
+	case NAN_DE_REASON_USER_REQUEST:
+		return "user-request";
+	case NAN_DE_REASON_FAILURE:
+		return "failure";
+	}
+
+	return "unknown";
+}
+
+
+static void hostapd_nan_de_publish_terminated(void *ctx, int publish_id,
+					      enum nan_de_reason reason)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_PUBLISH_TERMINATED
+		"publish_id=%d reason=%s",
+		publish_id, nan_reason_txt(reason));
+}
+
+
+static void hostapd_nan_de_subscribe_terminated(void *ctx, int subscribe_id,
+						enum nan_de_reason reason)
+{
+	struct hostapd_data *hapd = ctx;
+
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_SUBSCRIBE_TERMINATED
+		"subscribe_id=%d reason=%s",
+		subscribe_id, nan_reason_txt(reason));
+}
+
+
+static void hostapd_nan_de_receive(void *ctx, int id, int peer_instance_id,
+				   const u8 *ssi, size_t ssi_len,
+				   const u8 *peer_addr)
+{
+	struct hostapd_data *hapd = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(hapd->msg_ctx, MSG_INFO, NAN_RECEIVE
+		"id=%d peer_instance_id=%d address=" MACSTR " ssi=%s",
+		id, peer_instance_id, MAC2STR(peer_addr), ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+int hostapd_nan_usd_init(struct hostapd_data *hapd)
+{
+	struct nan_callbacks cb;
+
+	os_memset(&cb, 0, sizeof(cb));
+	cb.ctx = hapd;
+	cb.tx = hostapd_nan_de_tx;
+	cb.listen = hostapd_nan_de_listen;
+	cb.discovery_result = hostapd_nan_de_discovery_result;
+	cb.replied = hostapd_nan_de_replied;
+	cb.publish_terminated = hostapd_nan_de_publish_terminated;
+	cb.subscribe_terminated = hostapd_nan_de_subscribe_terminated;
+	cb.receive = hostapd_nan_de_receive;
+
+	hapd->nan_de = nan_de_init(hapd->own_addr, true, &cb);
+	if (!hapd->nan_de)
+		return -1;
+	return 0;
+}
+
+
+void hostapd_nan_usd_deinit(struct hostapd_data *hapd)
+{
+	nan_de_deinit(hapd->nan_de);
+	hapd->nan_de = NULL;
+}
+
+
+void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src,
+			    unsigned int freq, const u8 *buf, size_t len)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_rx_sdf(hapd->nan_de, src, freq, buf, len);
+}
+
+
+void hostapd_nan_usd_flush(struct hostapd_data *hapd)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_flush(hapd->nan_de);
+}
+
+
+int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name,
+			    enum nan_service_protocol_type srv_proto_type,
+			    const struct wpabuf *ssi,
+			    struct nan_publish_params *params)
+{
+	int publish_id;
+	struct wpabuf *elems = NULL;
+
+	if (!hapd->nan_de)
+		return -1;
+
+	publish_id = nan_de_publish(hapd->nan_de, service_name, srv_proto_type,
+				    ssi, elems, params);
+	wpabuf_free(elems);
+	return publish_id;
+}
+
+
+void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_cancel_publish(hapd->nan_de, publish_id);
+}
+
+
+int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id,
+				   const struct wpabuf *ssi)
+{
+	int ret;
+
+	if (!hapd->nan_de)
+		return -1;
+	ret = nan_de_update_publish(hapd->nan_de, publish_id, ssi);
+	return ret;
+}
+
+
+int hostapd_nan_usd_subscribe(struct hostapd_data *hapd,
+			      const char *service_name,
+			      enum nan_service_protocol_type srv_proto_type,
+			      const struct wpabuf *ssi,
+			      struct nan_subscribe_params *params)
+{
+	int subscribe_id;
+	struct wpabuf *elems = NULL;
+
+	if (!hapd->nan_de)
+		return -1;
+
+	subscribe_id = nan_de_subscribe(hapd->nan_de, service_name,
+					srv_proto_type, ssi, elems, params);
+	wpabuf_free(elems);
+	return subscribe_id;
+}
+
+
+void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd,
+				      int subscribe_id)
+{
+	if (!hapd->nan_de)
+		return;
+	nan_de_cancel_subscribe(hapd->nan_de, subscribe_id);
+}
+
+
+int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle,
+			     const struct wpabuf *ssi,
+			     const struct wpabuf *elems,
+			     const u8 *peer_addr, u8 req_instance_id)
+{
+	if (!hapd->nan_de)
+		return -1;
+	return nan_de_transmit(hapd->nan_de, handle, ssi, elems, peer_addr,
+			       req_instance_id);
+}
diff --git a/src/ap/nan_usd_ap.h b/src/ap/nan_usd_ap.h
new file mode 100644
index 0000000..58ff5fc
--- /dev/null
+++ b/src/ap/nan_usd_ap.h
@@ -0,0 +1,46 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef NAN_USD_AP_H
+#define NAN_USD_AP_H
+
+struct nan_subscribe_params;
+struct nan_publish_params;
+enum nan_service_protocol_type;
+
+int hostapd_nan_usd_init(struct hostapd_data *hapd);
+void hostapd_nan_usd_deinit(struct hostapd_data *hapd);
+void hostapd_nan_usd_rx_sdf(struct hostapd_data *hapd, const u8 *src,
+			    unsigned int freq, const u8 *buf, size_t len);
+void hostapd_nan_usd_flush(struct hostapd_data *hapd);
+int hostapd_nan_usd_publish(struct hostapd_data *hapd, const char *service_name,
+			    enum nan_service_protocol_type srv_proto_type,
+			    const struct wpabuf *ssi,
+			    struct nan_publish_params *params);
+void hostapd_nan_usd_cancel_publish(struct hostapd_data *hapd, int publish_id);
+int hostapd_nan_usd_update_publish(struct hostapd_data *hapd, int publish_id,
+				   const struct wpabuf *ssi);
+int hostapd_nan_usd_subscribe(struct hostapd_data *hapd,
+			      const char *service_name,
+			      enum nan_service_protocol_type srv_proto_type,
+			      const struct wpabuf *ssi,
+			      struct nan_subscribe_params *params);
+void hostapd_nan_usd_cancel_subscribe(struct hostapd_data *hapd,
+				      int subscribe_id);
+int hostapd_nan_usd_transmit(struct hostapd_data *hapd, int handle,
+			     const struct wpabuf *ssi,
+			     const struct wpabuf *elems,
+			     const u8 *peer_addr, u8 req_instance_id);
+void hostapd_nan_usd_remain_on_channel_cb(struct hostapd_data *hapd,
+					  unsigned int freq,
+					  unsigned int duration);
+void hostapd_nan_usd_cancel_remain_on_channel_cb(struct hostapd_data *hapd,
+						 unsigned int freq);
+void hostapd_nan_usd_tx_wait_expire(struct hostapd_data *hapd);
+
+#endif /* NAN_USD_AP_H */
diff --git a/src/ap/neighbor_db.c b/src/ap/neighbor_db.c
index 5b276e8..2a25ae2 100644
--- a/src/ap/neighbor_db.c
+++ b/src/ap/neighbor_db.c
@@ -24,7 +24,7 @@
 
 	dl_list_for_each(nr, &hapd->nr_db, struct hostapd_neighbor_entry,
 			 list) {
-		if (os_memcmp(bssid, nr->bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(bssid, nr->bssid) &&
 		    (!ssid ||
 		     (ssid->ssid_len == nr->ssid.ssid_len &&
 		      os_memcmp(ssid->ssid, nr->ssid.ssid,
diff --git a/src/ap/pmksa_cache_auth.c b/src/ap/pmksa_cache_auth.c
index ee4232f..2fce838 100644
--- a/src/ap/pmksa_cache_auth.c
+++ b/src/ap/pmksa_cache_auth.c
@@ -487,14 +487,14 @@
 		for (entry = pmksa->pmkid[PMKID_HASH(pmkid)]; entry;
 		     entry = entry->hnext) {
 			if ((spa == NULL ||
-			     os_memcmp(entry->spa, spa, ETH_ALEN) == 0) &&
+			     ether_addr_equal(entry->spa, spa)) &&
 			    os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0)
 				return entry;
 		}
 	} else {
 		for (entry = pmksa->pmksa; entry; entry = entry->next) {
 			if (spa == NULL ||
-			    os_memcmp(entry->spa, spa, ETH_ALEN) == 0)
+			    ether_addr_equal(entry->spa, spa))
 				return entry;
 		}
 	}
@@ -521,7 +521,7 @@
 	u8 new_pmkid[PMKID_LEN];
 
 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
-		if (os_memcmp(entry->spa, spa, ETH_ALEN) != 0)
+		if (!ether_addr_equal(entry->spa, spa))
 			continue;
 		if (wpa_key_mgmt_sae(entry->akmp) ||
 		    wpa_key_mgmt_fils(entry->akmp)) {
@@ -575,7 +575,7 @@
 	int match = 0;
 
 	if (attr->sta_addr) {
-		if (os_memcmp(attr->sta_addr, entry->spa, ETH_ALEN) != 0)
+		if (!ether_addr_equal(attr->sta_addr, entry->spa))
 			return 0;
 		match++;
 	}
@@ -717,7 +717,7 @@
 	 * <BSSID> <PMKID> <PMK> <expiration in seconds>
 	 */
 	for (entry = pmksa->pmksa; entry; entry = entry->next) {
-		if (addr && os_memcmp(entry->spa, addr, ETH_ALEN) != 0)
+		if (addr && !ether_addr_equal(entry->spa, addr))
 			continue;
 
 		ret = os_snprintf(pos, end - pos, MACSTR " ",
diff --git a/src/ap/preauth_auth.c b/src/ap/preauth_auth.c
index 3284a10..cb225c6 100644
--- a/src/ap/preauth_auth.c
+++ b/src/ap/preauth_auth.c
@@ -58,7 +58,7 @@
 	ethhdr = (struct l2_ethhdr *) buf;
 	hdr = (struct ieee802_1x_hdr *) (ethhdr + 1);
 
-	if (os_memcmp(ethhdr->h_dest, hapd->own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(ethhdr->h_dest, hapd->own_addr)) {
 		wpa_printf(MSG_DEBUG, "RSN: pre-auth for foreign address "
 			   MACSTR, MAC2STR(ethhdr->h_dest));
 		return;
diff --git a/src/ap/sta_info.c b/src/ap/sta_info.c
index a00f896..2178d65 100644
--- a/src/ap/sta_info.c
+++ b/src/ap/sta_info.c
@@ -92,7 +92,7 @@
 		if (p2p_dev_addr == NULL)
 			continue;
 
-		if (os_memcmp(p2p_dev_addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(p2p_dev_addr, addr))
 			return sta;
 	}
 
@@ -140,7 +140,7 @@
 	}
 
 	while (s->hnext != NULL &&
-	       os_memcmp(s->hnext->addr, sta->addr, ETH_ALEN) != 0)
+	       !ether_addr_equal(s->hnext->addr, sta->addr))
 		s = s->hnext;
 	if (s->hnext != NULL)
 		s->hnext = s->hnext->hnext;
@@ -303,7 +303,7 @@
 	ieee802_1x_free_station(hapd, sta);
 
 #ifdef CONFIG_IEEE80211BE
-	if (!hapd->conf->mld_ap || !sta->mld_info.mld_sta ||
+	if (!ap_sta_is_mld(hapd, sta) ||
 	    hapd->mld_link_id == sta->mld_assoc_link_id)
 		wpa_auth_sta_deinit(sta->wpa_sm);
 #else
@@ -350,6 +350,7 @@
 #ifdef CONFIG_INTERWORKING
 	if (sta->gas_dialog) {
 		int i;
+
 		for (i = 0; i < GAS_DIALOG_MAX; i++)
 			gas_serv_dialog_clear(&sta->gas_dialog[i]);
 		os_free(sta->gas_dialog);
@@ -420,6 +421,10 @@
 
 	os_free(sta->ifname_wds);
 
+#ifdef CONFIG_IEEE80211BE
+	ap_sta_free_sta_profile(&sta->mld_info);
+#endif /* CONFIG_IEEE80211BE */
+
 #ifdef CONFIG_TESTING_OPTIONS
 	os_free(sta->sae_postponed_commit);
 	forced_memzero(sta->last_tk, WPA_TK_MAX_LEN);
@@ -846,32 +851,20 @@
 }
 
 
-void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
-			 u16 reason)
+static void ap_sta_disconnect_common(struct hostapd_data *hapd,
+				     struct sta_info *sta, unsigned int timeout)
 {
-	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
-		   hapd->conf->iface, MAC2STR(sta->addr));
 	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
-	if (hapd->iface->current_mode &&
-	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
-		/* Skip deauthentication in DMG/IEEE 802.11ad */
-		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
-				WLAN_STA_ASSOC_REQ_OK);
-		sta->timeout_next = STA_REMOVE;
-	} else {
-		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
-		sta->timeout_next = STA_DEAUTH;
-	}
+
 	ap_sta_set_authorized(hapd, sta, 0);
 	hostapd_set_sta_flags(hapd, sta);
-	wpa_printf(MSG_DEBUG, "%s: reschedule ap_handle_timer timeout "
-		   "for " MACSTR " (%d seconds - "
-		   "AP_MAX_INACTIVITY_AFTER_DISASSOC)",
-		   __func__, MAC2STR(sta->addr),
-		   AP_MAX_INACTIVITY_AFTER_DISASSOC);
+
+	wpa_printf(MSG_DEBUG,
+		   "reschedule ap_handle_timer timeout (%u sec) for " MACSTR,
+		   MAC2STR(sta->addr), timeout);
+
 	eloop_cancel_timeout(ap_handle_timer, hapd, sta);
-	eloop_register_timeout(AP_MAX_INACTIVITY_AFTER_DISASSOC, 0,
-			       ap_handle_timer, hapd, sta);
+	eloop_register_timeout(timeout, 0, ap_handle_timer, hapd, sta);
 	accounting_sta_stop(hapd, sta);
 	ieee802_1x_free_station(hapd, sta);
 #ifdef CONFIG_IEEE80211BE
@@ -883,6 +876,27 @@
 #endif /* CONFIG_IEEE80211BE */
 
 	sta->wpa_sm = NULL;
+}
+
+
+static void ap_sta_handle_disassociate(struct hostapd_data *hapd,
+				       struct sta_info *sta, u16 reason)
+{
+	wpa_printf(MSG_DEBUG, "%s: disassociate STA " MACSTR,
+		   hapd->conf->iface, MAC2STR(sta->addr));
+
+	if (hapd->iface->current_mode &&
+	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
+		/* Skip deauthentication in DMG/IEEE 802.11ad */
+		sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC |
+				WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_REMOVE;
+	} else {
+		sta->flags &= ~(WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
+		sta->timeout_next = STA_DEAUTH;
+	}
+
+	ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DISASSOC);
 
 	sta->disassoc_reason = reason;
 	sta->flags |= WLAN_STA_PENDING_DISASSOC_CB;
@@ -905,8 +919,8 @@
 }
 
 
-void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
-			   u16 reason)
+static void ap_sta_handle_deauthenticate(struct hostapd_data *hapd,
+					 struct sta_info *sta, u16 reason)
 {
 	if (hapd->iface->current_mode &&
 	    hapd->iface->current_mode->mode == HOSTAPD_MODE_IEEE80211AD) {
@@ -918,21 +932,11 @@
 
 	wpa_printf(MSG_DEBUG, "%s: deauthenticate STA " MACSTR,
 		   hapd->conf->iface, MAC2STR(sta->addr));
-	sta->last_seq_ctrl = WLAN_INVALID_MGMT_SEQ;
+
 	sta->flags &= ~(WLAN_STA_AUTH | WLAN_STA_ASSOC | WLAN_STA_ASSOC_REQ_OK);
-	ap_sta_set_authorized(hapd, sta, 0);
-	hostapd_set_sta_flags(hapd, sta);
+
 	sta->timeout_next = STA_REMOVE;
-	wpa_printf(MSG_DEBUG, "%s: reschedule ap_handle_timer timeout "
-		   "for " MACSTR " (%d seconds - "
-		   "AP_MAX_INACTIVITY_AFTER_DEAUTH)",
-		   __func__, MAC2STR(sta->addr),
-		   AP_MAX_INACTIVITY_AFTER_DEAUTH);
-	eloop_cancel_timeout(ap_handle_timer, hapd, sta);
-	eloop_register_timeout(AP_MAX_INACTIVITY_AFTER_DEAUTH, 0,
-			       ap_handle_timer, hapd, sta);
-	accounting_sta_stop(hapd, sta);
-	ieee802_1x_free_station(hapd, sta);
+	ap_sta_disconnect_common(hapd, sta, AP_MAX_INACTIVITY_AFTER_DEAUTH);
 
 	sta->deauth_reason = reason;
 	sta->flags |= WLAN_STA_PENDING_DEAUTH_CB;
@@ -943,6 +947,105 @@
 }
 
 
+static bool ap_sta_ml_disconnect(struct hostapd_data *hapd,
+				 struct sta_info *sta, u16 reason,
+				 bool disassoc)
+{
+#ifdef CONFIG_IEEE80211BE
+	struct hostapd_data *assoc_hapd, *tmp_hapd;
+	struct sta_info *assoc_sta;
+	unsigned int i, link_id;
+	struct hapd_interfaces *interfaces;
+
+	if (!hostapd_is_mld_ap(hapd))
+		return false;
+
+	/*
+	 * Get the station on which the association was performed, as it holds
+	 * the information about all the other links.
+	 */
+	assoc_sta = hostapd_ml_get_assoc_sta(hapd, sta, &assoc_hapd);
+	if (!assoc_sta)
+		return false;
+	interfaces = assoc_hapd->iface->interfaces;
+
+	for (link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
+		for (i = 0; i < interfaces->count; i++) {
+			struct sta_info *tmp_sta;
+
+			if (!assoc_sta->mld_info.links[link_id].valid)
+				continue;
+
+			tmp_hapd = interfaces->iface[i]->bss[0];
+
+			if (!tmp_hapd->conf->mld_ap ||
+			    assoc_hapd->conf->mld_id != tmp_hapd->conf->mld_id)
+				continue;
+
+			for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+			     tmp_sta = tmp_sta->next) {
+				/*
+				 * Handle the station on which the association
+				 * was done only after all other link station
+				 * are removed. Since there is a only a single
+				 * station per hapd with the same association
+				 * link simply break;
+				 */
+				if (tmp_sta == assoc_sta)
+					break;
+
+				if (tmp_sta->mld_assoc_link_id !=
+				    assoc_sta->mld_assoc_link_id ||
+				    tmp_sta->aid != assoc_sta->aid)
+					continue;
+
+				if (disassoc)
+					ap_sta_handle_disassociate(tmp_hapd,
+								   tmp_sta,
+								   reason);
+				else
+					ap_sta_handle_deauthenticate(tmp_hapd,
+								     tmp_sta,
+								     reason);
+
+				break;
+			}
+		}
+	}
+
+	/* Disconnect the station on which the association was performed. */
+	if (disassoc)
+		ap_sta_handle_disassociate(assoc_hapd, assoc_sta, reason);
+	else
+		ap_sta_handle_deauthenticate(assoc_hapd, assoc_sta, reason);
+
+	return true;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+
+void ap_sta_disassociate(struct hostapd_data *hapd, struct sta_info *sta,
+			 u16 reason)
+{
+	if (ap_sta_ml_disconnect(hapd, sta, reason, true))
+		return;
+
+	ap_sta_handle_disassociate(hapd, sta, reason);
+}
+
+
+void ap_sta_deauthenticate(struct hostapd_data *hapd, struct sta_info *sta,
+			   u16 reason)
+{
+	if (ap_sta_ml_disconnect(hapd, sta, reason, false))
+		return;
+
+	ap_sta_handle_deauthenticate(hapd, sta, reason);
+}
+
+
 #ifdef CONFIG_WPS
 int ap_sta_wps_cancel(struct hostapd_data *hapd,
 		      struct sta_info *sta, void *ctx)
@@ -1292,25 +1395,17 @@
 }
 
 
-void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
-			   int authorized)
+bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta,
+				int authorized)
 {
-	const u8 *dev_addr = NULL;
-	char buf[100];
-#ifdef CONFIG_P2P
-	u8 addr[ETH_ALEN];
-	u8 ip_addr_buf[4];
-#endif /* CONFIG_P2P */
-	u8 *ip_ptr = NULL;
-
 	if (!!authorized == !!(sta->flags & WLAN_STA_AUTHORIZED))
-		return;
+		return false;
 
 	if (authorized) {
 		int mld_assoc_link_id = -1;
 
 #ifdef CONFIG_IEEE80211BE
-		if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+		if (ap_sta_is_mld(hapd, sta)) {
 			if (sta->mld_assoc_link_id == hapd->mld_link_id)
 				mld_assoc_link_id = sta->mld_assoc_link_id;
 			else
@@ -1325,6 +1420,21 @@
 		sta->flags &= ~WLAN_STA_AUTHORIZED;
 	}
 
+	return true;
+}
+
+
+void ap_sta_set_authorized_event(struct hostapd_data *hapd,
+				 struct sta_info *sta, int authorized)
+{
+	const u8 *dev_addr = NULL;
+	char buf[100];
+#ifdef CONFIG_P2P
+	u8 addr[ETH_ALEN];
+	u8 ip_addr_buf[4];
+#endif /* CONFIG_P2P */
+	u8 *ip_ptr = NULL;
+
 #ifdef CONFIG_P2P
 	if (hapd->p2p_group == NULL) {
 		if (sta->p2p_ie != NULL &&
@@ -1413,6 +1523,15 @@
 }
 
 
+void ap_sta_set_authorized(struct hostapd_data *hapd, struct sta_info *sta,
+			   int authorized)
+{
+	if (!ap_sta_set_authorized_flag(hapd, sta, authorized))
+		return;
+	ap_sta_set_authorized_event(hapd, sta, authorized);
+}
+
+
 void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *addr, u16 reason)
 {
@@ -1604,6 +1723,34 @@
 }
 
 
+#ifdef CONFIG_IEEE80211BE
+static void ap_sta_remove_link_sta(struct hostapd_data *hapd,
+				   struct sta_info *sta)
+{
+	struct hostapd_data *tmp_hapd;
+	unsigned int i, j;
+
+	for_each_mld_link(tmp_hapd, i, j, hapd->iface->interfaces,
+			  hapd->conf->mld_id) {
+		struct sta_info *tmp_sta;
+
+		if (hapd == tmp_hapd)
+			continue;
+
+		for (tmp_sta = tmp_hapd->sta_list; tmp_sta;
+		     tmp_sta = tmp_sta->next) {
+			if (tmp_sta == sta ||
+			    !ether_addr_equal(tmp_sta->addr, sta->addr))
+				continue;
+
+			ap_free_sta(tmp_hapd, tmp_sta);
+			break;
+		}
+	}
+}
+#endif /* CONFIG_IEEE80211BE */
+
+
 int ap_sta_re_add(struct hostapd_data *hapd, struct sta_info *sta)
 {
 	const u8 *mld_link_addr = NULL;
@@ -1618,11 +1765,17 @@
 	 */
 
 #ifdef CONFIG_IEEE80211BE
-	if (hapd->conf->mld_ap && sta->mld_info.mld_sta) {
+	if (ap_sta_is_mld(hapd, sta)) {
 		u8 mld_link_id = hapd->mld_link_id;
 
 		mld_link_sta = sta->mld_assoc_link_id != mld_link_id;
 		mld_link_addr = sta->mld_info.links[mld_link_id].peer_addr;
+
+		/*
+		 * In case the AP is affiliated with an AP MLD, we need to
+		 * remove the station from all relevant links/APs.
+		 */
+		ap_sta_remove_link_sta(hapd, sta);
 	}
 #endif /* CONFIG_IEEE80211BE */
 
@@ -1646,3 +1799,19 @@
 	sta->added_unassoc = 1;
 	return 0;
 }
+
+
+#ifdef CONFIG_IEEE80211BE
+void ap_sta_free_sta_profile(struct mld_info *info)
+{
+	int i;
+
+	if (!info)
+		return;
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		os_free(info->links[i].resp_sta_profile);
+		info->links[i].resp_sta_profile = NULL;
+	}
+}
+#endif /* CONFIG_IEEE80211BE */
diff --git a/src/ap/sta_info.h b/src/ap/sta_info.h
index e2b9dde..b136ff7 100644
--- a/src/ap/sta_info.h
+++ b/src/ap/sta_info.h
@@ -17,6 +17,7 @@
 #include "common/sae.h"
 #include "crypto/sha384.h"
 #include "pasn/pasn_common.h"
+#include "hostapd.h"
 
 /* STA flags */
 #define WLAN_STA_AUTH BIT(0)
@@ -81,18 +82,18 @@
 	} common_info;
 
 	struct mld_link_info {
-		u8 valid;
+		u8 valid:1;
+		u8 nstr_bitmap_len:2;
 		u8 local_addr[ETH_ALEN];
 		u8 peer_addr[ETH_ALEN];
 
-		size_t nstr_bitmap_len;
 		u8 nstr_bitmap[2];
 
 		u16 capability;
 
 		u16 status;
-		size_t resp_sta_profile_len;
-		u8 resp_sta_profile[EHT_ML_MAX_STA_PROF_LEN];
+		u16 resp_sta_profile_len;
+		u8 *resp_sta_profile;
 
 		const u8 *rsne, *rsnxe;
 	} links[MAX_NUM_MLD_LINKS];
@@ -393,6 +394,10 @@
 void ap_sta_disconnect(struct hostapd_data *hapd, struct sta_info *sta,
 		       const u8 *addr, u16 reason);
 
+bool ap_sta_set_authorized_flag(struct hostapd_data *hapd, struct sta_info *sta,
+				int authorized);
+void ap_sta_set_authorized_event(struct hostapd_data *hapd,
+				 struct sta_info *sta, int authorized);
 void ap_sta_set_authorized(struct hostapd_data *hapd,
 			   struct sta_info *sta, int authorized);
 static inline int ap_sta_is_authorized(struct sta_info *sta)
@@ -415,4 +420,24 @@
 
 void ap_free_sta_pasn(struct hostapd_data *hapd, struct sta_info *sta);
 
+static inline bool ap_sta_is_mld(struct hostapd_data *hapd,
+				 struct sta_info *sta)
+{
+#ifdef CONFIG_IEEE80211BE
+	return hapd->conf->mld_ap && sta && sta->mld_info.mld_sta;
+#else /* CONFIG_IEEE80211BE */
+	return false;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+static inline void ap_sta_set_mld(struct sta_info *sta, bool mld)
+{
+#ifdef CONFIG_IEEE80211BE
+	if (sta)
+		sta->mld_info.mld_sta = mld;
+#endif /* CONFIG_IEEE80211BE */
+}
+
+void ap_sta_free_sta_profile(struct mld_info *info);
+
 #endif /* STA_INFO_H */
diff --git a/src/ap/wmm.c b/src/ap/wmm.c
index 9ebb01e..dad768e 100644
--- a/src/ap/wmm.c
+++ b/src/ap/wmm.c
@@ -20,13 +20,6 @@
 #include "ap_drv_ops.h"
 #include "wmm.h"
 
-#ifndef MIN
-#define MIN(a, b) (((a) < (b)) ? (a) : (b))
-#endif
-#ifndef MAX
-#define MAX(a, b) (((a) > (b)) ? (a) : (b))
-#endif
-
 
 static inline u8 wmm_aci_aifsn(int aifsn, int acm, int aci)
 {
diff --git a/src/ap/wnm_ap.c b/src/ap/wnm_ap.c
index 153ee40..b77e21b 100644
--- a/src/ap/wnm_ap.c
+++ b/src/ap/wnm_ap.c
@@ -44,6 +44,20 @@
 }
 
 
+static const u8 * wnm_ap_get_own_addr(struct hostapd_data *hapd,
+				      struct sta_info *sta)
+{
+	const u8 *own_addr = hapd->own_addr;
+
+#ifdef CONFIG_IEEE80211BE
+	if (hapd->conf->mld_ap && (!sta || ap_sta_is_mld(hapd, sta)))
+		own_addr = hapd->mld_addr;
+#endif /* CONFIG_IEEE80211BE */
+
+	return own_addr;
+}
+
+
 /* MLME-SLEEPMODE.response */
 static int ieee802_11_send_wnmsleep_resp(struct hostapd_data *hapd,
 					 const u8 *addr, u8 dialog_token,
@@ -63,6 +77,7 @@
 	struct sta_info *sta;
 	enum wnm_oper tfs_oper = action_type == WNM_SLEEP_MODE_ENTER ?
 		WNM_SLEEP_TFS_RESP_IE_ADD : WNM_SLEEP_TFS_RESP_IE_NONE;
+	const u8 *own_addr;
 
 	sta = ap_get_sta(hapd, addr);
 	if (sta == NULL) {
@@ -143,9 +158,12 @@
 		res = -1;
 		goto fail;
 	}
+
+	own_addr = wnm_ap_get_own_addr(hapd, sta);
+
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
@@ -366,6 +384,8 @@
 						  u8 dialog_token)
 {
 	struct ieee80211_mgmt *mgmt;
+	const u8 *own_addr;
+	struct sta_info *sta;
 	size_t len;
 	u8 *pos;
 	int res;
@@ -373,9 +393,13 @@
 	mgmt = os_zalloc(sizeof(*mgmt));
 	if (mgmt == NULL)
 		return -1;
+
+	sta = ap_get_sta(hapd, addr);
+	own_addr = wnm_ap_get_own_addr(hapd, sta);
+
 	os_memcpy(mgmt->da, addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
@@ -821,14 +845,15 @@
 {
 	u8 buf[1000], *pos;
 	struct ieee80211_mgmt *mgmt;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	os_memset(buf, 0, sizeof(buf));
 	mgmt = (struct ieee80211_mgmt *) buf;
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
@@ -887,14 +912,15 @@
 	u8 buf[1000], *pos;
 	struct ieee80211_mgmt *mgmt;
 	size_t url_len;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	os_memset(buf, 0, sizeof(buf));
 	mgmt = (struct ieee80211_mgmt *) buf;
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = 1;
@@ -939,6 +965,7 @@
 	u8 *buf, *pos;
 	struct ieee80211_mgmt *mgmt;
 	size_t url_len;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	wpa_printf(MSG_DEBUG, "WNM: Send BSS Transition Management Request to "
 		   MACSTR
@@ -952,8 +979,8 @@
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.bss_tm_req.action = WNM_BSS_TRANS_MGMT_REQ;
 	mgmt->u.action.u.bss_tm_req.dialog_token = dialog_token;
@@ -1002,6 +1029,26 @@
 	os_free(buf);
 
 	if (disassoc_timer) {
+#ifdef CONFIG_IEEE80211BE
+		if (ap_sta_is_mld(hapd, sta)) {
+			int i;
+			unsigned int links = 0;
+
+			for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+				if (sta->mld_info.links[i].valid)
+					links++;
+			}
+
+			if (links > 1) {
+				wpa_printf(MSG_DEBUG,
+					   "WNM: Only terminating one link - other links remains associated for "
+					   MACSTR,
+					   MAC2STR(sta->mld_info.common_info.mld_addr));
+				return 0;
+			}
+		}
+#endif /* CONFIG_IEEE80211BE */
+
 		/* send disassociation frame after time-out */
 		set_disassoc_timer(hapd, sta, disassoc_timer);
 	}
@@ -1016,6 +1063,7 @@
 	u8 buf[100], *pos;
 	struct ieee80211_mgmt *mgmt;
 	u8 dialog_token = 1;
+	const u8 *own_addr = wnm_ap_get_own_addr(hapd, sta);
 
 	if (auto_report > 3 || timeout > 63)
 		return -1;
@@ -1024,8 +1072,8 @@
 	mgmt->frame_control = IEEE80211_FC(WLAN_FC_TYPE_MGMT,
 					   WLAN_FC_STYPE_ACTION);
 	os_memcpy(mgmt->da, sta->addr, ETH_ALEN);
-	os_memcpy(mgmt->sa, hapd->own_addr, ETH_ALEN);
-	os_memcpy(mgmt->bssid, hapd->own_addr, ETH_ALEN);
+	os_memcpy(mgmt->sa, own_addr, ETH_ALEN);
+	os_memcpy(mgmt->bssid, own_addr, ETH_ALEN);
 	mgmt->u.action.category = WLAN_ACTION_WNM;
 	mgmt->u.action.u.coloc_intf_req.action =
 		WNM_COLLOCATED_INTERFERENCE_REQ;
diff --git a/src/ap/wpa_auth.c b/src/ap/wpa_auth.c
index a662201..3002d91 100644
--- a/src/ap/wpa_auth.c
+++ b/src/ap/wpa_auth.c
@@ -194,6 +194,9 @@
 
 	if (!wpa_auth->cb->get_seqnum)
 		return -1;
+#ifdef CONFIG_TESTING_OPTIONS
+	os_memset(seq, 0, WPA_KEY_RSC_LEN);
+#endif /* CONFIG_TESTING_OPTIONS */
 	res = wpa_auth->cb->get_seqnum(wpa_auth->cb_ctx, addr, idx, seq);
 #ifdef CONFIG_TESTING_OPTIONS
 	if (!addr && idx < 4 && wpa_auth->conf.gtk_rsc_override_set) {
@@ -599,6 +602,15 @@
 	}
 #endif /* CONFIG_P2P */
 
+	if (conf->tx_bss_auth && conf->beacon_prot) {
+		conf->tx_bss_auth->non_tx_beacon_prot = true;
+		if (!conf->tx_bss_auth->conf.beacon_prot)
+			conf->tx_bss_auth->conf.beacon_prot = true;
+		if (!conf->tx_bss_auth->conf.group_mgmt_cipher)
+			conf->tx_bss_auth->conf.group_mgmt_cipher =
+				conf->group_mgmt_cipher;
+	}
+
 	return wpa_auth;
 }
 
@@ -618,6 +630,17 @@
 }
 
 
+static void wpa_auth_free_conf(struct wpa_auth_config *conf)
+{
+#ifdef CONFIG_TESTING_OPTIONS
+	wpabuf_free(conf->eapol_m1_elements);
+	conf->eapol_m1_elements = NULL;
+	wpabuf_free(conf->eapol_m3_elements);
+	conf->eapol_m3_elements = NULL;
+#endif /* CONFIG_TESTING_OPTIONS */
+}
+
+
 /**
  * wpa_deinit - Deinitialize WPA authenticator
  * @wpa_auth: Pointer to WPA authenticator data from wpa_init()
@@ -651,6 +674,7 @@
 		bin_clear_free(prev, sizeof(*prev));
 	}
 
+	wpa_auth_free_conf(&wpa_auth->conf);
 	os_free(wpa_auth);
 }
 
@@ -668,6 +692,7 @@
 	if (!wpa_auth)
 		return 0;
 
+	wpa_auth_free_conf(&wpa_auth->conf);
 	os_memcpy(&wpa_auth->conf, conf, sizeof(*conf));
 	if (wpa_auth_gen_wpa_ie(wpa_auth)) {
 		wpa_printf(MSG_ERROR, "Could not generate WPA IE.");
@@ -900,19 +925,70 @@
 			       struct wpa_state_machine *sm,
 			       struct wpa_eapol_ie_parse *kde)
 {
-	struct wpa_ie_data ie;
+	struct wpa_ie_data ie, assoc_ie;
 	struct rsn_mdie *mdie;
+	unsigned int i, j;
+	bool found = false;
+
+	/* Verify that PMKR1Name from EAPOL-Key message 2/4 matches the value
+	 * we derived. */
 
 	if (wpa_parse_wpa_ie_rsn(kde->rsn_ie, kde->rsn_ie_len, &ie) < 0 ||
-	    ie.num_pmkid != 1 || !ie.pmkid) {
+	    ie.num_pmkid < 1 || !ie.pmkid) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: No PMKR1Name in FT 4-way handshake message 2/4");
 		return -1;
 	}
 
-	os_memcpy(sm->sup_pmk_r1_name, ie.pmkid, PMKID_LEN);
-	wpa_hexdump(MSG_DEBUG, "FT: PMKR1Name from Supplicant",
-		    sm->sup_pmk_r1_name, PMKID_LEN);
+	if (wpa_parse_wpa_ie_rsn(sm->wpa_ie, sm->wpa_ie_len, &assoc_ie) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "FT: Could not parse (Re)Association Request frame RSNE");
+		os_memset(&assoc_ie, 0, sizeof(assoc_ie));
+		/* Continue to allow PMKR1Name matching to be done to cover the
+		 * case where it is the only listed PMKID. */
+	}
+
+	for (i = 0; i < ie.num_pmkid; i++) {
+		const u8 *pmkid = ie.pmkid + i * PMKID_LEN;
+
+		if (os_memcmp_const(pmkid, sm->pmk_r1_name,
+				    WPA_PMK_NAME_LEN) == 0) {
+			wpa_printf(MSG_DEBUG,
+				   "FT: RSNE[PMKID[%u]] from supplicant matches PMKR1Name",
+				   i);
+			found = true;
+		} else {
+			for (j = 0; j < assoc_ie.num_pmkid; j++) {
+				if (os_memcmp(pmkid,
+					      assoc_ie.pmkid + j * PMKID_LEN,
+					      PMKID_LEN) == 0)
+					break;
+			}
+
+			if (j == assoc_ie.num_pmkid) {
+				wpa_printf(MSG_DEBUG,
+					   "FT: RSNE[PMKID[%u]] from supplicant is neither PMKR1Name nor included in AssocReq",
+					   i);
+				found = false;
+				break;
+			}
+			wpa_printf(MSG_DEBUG,
+				   "FT: RSNE[PMKID[%u]] from supplicant is not PMKR1Name, but matches a PMKID in AssocReq",
+				   i);
+		}
+	}
+
+	if (!found) {
+		wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_DEBUG,
+				"PMKR1Name mismatch in FT 4-way handshake");
+		wpa_hexdump(MSG_DEBUG,
+			    "FT: PMKIDs/PMKR1Name from Supplicant",
+			    ie.pmkid, ie.num_pmkid * PMKID_LEN);
+		wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
+			    sm->pmk_r1_name, WPA_PMK_NAME_LEN);
+		return -1;
+	}
 
 	if (!kde->mdie || !kde->ftie) {
 		wpa_printf(MSG_DEBUG,
@@ -1076,28 +1152,166 @@
 }
 
 
+enum eapol_key_msg { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST };
+
+static bool wpa_auth_valid_key_desc_ver(struct wpa_authenticator *wpa_auth,
+					struct wpa_state_machine *sm, u16 ver)
+{
+	if (ver > WPA_KEY_INFO_TYPE_AES_128_CMAC) {
+		wpa_printf(MSG_INFO, "RSN: " MACSTR
+			   " used undefined Key Descriptor Version %d",
+			   MAC2STR(wpa_auth_get_spa(sm)), ver);
+		return false;
+	}
+
+	if (!wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    wpa_use_cmac(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"advertised support for AES-128-CMAC, but did not use it");
+		return false;
+	}
+
+	if (sm->pairwise != WPA_CIPHER_TKIP &&
+	    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    !wpa_use_cmac(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"did not use HMAC-SHA1-AES with CCMP/GCMP");
+		return false;
+	}
+
+	if (wpa_use_akm_defined(sm->wpa_key_mgmt) &&
+	    ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases");
+		return false;
+	}
+
+	return true;
+}
+
+
+static bool wpa_auth_valid_request_counter(struct wpa_authenticator *wpa_auth,
+					   struct wpa_state_machine *sm,
+					   const u8 *replay_counter)
+{
+
+	if (sm->req_replay_counter_used &&
+	    os_memcmp(replay_counter, sm->req_replay_counter,
+		      WPA_REPLAY_COUNTER_LEN) <= 0) {
+		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+				LOGGER_WARNING,
+				"received EAPOL-Key request with replayed counter");
+		return false;
+	}
+
+	return true;
+}
+
+
+static bool wpa_auth_valid_counter(struct wpa_authenticator *wpa_auth,
+				   struct wpa_state_machine *sm,
+				   const struct wpa_eapol_key *key,
+				   enum eapol_key_msg msg,
+				   const char *msgtxt)
+{
+	int i;
+
+	if (msg == REQUEST)
+		return wpa_auth_valid_request_counter(wpa_auth, sm,
+						      key->replay_counter);
+
+	if (wpa_replay_counter_valid(sm->key_replay, key->replay_counter))
+		return true;
+
+	if (msg == PAIRWISE_2 &&
+	    wpa_replay_counter_valid(sm->prev_key_replay,
+				     key->replay_counter) &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+	    os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0) {
+		/*
+		 * Some supplicant implementations (e.g., Windows XP
+		 * WZC) update SNonce for each EAPOL-Key 2/4. This
+		 * breaks the workaround on accepting any of the
+		 * pending requests, so allow the SNonce to be updated
+		 * even if we have already sent out EAPOL-Key 3/4.
+		 */
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "Process SNonce update from STA based on retransmitted EAPOL-Key 1/4");
+		sm->update_snonce = 1;
+		os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN);
+		sm->alt_snonce_valid = true;
+		os_memcpy(sm->alt_replay_counter,
+			  sm->key_replay[0].counter,
+			  WPA_REPLAY_COUNTER_LEN);
+		return true;
+	}
+
+	if (msg == PAIRWISE_4 && sm->alt_snonce_valid &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
+	    os_memcmp(key->replay_counter, sm->alt_replay_counter,
+		      WPA_REPLAY_COUNTER_LEN) == 0) {
+		/*
+		 * Supplicant may still be using the old SNonce since
+		 * there was two EAPOL-Key 2/4 messages and they had
+		 * different SNonce values.
+		 */
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4");
+		return true;
+	}
+
+	if (msg == PAIRWISE_2 &&
+	    wpa_replay_counter_valid(sm->prev_key_replay,
+				     key->replay_counter) &&
+	    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "ignore retransmitted EAPOL-Key %s - SNonce did not change",
+				 msgtxt);
+	} else {
+		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+				 LOGGER_DEBUG,
+				 "received EAPOL-Key %s with unexpected replay counter",
+				 msgtxt);
+	}
+	for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
+		if (!sm->key_replay[i].valid)
+			break;
+		wpa_hexdump(MSG_DEBUG, "pending replay counter",
+			    sm->key_replay[i].counter,
+			    WPA_REPLAY_COUNTER_LEN);
+	}
+	wpa_hexdump(MSG_DEBUG, "received replay counter",
+		    key->replay_counter, WPA_REPLAY_COUNTER_LEN);
+	return false;
+}
+
+
 void wpa_receive(struct wpa_authenticator *wpa_auth,
 		 struct wpa_state_machine *sm,
 		 u8 *data, size_t data_len)
 {
 	struct ieee802_1x_hdr *hdr;
 	struct wpa_eapol_key *key;
-	u16 key_info, key_data_length;
-	enum { PAIRWISE_2, PAIRWISE_4, GROUP_2, REQUEST } msg;
-	char *msgtxt;
-	struct wpa_eapol_ie_parse kde;
+	u16 key_info, ver, key_data_length;
+	enum eapol_key_msg msg;
+	const char *msgtxt;
 	const u8 *key_data;
 	size_t keyhdrlen, mic_len;
 	u8 *mic;
-	bool is_mld = false;
+	u8 *key_data_buf = NULL;
+	size_t key_data_buf_len = 0;
 
 	if (!wpa_auth || !wpa_auth->conf.wpa || !sm)
 		return;
 
-#ifdef CONFIG_IEEE80211BE
-	is_mld = sm->mld_assoc_link_id >= 0;
-#endif /* CONFIG_IEEE80211BE */
-
 	wpa_hexdump(MSG_MSGDUMP, "WPA: RX EAPOL data", data, data_len);
 
 	mic_len = wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len);
@@ -1167,11 +1381,31 @@
 		return;
 	}
 
-	/* TODO: Make this more robust for distinguising EAPOL-Key msg 2/4 from
-	 * 4/4. Secure=1 is used in msg 2/4 when doing PTK rekeying, so the
-	 * MLD mechanism here does not work without the somewhat undesired check
-	 * on wpa_ptk_state.. Would likely need to decrypt Key Data first to be
-	 * able to know which message this is in MLO cases.. */
+	ver = key_info & WPA_KEY_INFO_TYPE_MASK;
+	if (!wpa_auth_valid_key_desc_ver(wpa_auth, sm, ver))
+		goto out;
+	if (mic_len > 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
+	    sm->PTK_valid &&
+	    (ver == WPA_KEY_INFO_TYPE_HMAC_SHA1_AES ||
+	     ver == WPA_KEY_INFO_TYPE_AES_128_CMAC ||
+	     wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) &&
+	    key_data_length >= 8 && key_data_length % 8 == 0) {
+		key_data_length -= 8; /* AES-WRAP adds 8 bytes */
+		key_data_buf = os_malloc(key_data_length);
+		if (!key_data_buf)
+			goto out;
+		key_data_buf_len = key_data_length;
+		if (aes_unwrap(sm->PTK.kek, sm->PTK.kek_len,
+			       key_data_length / 8, key_data, key_data_buf)) {
+			wpa_printf(MSG_INFO,
+				   "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data");
+			goto out;
+		}
+		key_data = key_data_buf;
+		wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data",
+				key_data, key_data_length);
+	}
+
 	if (key_info & WPA_KEY_INFO_REQUEST) {
 		msg = REQUEST;
 		msgtxt = "Request";
@@ -1179,10 +1413,13 @@
 		msg = GROUP_2;
 		msgtxt = "2/2 Group";
 	} else if (key_data_length == 0 ||
+		   (sm->wpa == WPA_VERSION_WPA2 &&
+		    (!(key_info & WPA_KEY_INFO_ENCR_KEY_DATA) ||
+		     key_data_buf) &&
+		    (key_info & WPA_KEY_INFO_SECURE) &&
+		    !get_ie(key_data, key_data_length, WLAN_EID_RSN)) ||
 		   (mic_len == 0 && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA) &&
-		    key_data_length == AES_BLOCK_SIZE) ||
-		   (is_mld && (key_info & WPA_KEY_INFO_SECURE) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING)) {
+		    key_data_length == AES_BLOCK_SIZE)) {
 		msg = PAIRWISE_4;
 		msgtxt = "4/4 Pairwise";
 	} else {
@@ -1190,127 +1427,15 @@
 		msgtxt = "2/4 Pairwise";
 	}
 
-	if (msg == REQUEST || msg == PAIRWISE_2 || msg == PAIRWISE_4 ||
-	    msg == GROUP_2) {
-		u16 ver = key_info & WPA_KEY_INFO_TYPE_MASK;
-		if (sm->pairwise == WPA_CIPHER_CCMP ||
-		    sm->pairwise == WPA_CIPHER_GCMP) {
-			if (wpa_use_cmac(sm->wpa_key_mgmt) &&
-			    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-			    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC) {
-				wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-						LOGGER_WARNING,
-						"advertised support for AES-128-CMAC, but did not use it");
-				return;
-			}
+	if (!wpa_auth_valid_counter(wpa_auth, sm, key, msg, msgtxt))
+		goto out;
 
-			if (!wpa_use_cmac(sm->wpa_key_mgmt) &&
-			    !wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-			    ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES) {
-				wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-						LOGGER_WARNING,
-						"did not use HMAC-SHA1-AES with CCMP/GCMP");
-				return;
-			}
-		}
-
-		if (wpa_use_akm_defined(sm->wpa_key_mgmt) &&
-		    ver != WPA_KEY_INFO_TYPE_AKM_DEFINED) {
-			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_WARNING,
-					"did not use EAPOL-Key descriptor version 0 as required for AKM-defined cases");
-			return;
-		}
-	}
-
-	if (key_info & WPA_KEY_INFO_REQUEST) {
-		if (sm->req_replay_counter_used &&
-		    os_memcmp(key->replay_counter, sm->req_replay_counter,
-			      WPA_REPLAY_COUNTER_LEN) <= 0) {
-			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_WARNING,
-					"received EAPOL-Key request with replayed counter");
-			return;
-		}
-	}
-
-	if (!(key_info & WPA_KEY_INFO_REQUEST) &&
-	    !wpa_replay_counter_valid(sm->key_replay, key->replay_counter)) {
-		int i;
-
-		if (msg == PAIRWISE_2 &&
-		    wpa_replay_counter_valid(sm->prev_key_replay,
-					     key->replay_counter) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
-		    os_memcmp(sm->SNonce, key->key_nonce, WPA_NONCE_LEN) != 0)
-		{
-			/*
-			 * Some supplicant implementations (e.g., Windows XP
-			 * WZC) update SNonce for each EAPOL-Key 2/4. This
-			 * breaks the workaround on accepting any of the
-			 * pending requests, so allow the SNonce to be updated
-			 * even if we have already sent out EAPOL-Key 3/4.
-			 */
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "Process SNonce update from STA based on retransmitted EAPOL-Key 1/4");
-			sm->update_snonce = 1;
-			os_memcpy(sm->alt_SNonce, sm->SNonce, WPA_NONCE_LEN);
-			sm->alt_snonce_valid = true;
-			os_memcpy(sm->alt_replay_counter,
-				  sm->key_replay[0].counter,
-				  WPA_REPLAY_COUNTER_LEN);
-			goto continue_processing;
-		}
-
-		if (msg == PAIRWISE_4 && sm->alt_snonce_valid &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING &&
-		    os_memcmp(key->replay_counter, sm->alt_replay_counter,
-			      WPA_REPLAY_COUNTER_LEN) == 0) {
-			/*
-			 * Supplicant may still be using the old SNonce since
-			 * there was two EAPOL-Key 2/4 messages and they had
-			 * different SNonce values.
-			 */
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "Try to process received EAPOL-Key 4/4 based on old Replay Counter and SNonce from an earlier EAPOL-Key 1/4");
-			goto continue_processing;
-		}
-
-		if (msg == PAIRWISE_2 &&
-		    wpa_replay_counter_valid(sm->prev_key_replay,
-					     key->replay_counter) &&
-		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "ignore retransmitted EAPOL-Key %s - SNonce did not change",
-					 msgtxt);
-		} else {
-			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
-					 LOGGER_DEBUG,
-					 "received EAPOL-Key %s with unexpected replay counter",
-					 msgtxt);
-		}
-		for (i = 0; i < RSNA_MAX_EAPOL_RETRIES; i++) {
-			if (!sm->key_replay[i].valid)
-				break;
-			wpa_hexdump(MSG_DEBUG, "pending replay counter",
-				    sm->key_replay[i].counter,
-				    WPA_REPLAY_COUNTER_LEN);
-		}
-		wpa_hexdump(MSG_DEBUG, "received replay counter",
-			    key->replay_counter, WPA_REPLAY_COUNTER_LEN);
-		return;
-	}
-
-continue_processing:
 #ifdef CONFIG_FILS
 	if (sm->wpa == WPA_VERSION_WPA2 && mic_len == 0 &&
 	    !(key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
 		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
 				 "WPA: Encr Key Data bit not set even though AEAD cipher is supposed to be used - drop frame");
-		return;
+		goto out;
 	}
 #endif /* CONFIG_FILS */
 
@@ -1324,7 +1449,7 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 2/4 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_state);
-			return;
+			goto out;
 		}
 		random_add_randomness(key->key_nonce, WPA_NONCE_LEN);
 		if (sm->group->reject_4way_hs_for_entropy) {
@@ -1342,7 +1467,7 @@
 			random_mark_pool_ready();
 			wpa_sta_disconnect(wpa_auth, sm->addr,
 					   WLAN_REASON_PREV_AUTH_NOT_VALID);
-			return;
+			goto out;
 		}
 		break;
 	case PAIRWISE_4:
@@ -1352,7 +1477,7 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 4/4 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_state);
-			return;
+			goto out;
 		}
 		break;
 	case GROUP_2:
@@ -1362,10 +1487,20 @@
 					 LOGGER_INFO,
 					 "received EAPOL-Key msg 2/2 in invalid state (%d) - dropped",
 					 sm->wpa_ptk_group_state);
-			return;
+			goto out;
 		}
 		break;
 	case REQUEST:
+		if (sm->wpa_ptk_state == WPA_PTK_PTKSTART ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKCALCNEGOTIATING2 ||
+		    sm->wpa_ptk_state == WPA_PTK_PTKINITNEGOTIATING) {
+			wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm),
+					 LOGGER_INFO,
+					 "received EAPOL-Key Request in invalid state (%d) - dropped",
+					 sm->wpa_ptk_state);
+			goto out;
+		}
 		break;
 	}
 
@@ -1375,14 +1510,14 @@
 	if (key_info & WPA_KEY_INFO_ACK) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key Ack set");
-		return;
+		goto out;
 	}
 
 	if (!wpa_key_mgmt_fils(sm->wpa_key_mgmt) &&
 	    !(key_info & WPA_KEY_INFO_MIC)) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key MIC not set");
-		return;
+		goto out;
 	}
 
 #ifdef CONFIG_FILS
@@ -1390,7 +1525,7 @@
 	    (key_info & WPA_KEY_INFO_MIC)) {
 		wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				"received invalid EAPOL-Key: Key MIC set");
-		return;
+		goto out;
 	}
 #endif /* CONFIG_FILS */
 
@@ -1409,7 +1544,7 @@
 				   "TEST: Ignore Key MIC failure for fuzz testing");
 			goto continue_fuzz;
 #endif /* TEST_FUZZ */
-			return;
+			goto out;
 		}
 #ifdef CONFIG_FILS
 		if (!mic_len &&
@@ -1423,7 +1558,7 @@
 				   "TEST: Ignore Key MIC failure for fuzz testing");
 			goto continue_fuzz;
 #endif /* TEST_FUZZ */
-			return;
+			goto out;
 		}
 #endif /* CONFIG_FILS */
 #ifdef TEST_FUZZ
@@ -1435,6 +1570,12 @@
 	}
 
 	if (key_info & WPA_KEY_INFO_REQUEST) {
+		if (!(key_info & WPA_KEY_INFO_SECURE)) {
+			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
+					LOGGER_INFO,
+					"received EAPOL-Key request without Secure=1");
+			goto out;
+		}
 		if (sm->MICVerified) {
 			sm->req_replay_counter_used = 1;
 			os_memcpy(sm->req_replay_counter, key->replay_counter,
@@ -1443,28 +1584,19 @@
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
 					"received EAPOL-Key request with invalid MIC");
-			return;
+			goto out;
 		}
 
-		/*
-		 * TODO: should decrypt key data field if encryption was used;
-		 * even though MAC address KDE is not normally encrypted,
-		 * supplicant is allowed to encrypt it.
-		 */
 		if (key_info & WPA_KEY_INFO_ERROR) {
 			if (wpa_receive_error_report(
 				    wpa_auth, sm,
 				    !(key_info & WPA_KEY_INFO_KEY_TYPE)) > 0)
-				return; /* STA entry was removed */
+				goto out; /* STA entry was removed */
 		} else if (key_info & WPA_KEY_INFO_KEY_TYPE) {
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
 					"received EAPOL-Key Request for new 4-Way Handshake");
 			wpa_request_new_ptk(sm);
-		} else if (key_data_length > 0 &&
-			   wpa_parse_kde_ies(key_data, key_data_length,
-					     &kde) == 0 &&
-			   kde.mac_addr) {
 		} else {
 			wpa_auth_logger(wpa_auth, wpa_auth_get_spa(sm),
 					LOGGER_INFO,
@@ -1506,7 +1638,7 @@
 	os_free(sm->last_rx_eapol_key);
 	sm->last_rx_eapol_key = os_memdup(data, data_len);
 	if (!sm->last_rx_eapol_key)
-		return;
+		goto out;
 	sm->last_rx_eapol_key_len = data_len;
 
 	sm->rx_eapol_key_secure = !!(key_info & WPA_KEY_INFO_SECURE);
@@ -1515,6 +1647,9 @@
 	sm->EAPOLKeyRequest = !!(key_info & WPA_KEY_INFO_REQUEST);
 	os_memcpy(sm->SNonce, key->key_nonce, WPA_NONCE_LEN);
 	wpa_sm_step(sm);
+
+out:
+	bin_clear_free(key_data_buf, key_data_buf_len);
 }
 
 
@@ -2332,10 +2467,14 @@
 
 SM_STATE(WPA_PTK, PTKSTART)
 {
-	u8 buf[2 * (2 + RSN_SELECTOR_LEN) + PMKID_LEN + ETH_ALEN];
+	u8 *buf;
+	size_t buf_len = 2 + RSN_SELECTOR_LEN + PMKID_LEN;
 	u8 *pmkid = NULL;
 	size_t kde_len = 0;
 	u16 key_info;
+#ifdef CONFIG_TESTING_OPTIONS
+	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+#endif /* CONFIG_TESTING_OPTIONS */
 
 	SM_ENTRY_MA(WPA_PTK, PTKSTART, wpa_ptk);
 	sm->PTKRequest = false;
@@ -2350,6 +2489,19 @@
 		return;
 	}
 
+#ifdef CONFIG_IEEE80211BE
+	if (sm->mld_assoc_link_id >= 0)
+		buf_len += 2 + RSN_SELECTOR_LEN + ETH_ALEN;
+#endif /* CONFIG_IEEE80211BE */
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m1_elements)
+		buf_len += wpabuf_len(conf->eapol_m1_elements);
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	buf = os_zalloc(buf_len);
+	if (!buf)
+		return;
+
 	wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm), LOGGER_DEBUG,
 			"sending 1/4 msg of 4-Way Handshake");
 	/*
@@ -2453,11 +2605,20 @@
 	}
 #endif /* CONFIG_IEEE80211BE */
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m1_elements) {
+		os_memcpy(buf + kde_len, wpabuf_head(conf->eapol_m1_elements),
+			  wpabuf_len(conf->eapol_m1_elements));
+		kde_len += wpabuf_len(conf->eapol_m1_elements);
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	key_info = WPA_KEY_INFO_ACK | WPA_KEY_INFO_KEY_TYPE;
 	if (sm->pairwise_set && sm->wpa != WPA_VERSION_WPA)
 		key_info |= WPA_KEY_INFO_SECURE;
 	wpa_send_eapol(sm->wpa_auth, sm, key_info, NULL,
 		       sm->ANonce, kde_len ? buf : NULL, kde_len, 0, 0);
+	os_free(buf);
 }
 
 
@@ -3178,7 +3339,7 @@
 
 	/* MLD MAC address must be the same */
 	if (!kde->mac_addr ||
-	    os_memcmp(kde->mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(kde->mac_addr, sm->peer_mld_addr)) {
 		wpa_printf(MSG_DEBUG, "RSN: MLD: Invalid MLD address");
 		return -1;
 	}
@@ -3205,8 +3366,8 @@
 			return -1;
 		}
 
-		if (os_memcmp(sm->mld_links[i].peer_addr, kde->mlo_link[i] + 1,
-			      ETH_ALEN) != 0) {
+		if (!ether_addr_equal(sm->mld_links[i].peer_addr,
+				      kde->mlo_link[i] + 1)) {
 			wpa_printf(MSG_DEBUG,
 				   "RSN: MLD: invalid MAC address=" MACSTR
 				   " expected " MACSTR " (link ID %u)",
@@ -3240,7 +3401,7 @@
 	size_t pmk_len;
 	int ft;
 	const u8 *eapol_key_ie, *key_data, *mic;
-	u16 key_data_length;
+	u16 key_info, ver, key_data_length;
 	size_t mic_len, eapol_key_ie_len;
 	struct ieee802_1x_hdr *hdr;
 	struct wpa_eapol_key *key;
@@ -3250,6 +3411,8 @@
 	u8 pmk_r0[PMK_LEN_MAX], pmk_r0_name[WPA_PMK_NAME_LEN];
 	u8 pmk_r1[PMK_LEN_MAX];
 	size_t key_len;
+	u8 *key_data_buf = NULL;
+	size_t key_data_buf_len = 0;
 
 	SM_ENTRY_MA(WPA_PTK, PTKCALCNEGOTIATING, wpa_ptk);
 	sm->EAPOLKeyReceived = false;
@@ -3357,12 +3520,46 @@
 	hdr = (struct ieee802_1x_hdr *) sm->last_rx_eapol_key;
 	key = (struct wpa_eapol_key *) (hdr + 1);
 	mic = (u8 *) (key + 1);
+	key_info = WPA_GET_BE16(key->key_info);
 	key_data = mic + mic_len + 2;
 	key_data_length = WPA_GET_BE16(mic + mic_len);
 	if (key_data_length > sm->last_rx_eapol_key_len - sizeof(*hdr) -
 	    sizeof(*key) - mic_len - 2)
 		goto out;
 
+	ver = key_info & WPA_KEY_INFO_TYPE_MASK;
+	if (mic_len && (key_info & WPA_KEY_INFO_ENCR_KEY_DATA)) {
+		if (ver != WPA_KEY_INFO_TYPE_HMAC_SHA1_AES &&
+		    ver != WPA_KEY_INFO_TYPE_AES_128_CMAC &&
+		    !wpa_use_aes_key_wrap(sm->wpa_key_mgmt)) {
+			wpa_printf(MSG_INFO,
+				   "Unsupported EAPOL-Key Key Data field encryption");
+			goto out;
+		}
+
+		if (key_data_length < 8 || key_data_length % 8) {
+			wpa_printf(MSG_INFO,
+				   "RSN: Unsupported AES-WRAP len %u",
+				   key_data_length);
+			goto out;
+		}
+		key_data_length -= 8; /* AES-WRAP adds 8 bytes */
+		key_data_buf = os_malloc(key_data_length);
+		if (!key_data_buf)
+			goto out;
+		key_data_buf_len = key_data_length;
+		if (aes_unwrap(PTK.kek, PTK.kek_len, key_data_length / 8,
+			       key_data, key_data_buf)) {
+			bin_clear_free(key_data_buf, key_data_buf_len);
+			wpa_printf(MSG_INFO,
+				   "RSN: AES unwrap failed - could not decrypt EAPOL-Key key data");
+			goto out;
+		}
+		key_data = key_data_buf;
+		wpa_hexdump_key(MSG_DEBUG, "RSN: Decrypted EAPOL-Key Key Data",
+				key_data, key_data_length);
+	}
+
 	if (wpa_parse_kde_ies(key_data, key_data_length, &kde) < 0) {
 		wpa_auth_vlogger(wpa_auth, wpa_auth_get_spa(sm), LOGGER_INFO,
 				 "received EAPOL-Key msg 2/4 with invalid Key Data contents");
@@ -3507,27 +3704,6 @@
 		return;
 	}
 
-#ifdef CONFIG_IEEE80211R_AP
-	if (sm->wpa == WPA_VERSION_WPA2 && wpa_key_mgmt_ft(sm->wpa_key_mgmt)) {
-		/*
-		 * Verify that PMKR1Name from EAPOL-Key message 2/4 matches
-		 * with the value we derived.
-		 */
-		if (os_memcmp_const(sm->sup_pmk_r1_name, sm->pmk_r1_name,
-				    WPA_PMK_NAME_LEN) != 0) {
-			wpa_auth_logger(sm->wpa_auth, wpa_auth_get_spa(sm),
-					LOGGER_DEBUG,
-					"PMKR1Name mismatch in FT 4-way handshake");
-			wpa_hexdump(MSG_DEBUG,
-				    "FT: PMKR1Name from Supplicant",
-				    sm->sup_pmk_r1_name, WPA_PMK_NAME_LEN);
-			wpa_hexdump(MSG_DEBUG, "FT: Derived PMKR1Name",
-				    sm->pmk_r1_name, WPA_PMK_NAME_LEN);
-			goto out;
-		}
-	}
-#endif /* CONFIG_IEEE80211R_AP */
-
 	if (vlan_id && wpa_key_mgmt_wpa_psk(sm->wpa_key_mgmt) &&
 	    wpa_auth_update_vlan(wpa_auth, sm->addr, vlan_id) < 0) {
 		wpa_sta_disconnect(wpa_auth, sm->addr,
@@ -3562,6 +3738,7 @@
 out:
 	forced_memzero(pmk_r0, sizeof(pmk_r0));
 	forced_memzero(pmk_r1, sizeof(pmk_r1));
+	bin_clear_free(key_data_buf, key_data_buf_len);
 }
 
 
@@ -3575,14 +3752,18 @@
 static int ieee80211w_kde_len(struct wpa_state_machine *sm)
 {
 	size_t len = 0;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
 
 	if (sm->mgmt_frame_prot) {
 		len += 2 + RSN_SELECTOR_LEN + WPA_IGTK_KDE_PREFIX_LEN;
-		len += wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+		len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	}
+
+	if (wpa_auth->conf.tx_bss_auth)
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
 	if (sm->mgmt_frame_prot && sm->wpa_auth->conf.beacon_prot) {
 		len += 2 + RSN_SELECTOR_LEN + WPA_BIGTK_KDE_PREFIX_LEN;
-		len += wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+		len += wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	}
 
 	return len;
@@ -3595,7 +3776,8 @@
 	struct wpa_bigtk_kde bigtk;
 	struct wpa_group *gsm = sm->group;
 	u8 rsc[WPA_KEY_RSC_LEN];
-	struct wpa_auth_config *conf = &sm->wpa_auth->conf;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_auth_config *conf = &wpa_auth->conf;
 	size_t len = wpa_cipher_key_len(conf->group_mgmt_cipher);
 
 	if (!sm->mgmt_frame_prot)
@@ -3627,7 +3809,14 @@
 			  NULL, 0);
 	forced_memzero(&igtk, sizeof(igtk));
 
-	if (!conf->beacon_prot)
+	if (wpa_auth->conf.tx_bss_auth) {
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
+		conf = &wpa_auth->conf;
+		len = wpa_cipher_key_len(conf->group_mgmt_cipher);
+		gsm = wpa_auth->group;
+	}
+
+	if (!sm->wpa_auth->conf.beacon_prot)
 		return pos;
 
 	bigtk.keyid[0] = gsm->GN_bigtk;
@@ -3785,6 +3974,11 @@
 	if (!beacon_prot)
 		return;
 
+	if (a->conf.tx_bss_auth) {
+		a = a->conf.tx_bss_auth;
+		gsm = a->group;
+	}
+
 	info->bigtkidx = gsm->GN_bigtk;
 	info->bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
 
@@ -3807,6 +4001,7 @@
 
 static size_t wpa_auth_ml_group_kdes_len(struct wpa_state_machine *sm)
 {
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
 	struct wpa_group *gsm = sm->group;
 	size_t gtk_len = gsm->GTK_len;
 	size_t igtk_len;
@@ -3825,10 +4020,15 @@
 		return kde_len;
 
 	/* MLO IGTK KDE for each link */
-	igtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	igtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 	kde_len += n_links * (2 + RSN_SELECTOR_LEN + 2 + 6 + 1 + igtk_len);
 
-	if (!sm->wpa_auth->conf.beacon_prot)
+	if (wpa_auth->conf.tx_bss_auth) {
+		wpa_auth = wpa_auth->conf.tx_bss_auth;
+		igtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
+	}
+
+	if (!wpa_auth->conf.beacon_prot)
 		return kde_len;
 
 	/* MLO BIGTK KDE for each link */
@@ -3865,7 +4065,8 @@
 
 	/* Add MLO GTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].gtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO GTK: link=%u", link_id);
@@ -3897,7 +4098,8 @@
 
 	/* Add MLO IGTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].igtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO IGTK: link=%u", link_id);
@@ -3936,7 +4138,9 @@
 
 	/* Add MLO BIGTK KDEs */
 	for (i = 0, link_id = 0; link_id < MAX_NUM_MLD_LINKS; link_id++) {
-		if (!sm->mld_links[link_id].valid)
+		if (!sm->mld_links[link_id].valid ||
+		    !ml_key_info.links[i].bigtk ||
+		    !ml_key_info.links[i].igtk_len)
 			continue;
 
 		wpa_printf(MSG_DEBUG, "RSN: MLO BIGTK: link=%u", link_id);
@@ -4233,6 +4437,11 @@
 
 	kde_len += wpa_auth_ml_kdes_len(sm);
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m3_elements)
+		kde_len += wpabuf_len(conf->eapol_m3_elements);
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	kde = os_malloc(kde_len);
 	if (!kde)
 		goto done;
@@ -4248,7 +4457,7 @@
 		size_t elen;
 
 		elen = pos - kde;
-		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name);
+		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true);
 		if (res < 0) {
 			wpa_printf(MSG_ERROR,
 				   "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data");
@@ -4347,6 +4556,17 @@
 
 	pos = wpa_auth_ml_kdes(sm, pos);
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (conf->eapol_m3_elements) {
+		os_memcpy(pos, wpabuf_head(conf->eapol_m3_elements),
+			  wpabuf_len(conf->eapol_m3_elements));
+		pos += wpabuf_len(conf->eapol_m3_elements);
+	}
+
+	if (conf->eapol_m3_no_encrypt)
+		encr = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	wpa_send_eapol(sm->wpa_auth, sm,
 		       (secure ? WPA_KEY_INFO_SECURE : 0) |
 		       (wpa_mic_len(sm->wpa_key_mgmt, sm->pmk_len) ?
@@ -4398,7 +4618,7 @@
 
 	/* MLD MAC address must be the same */
 	if (!kde.mac_addr ||
-	    os_memcmp(kde.mac_addr, sm->peer_mld_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(kde.mac_addr, sm->peer_mld_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "MLD: Mismatching or missing MLD address in EAPOL-Key msg 4/4");
 		return -1;
@@ -4732,7 +4952,8 @@
 				return;
 
 			kde = pos = kde_buf;
-			wpa_auth_ml_group_kdes(sm, pos);
+			pos = wpa_auth_ml_group_kdes(sm, pos);
+			kde_len = pos - kde_buf;
 		}
 #endif /* CONFIG_IEEE80211BE */
 	} else {
@@ -4912,19 +5133,30 @@
 				group->IGTK[group->GN_igtk - 4], len);
 	}
 
-	if (conf->ieee80211w != NO_MGMT_FRAME_PROTECTION &&
-	    conf->beacon_prot) {
-		len = wpa_cipher_key_len(conf->group_mgmt_cipher);
-		os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN);
-		inc_byte_array(group->Counter, WPA_NONCE_LEN);
-		if (wpa_gmk_to_gtk(group->GMK, "BIGTK key expansion",
-				   wpa_auth->addr, group->GNonce,
-				   group->BIGTK[group->GN_bigtk - 6], len) < 0)
-			ret = -1;
-		wpa_hexdump_key(MSG_DEBUG, "BIGTK",
-				group->BIGTK[group->GN_bigtk - 6], len);
+	if (!wpa_auth->non_tx_beacon_prot &&
+	    conf->ieee80211w == NO_MGMT_FRAME_PROTECTION)
+		return ret;
+	if (!conf->beacon_prot)
+		return ret;
+
+	if (wpa_auth->conf.tx_bss_auth) {
+		group = wpa_auth->conf.tx_bss_auth->group;
+		if (group->bigtk_set)
+			return ret;
+		wpa_printf(MSG_DEBUG, "Set up BIGTK for TX BSS");
 	}
 
+	len = wpa_cipher_key_len(conf->group_mgmt_cipher);
+	os_memcpy(group->GNonce, group->Counter, WPA_NONCE_LEN);
+	inc_byte_array(group->Counter, WPA_NONCE_LEN);
+	if (wpa_gmk_to_gtk(group->GMK, "BIGTK key expansion",
+			   wpa_auth->addr, group->GNonce,
+			   group->BIGTK[group->GN_bigtk - 6], len) < 0)
+		return -1;
+	group->bigtk_set = true;
+	wpa_hexdump_key(MSG_DEBUG, "BIGTK",
+			group->BIGTK[group->GN_bigtk - 6], len);
+
 	return ret;
 }
 
@@ -5085,9 +5317,10 @@
 
 int wpa_wnmsleep_bigtk_subelem(struct wpa_state_machine *sm, u8 *pos)
 {
-	struct wpa_group *gsm = sm->group;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_group *gsm = wpa_auth->group;
 	u8 *start = pos;
-	size_t len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	size_t len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 
 	/*
 	 * BIGTK subelement:
@@ -5097,7 +5330,7 @@
 	*pos++ = 2 + 6 + len;
 	WPA_PUT_LE16(pos, gsm->GN_bigtk);
 	pos += 2;
-	if (wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos) != 0)
+	if (wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos) != 0)
 		return 0;
 	pos += 6;
 
@@ -5187,12 +5420,21 @@
 				     KEY_FLAG_GROUP_TX_DEFAULT) < 0)
 			ret = -1;
 
-		if (ret == 0 && conf->beacon_prot &&
-		    wpa_auth_set_key(wpa_auth, group->vlan_id, alg,
+		if (ret || !conf->beacon_prot)
+			return ret;
+		if (wpa_auth->conf.tx_bss_auth) {
+			wpa_auth = wpa_auth->conf.tx_bss_auth;
+			group = wpa_auth->group;
+			if (!group->bigtk_set || group->bigtk_configured)
+				return ret;
+		}
+		if (wpa_auth_set_key(wpa_auth, group->vlan_id, alg,
 				     broadcast_ether_addr, group->GN_bigtk,
 				     group->BIGTK[group->GN_bigtk - 6], len,
 				     KEY_FLAG_GROUP_TX_DEFAULT) < 0)
 			ret = -1;
+		else
+			group->bigtk_configured = true;
 	}
 
 	return ret;
@@ -5337,9 +5579,11 @@
 		tmp = group->GM_igtk;
 		group->GM_igtk = group->GN_igtk;
 		group->GN_igtk = tmp;
-		tmp = group->GM_bigtk;
-		group->GM_bigtk = group->GN_bigtk;
-		group->GN_bigtk = tmp;
+		if (!wpa_auth->conf.tx_bss_auth) {
+			tmp = group->GM_bigtk;
+			group->GM_bigtk = group->GN_bigtk;
+			group->GN_bigtk = tmp;
+		}
 		wpa_gtk_update(wpa_auth, group);
 		wpa_group_config_group_keys(wpa_auth, group);
 	}
@@ -5689,28 +5933,11 @@
 
 int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr,
 			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
-			int session_timeout, int akmp)
-{
-	if (!wpa_auth || wpa_auth->conf.disable_pmksa_caching)
-		return -1;
-
-	wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK (2)", pmk, PMK_LEN);
-	if (pmksa_cache_auth_add(wpa_auth->pmksa, pmk, pmk_len, pmkid,
-				 NULL, 0, wpa_auth->addr, addr, session_timeout,
-				 NULL, akmp))
-		return 0;
-
-	return -1;
-}
-
-
-int wpa_auth_pmksa_add3(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 			int session_timeout, int akmp, const u8 *dpp_pkhash)
 {
 	struct rsn_pmksa_cache_entry *entry;
 
-	if (wpa_auth->conf.disable_pmksa_caching)
+	if (!wpa_auth || wpa_auth->conf.disable_pmksa_caching)
 		return -1;
 
 	wpa_hexdump_key(MSG_DEBUG, "RSN: Cache PMK (3)", pmk, PMK_LEN);
@@ -5834,13 +6061,14 @@
 void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa,
 			      struct wpa_state_machine *sm,
 			      struct wpa_authenticator *wpa_auth,
-			      u8 *pmkid, u8 *pmk)
+			      u8 *pmkid, u8 *pmk, size_t *pmk_len)
 {
 	if (!sm)
 		return;
 
 	sm->pmksa = pmksa;
-	os_memcpy(pmk, pmksa->pmk, PMK_LEN);
+	os_memcpy(pmk, pmksa->pmk, pmksa->pmk_len);
+	*pmk_len = pmksa->pmk_len;
 	os_memcpy(pmkid, pmksa->pmkid, PMKID_LEN);
 	os_memcpy(wpa_auth->dot11RSNAPMKIDUsed, pmksa->pmkid, PMKID_LEN);
 }
@@ -6358,7 +6586,7 @@
 		size_t elen;
 
 		elen = pos - kde;
-		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name);
+		res = wpa_insert_pmkid(kde, &elen, sm->pmk_r1_name, true);
 		if (res < 0) {
 			wpa_printf(MSG_ERROR,
 				   "FT: Failed to insert PMKR1Name into RSN IE in EAPOL-Key data");
diff --git a/src/ap/wpa_auth.h b/src/ap/wpa_auth.h
index 57fda8a..4f6bb76 100644
--- a/src/ap/wpa_auth.h
+++ b/src/ap/wpa_auth.h
@@ -244,6 +244,9 @@
 	unsigned int skip_send_eapol:1;
 	unsigned int enable_eapol_large_timeout:1;
 	bool delay_eapol_tx;
+	struct wpabuf *eapol_m1_elements;
+	struct wpabuf *eapol_m3_elements;
+	bool eapol_m3_no_encrypt;
 #endif /* CONFIG_TESTING_OPTIONS */
 	unsigned int oci_freq_override_eapol_m3;
 	unsigned int oci_freq_override_eapol_g1;
@@ -279,6 +282,11 @@
 	bool force_kdk_derivation;
 
 	bool radius_psk;
+
+	/* Pointer to Multi-BSSID transmitted BSS authenticator instance.
+	 * Set only in nontransmitted BSSs, i.e., is NULL for transmitted BSS
+	 * and in BSSs that are not part of a Multi-BSSID set. */
+	struct wpa_authenticator *tx_bss_auth;
 };
 
 typedef enum {
@@ -479,9 +487,6 @@
 void wpa_auth_add_sae_pmkid(struct wpa_state_machine *sm, const u8 *pmkid);
 int wpa_auth_pmksa_add2(struct wpa_authenticator *wpa_auth, const u8 *addr,
 			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
-			int session_timeout, int akmp);
-int wpa_auth_pmksa_add3(struct wpa_authenticator *wpa_auth, const u8 *addr,
-			const u8 *pmk, size_t pmk_len, const u8 *pmkid,
 			int session_timeout, int akmp, const u8 *dpp_pkhash);
 void wpa_auth_pmksa_remove(struct wpa_authenticator *wpa_auth,
 			   const u8 *sta_addr);
@@ -507,7 +512,7 @@
 void wpa_auth_pmksa_set_to_sm(struct rsn_pmksa_cache_entry *pmksa,
 			      struct wpa_state_machine *sm,
 			      struct wpa_authenticator *wpa_auth,
-			      u8 *pmkid, u8 *pmk);
+			      u8 *pmkid, u8 *pmk, size_t *pmk_len);
 int wpa_auth_sta_set_vlan(struct wpa_state_machine *sm, int vlan_id);
 void wpa_auth_eapol_key_tx_status(struct wpa_authenticator *wpa_auth,
 				  struct wpa_state_machine *sm, int ack);
diff --git a/src/ap/wpa_auth_ft.c b/src/ap/wpa_auth_ft.c
index 4b16f62..7744ed6 100644
--- a/src/ap/wpa_auth_ft.c
+++ b/src/ap/wpa_auth_ft.c
@@ -1427,7 +1427,7 @@
 
 	os_get_reltime(&now);
 	dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
-		if (os_memcmp(r0->spa, spa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(r0->spa, spa) &&
 		    os_memcmp_const(r0->pmk_r0_name, pmk_r0_name,
 				    WPA_PMK_NAME_LEN) == 0) {
 			*r0_out = r0;
@@ -1522,7 +1522,7 @@
 	os_get_reltime(&now);
 
 	dl_list_for_each(r1, &cache->pmk_r1, struct wpa_ft_pmk_r1_sa, list) {
-		if (os_memcmp(r1->spa, spa, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(r1->spa, spa) &&
 		    os_memcmp_const(r1->pmk_r1_name, pmk_r1_name,
 				    WPA_PMK_NAME_LEN) == 0) {
 			os_memcpy(pmk_r1, r1->pmk_r1, r1->pmk_r1_len);
@@ -2024,7 +2024,7 @@
 			    sm->r0kh_id, sm->r0kh_id_len);
 		return -1;
 	}
-	if (os_memcmp(r0kh->addr, sm->wpa_auth->addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(r0kh->addr, sm->wpa_auth->addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "FT: R0KH-ID points to self - no matching key available");
 		return -1;
@@ -2366,7 +2366,8 @@
 static u8 * wpa_ft_bigtk_subelem(struct wpa_state_machine *sm, size_t *len)
 {
 	u8 *subelem, *pos;
-	struct wpa_group *gsm = sm->group;
+	struct wpa_authenticator *wpa_auth = sm->wpa_auth;
+	struct wpa_group *gsm = wpa_auth->group;
 	size_t subelem_len;
 	const u8 *kek, *bigtk;
 	size_t kek_len;
@@ -2381,7 +2382,7 @@
 		kek_len = sm->PTK.kek_len;
 	}
 
-	bigtk_len = wpa_cipher_key_len(sm->wpa_auth->conf.group_mgmt_cipher);
+	bigtk_len = wpa_cipher_key_len(wpa_auth->conf.group_mgmt_cipher);
 
 	/* Sub-elem ID[1] | Length[1] | KeyID[2] | BIPN[6] | Key Length[1] |
 	 * Key[16+8] */
@@ -2395,7 +2396,7 @@
 	*pos++ = subelem_len - 2;
 	WPA_PUT_LE16(pos, gsm->GN_bigtk);
 	pos += 2;
-	wpa_auth_get_seqnum(sm->wpa_auth, NULL, gsm->GN_bigtk, pos);
+	wpa_auth_get_seqnum(wpa_auth, NULL, gsm->GN_bigtk, pos);
 	pos += 6;
 	*pos++ = bigtk_len;
 	bigtk = gsm->BIGTK[gsm->GN_bigtk - 6];
@@ -3765,7 +3766,7 @@
 		   " Target AP=" MACSTR " Action=%d)",
 		   MAC2STR(sta_addr), MAC2STR(target_ap), action);
 
-	if (os_memcmp(sta_addr, sm->addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sta_addr, sm->addr)) {
 		wpa_printf(MSG_DEBUG, "FT: Mismatch in FT Action STA address: "
 			   "STA=" MACSTR " STA-Address=" MACSTR,
 			   MAC2STR(sm->addr), MAC2STR(sta_addr));
@@ -3778,7 +3779,7 @@
 	 * APs in the MD (if such a list were configured).
 	 */
 	if ((target_ap[0] & 0x01) ||
-	    os_memcmp(target_ap, sm->wpa_auth->addr, ETH_ALEN) == 0) {
+	    ether_addr_equal(target_ap, sm->wpa_auth->addr)) {
 		wpa_printf(MSG_DEBUG, "FT: Invalid Target AP in FT Action "
 			   "frame");
 		return -1;
@@ -4036,7 +4037,7 @@
 		seq_ret = wpa_ft_rrb_seq_chk(r1kh->seq, src_addr, enc, enc_len,
 					     auth, auth_len, msgtype, no_defer);
 	if (!no_defer && r1kh_wildcard &&
-	    (!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
+	    (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) {
 		/* wildcard: r1kh-id unknown or changed addr -> do a seq req */
 		seq_ret = FT_RRB_SEQ_DEFER;
 	}
@@ -4203,7 +4204,7 @@
 					     cb ? 0 : 1);
 	}
 	if (cb && r0kh_wildcard &&
-	    (!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
+	    (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) {
 		/* wildcard: r0kh-id unknown or changed addr -> do a seq req */
 		seq_ret = FT_RRB_SEQ_DEFER;
 	}
@@ -4357,7 +4358,7 @@
 	struct ft_get_sta_ctx *info = ctx;
 
 	if ((info->s1kh_id &&
-	     os_memcmp(info->s1kh_id, sm->addr, ETH_ALEN) != 0) ||
+	     !ether_addr_equal(info->s1kh_id, sm->addr)) ||
 	    os_memcmp(info->nonce, sm->ft_pending_pull_nonce,
 		      FT_RRB_NONCE_LEN) != 0 ||
 	    sm->ft_pending_cb == NULL || sm->ft_pending_req_ies == NULL)
@@ -4482,7 +4483,7 @@
 		wpa_ft_rrb_lookup_r0kh(wpa_auth, f_r0kh_id, f_r0kh_id_len,
 				       &r0kh, &r0kh_wildcard);
 		if (!r0kh_wildcard &&
-		    (!r0kh || os_memcmp(r0kh->addr, src_addr, ETH_ALEN) != 0)) {
+		    (!r0kh || !ether_addr_equal(r0kh->addr, src_addr))) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R0KH-ID",
 				    f_r0kh_id, f_r0kh_id_len);
 			goto out;
@@ -4500,7 +4501,7 @@
 		wpa_ft_rrb_lookup_r1kh(wpa_auth, f_r1kh_id, &r1kh,
 				       &r1kh_wildcard);
 		if (!r1kh_wildcard &&
-		    (!r1kh || os_memcmp(r1kh->addr, src_addr, ETH_ALEN) != 0)) {
+		    (!r1kh || !ether_addr_equal(r1kh->addr, src_addr))) {
 			wpa_hexdump(MSG_DEBUG, "FT: Did not find R1KH-ID",
 				    f_r1kh_id, FT_R1KH_ID_LEN);
 			goto out;
@@ -4806,7 +4807,7 @@
 			return -1;
 		}
 
-		if (os_memcmp(target_ap_addr, wpa_auth->addr, ETH_ALEN) != 0) {
+		if (!ether_addr_equal(target_ap_addr, wpa_auth->addr)) {
 			wpa_printf(MSG_DEBUG, "FT: Target AP address in the "
 				   "RRB Request does not match with own "
 				   "address");
@@ -4969,7 +4970,7 @@
 		return;
 
 	dl_list_for_each(r0, &cache->pmk_r0, struct wpa_ft_pmk_r0_sa, list) {
-		if (os_memcmp(r0->spa, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(r0->spa, addr)) {
 			r0found = r0;
 			break;
 		}
diff --git a/src/ap/wpa_auth_glue.c b/src/ap/wpa_auth_glue.c
index 82d79f2..b286a77 100644
--- a/src/ap/wpa_auth_glue.c
+++ b/src/ap/wpa_auth_glue.c
@@ -183,8 +183,15 @@
 	wconf->oci_freq_override_ft_assoc = conf->oci_freq_override_ft_assoc;
 	wconf->oci_freq_override_fils_assoc =
 		conf->oci_freq_override_fils_assoc;
+
 	wconf->skip_send_eapol = iconf->skip_send_eapol;
 	wconf->enable_eapol_large_timeout = iconf->enable_eapol_large_timeout;
+
+	if (conf->eapol_m1_elements)
+		wconf->eapol_m1_elements = wpabuf_dup(conf->eapol_m1_elements);
+	if (conf->eapol_m3_elements)
+		wconf->eapol_m3_elements = wpabuf_dup(conf->eapol_m3_elements);
+	wconf->eapol_m3_no_encrypt = conf->eapol_m3_no_encrypt;
 #endif /* CONFIG_TESTING_OPTIONS */
 #ifdef CONFIG_P2P
 	os_memcpy(wconf->ip_addr_go, conf->ip_addr_go, 4);
@@ -556,7 +563,8 @@
 	if (sta) {
 		flags = hostapd_sta_flags_to_drv(sta->flags);
 #ifdef CONFIG_IEEE80211BE
-		if (sta->mld_info.mld_sta && (sta->flags & WLAN_STA_AUTHORIZED))
+		if (ap_sta_is_mld(hapd, sta) &&
+		    (sta->flags & WLAN_STA_AUTHORIZED))
 			link_id = -1;
 #endif /* CONFIG_IEEE80211BE */
 	}
@@ -669,7 +677,7 @@
 		hapd = iface->bss[j];
 		if (hapd == idata->src_hapd ||
 		    !hapd->wpa_auth ||
-		    os_memcmp(hapd->own_addr, idata->dst, ETH_ALEN) != 0)
+		    !ether_addr_equal(hapd->own_addr, idata->dst))
 			continue;
 
 		wpa_printf(MSG_DEBUG,
@@ -859,7 +867,7 @@
 			      MOBILITY_DOMAIN_ID_LEN) != 0)
 			continue; /* no matching FT SSID/mobility domain */
 		if (!is_multicast_ether_addr(idata->dst_addr) &&
-		    os_memcmp(hapd->own_addr, idata->dst_addr, ETH_ALEN) != 0)
+		    !ether_addr_equal(hapd->own_addr, idata->dst_addr))
 			continue; /* destination address does not match */
 
 		/* defer eth_p_oui_deliver until next eloop step as this is
@@ -1157,17 +1165,25 @@
 	if (!sta || !sta->wpa_sm)
 		return -1;
 
-	if (vlan->notempty &&
-	    !hostapd_vlan_valid(hapd->conf->vlan, vlan)) {
-		hostapd_logger(hapd, sta->addr, HOSTAPD_MODULE_IEEE80211,
-			       HOSTAPD_LEVEL_INFO,
-			       "Invalid VLAN %d%s received from FT",
-			       vlan->untagged, vlan->tagged[0] ? "+" : "");
-		return -1;
-	}
+	if (!(hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD)) {
+		if (vlan->notempty &&
+		    !hostapd_vlan_valid(hapd->conf->vlan, vlan)) {
+			hostapd_logger(hapd, sta->addr,
+				       HOSTAPD_MODULE_IEEE80211,
+				       HOSTAPD_LEVEL_INFO,
+				       "Invalid VLAN %d%s received from FT",
+				       vlan->untagged, vlan->tagged[0] ?
+				       "+" : "");
+			return -1;
+		}
 
-	if (ap_sta_set_vlan(hapd, sta, vlan) < 0)
-		return -1;
+		if (ap_sta_set_vlan(hapd, sta, vlan) < 0)
+			return -1;
+
+	} else {
+		if (vlan->notempty)
+			sta->vlan_id = vlan->untagged;
+	}
 	/* Configure wpa_group for GTK but ignore error due to driver not
 	 * knowing this STA. */
 	ap_sta_bind_vlan(hapd, sta);
@@ -1190,10 +1206,15 @@
 	if (!sta)
 		return -1;
 
-	if (sta->vlan_desc)
+	if (sta->vlan_desc) {
 		*vlan = *sta->vlan_desc;
-	else
+	} else if ((hapd->iface->drv_flags & WPA_DRIVER_FLAGS_VLAN_OFFLOAD) &&
+		   sta->vlan_id) {
+		vlan->notempty = 1;
+		vlan->untagged = sta->vlan_id;
+	} else {
 		os_memset(vlan, 0, sizeof(*vlan));
+	}
 
 	return 0;
 }
@@ -1396,7 +1417,7 @@
 	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
 		   MACSTR, MAC2STR(ethhdr->h_source), MAC2STR(ethhdr->h_dest));
 	if (!is_multicast_ether_addr(ethhdr->h_dest) &&
-	    os_memcmp(hapd->own_addr, ethhdr->h_dest, ETH_ALEN) != 0)
+	    !ether_addr_equal(hapd->own_addr, ethhdr->h_dest))
 		return;
 	wpa_ft_rrb_rx(hapd->wpa_auth, ethhdr->h_source, buf + sizeof(*ethhdr),
 		      len - sizeof(*ethhdr));
@@ -1412,7 +1433,7 @@
 	wpa_printf(MSG_DEBUG, "FT: RRB received packet " MACSTR " -> "
 		   MACSTR, MAC2STR(src_addr), MAC2STR(dst_addr));
 	if (!is_multicast_ether_addr(dst_addr) &&
-	    os_memcmp(hapd->own_addr, dst_addr, ETH_ALEN) != 0)
+	    !ether_addr_equal(hapd->own_addr, dst_addr))
 		return;
 	wpa_ft_rrb_oui_rx(hapd->wpa_auth, src_addr, dst_addr, oui_suffix, buf,
 			  len);
@@ -1540,7 +1561,8 @@
 
 			if (!iface->bss[0]->conf->mld_ap ||
 			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
-			    link_id != iface->bss[0]->mld_link_id)
+			    link_id != iface->bss[0]->mld_link_id ||
+			    !iface->bss[0]->wpa_auth)
 				continue;
 
 			wpa_auth_ml_get_rsn_info(iface->bss[0]->wpa_auth,
@@ -1582,7 +1604,8 @@
 
 			if (!iface->bss[0]->conf->mld_ap ||
 			    hapd->conf->mld_id != iface->bss[0]->conf->mld_id ||
-			    link_id != iface->bss[0]->mld_link_id)
+			    link_id != iface->bss[0]->mld_link_id ||
+			    !iface->bss[0]->wpa_auth)
 				continue;
 
 			wpa_auth_ml_get_key_info(iface->bss[0]->wpa_auth,
@@ -1675,9 +1698,13 @@
 	};
 	const u8 *wpa_ie;
 	size_t wpa_ie_len;
+	struct hostapd_data *tx_bss;
 
 	hostapd_wpa_auth_conf(hapd->conf, hapd->iconf, &_conf);
 	_conf.msg_ctx = hapd->msg_ctx;
+	tx_bss = hostapd_mbssid_get_tx_bss(hapd);
+	if (tx_bss != hapd)
+		_conf.tx_bss_auth = tx_bss->wpa_auth;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_EAPOL_TX_STATUS)
 		_conf.tx_status = 1;
 	if (hapd->iface->drv_flags & WPA_DRIVER_FLAGS_AP_MLME)
diff --git a/src/ap/wpa_auth_i.h b/src/ap/wpa_auth_i.h
index 74ae5ad..9ba8304 100644
--- a/src/ap/wpa_auth_i.h
+++ b/src/ap/wpa_auth_i.h
@@ -134,8 +134,6 @@
 					   * Request */
 	u8 r0kh_id[FT_R0KH_ID_MAX_LEN]; /* R0KH-ID from FT Auth Request */
 	size_t r0kh_id_len;
-	u8 sup_pmk_r1_name[WPA_PMK_NAME_LEN]; /* PMKR1Name from EAPOL-Key
-					       * message 2/4 */
 	u8 *assoc_resp_ftie;
 
 	void (*ft_pending_cb)(void *ctx, const u8 *dst, const u8 *bssid,
@@ -222,6 +220,8 @@
 	u8 BIGTK[2][WPA_IGTK_MAX_LEN];
 	int GN_igtk, GM_igtk;
 	int GN_bigtk, GM_bigtk;
+	bool bigtk_set;
+	bool bigtk_configured;
 	/* Number of references except those in struct wpa_group->next */
 	unsigned int references;
 	unsigned int num_setup_iface;
@@ -257,6 +257,8 @@
 	struct rsn_pmksa_cache *pmksa;
 	struct wpa_ft_pmk_cache *ft_pmk_cache;
 
+	bool non_tx_beacon_prot;
+
 #ifdef CONFIG_P2P
 	struct bitfield *ip_pool;
 #endif /* CONFIG_P2P */
diff --git a/src/ap/wps_hostapd.c b/src/ap/wps_hostapd.c
index aacfa33..82d4d5f 100644
--- a/src/ap/wps_hostapd.c
+++ b/src/ap/wps_hostapd.c
@@ -288,7 +288,7 @@
 			any_psk = wpa_psk->psk;
 
 		if (mac_addr && !dev_psk &&
-		    os_memcmp(mac_addr, wpa_psk->addr, ETH_ALEN) == 0) {
+		    ether_addr_equal(mac_addr, wpa_psk->addr)) {
 			dev_psk = wpa_psk->psk;
 			break;
 		}
diff --git a/src/common/dpp_tcp.c b/src/common/dpp_tcp.c
index d226a8a..2fad3e1 100644
--- a/src/common/dpp_tcp.c
+++ b/src/common/dpp_tcp.c
@@ -563,7 +563,7 @@
 	struct dpp_connection *conn;
 
 	dl_list_for_each(conn, &ctrl->conn, struct dpp_connection, list) {
-		if (os_memcmp(src, conn->mac_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(src, conn->mac_addr))
 			return conn;
 		if ((type == DPP_PA_PKEX_EXCHANGE_RESP ||
 		     type == DPP_PA_AUTHENTICATION_RESP) &&
@@ -661,7 +661,7 @@
 	struct dpp_connection *conn;
 
 	dl_list_for_each(conn, &ctrl->conn, struct dpp_connection, list) {
-		if (os_memcmp(src, conn->mac_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(src, conn->mac_addr))
 			return conn;
 	}
 
@@ -2601,7 +2601,8 @@
 }
 
 
-static void dpp_tcp_send_conn_status_msg(struct dpp_connection *conn,
+static void dpp_tcp_send_conn_status_msg(struct dpp_global *dpp,
+					 struct dpp_connection *conn,
 					 enum dpp_status_error result,
 					 const u8 *ssid, size_t ssid_len,
 					 const char *channel_list)
@@ -2609,6 +2610,7 @@
 	struct dpp_authentication *auth = conn->auth;
 	int res;
 	struct wpabuf *msg;
+	struct dpp_connection *c;
 
 	auth->conn_status_requested = 0;
 
@@ -2627,8 +2629,16 @@
 		return;
 	}
 
-	/* This exchange will be terminated in the TX status handler */
-	conn->on_tcp_tx_complete_remove = 1;
+	/* conn might have been removed during the dpp_tcp_send_msg() call, so
+	 * need to check that it is still present before modifying it. */
+	dl_list_for_each(c, &dpp->tcp_init, struct dpp_connection, list) {
+		if (conn == c) {
+			/* This exchange will be terminated in the TX status
+			 * handler */
+			conn->on_tcp_tx_complete_remove = 1;
+			break;
+		}
+	}
 }
 
 
@@ -2641,7 +2651,7 @@
 
 	dl_list_for_each(conn, &dpp->tcp_init, struct dpp_connection, list) {
 		if (conn->auth && conn->auth->conn_status_requested) {
-			dpp_tcp_send_conn_status_msg(conn, result, ssid,
+			dpp_tcp_send_conn_status_msg(dpp, conn, result, ssid,
 						     ssid_len, channel_list);
 			break;
 		}
diff --git a/src/common/gas_server.c b/src/common/gas_server.c
index 745a13f..1075500 100644
--- a/src/common/gas_server.c
+++ b/src/common/gas_server.c
@@ -352,7 +352,7 @@
 	dl_list_for_each(response, &gas->responses, struct gas_server_response,
 			 list) {
 		if (response->dialog_token != dialog_token ||
-		    os_memcmp(sa, response->dst, ETH_ALEN) != 0)
+		    !ether_addr_equal(sa, response->dst))
 			continue;
 		gas_server_handle_rx_comeback_req(response);
 		return 0;
@@ -470,7 +470,7 @@
 	dl_list_for_each(response, &gas->responses, struct gas_server_response,
 			 list) {
 		if (response->dialog_token != dialog_token ||
-		    os_memcmp(dst, response->dst, ETH_ALEN) != 0)
+		    !ether_addr_equal(dst, response->dst))
 			continue;
 		gas_server_handle_tx_status(response, ack);
 		return;
diff --git a/src/common/hw_features_common.c b/src/common/hw_features_common.c
index 57b5a8e..f45d2e9 100644
--- a/src/common/hw_features_common.c
+++ b/src/common/hw_features_common.c
@@ -448,6 +448,7 @@
 		} else {
 			int freq1, freq2 = 0;
 			int bw = center_idx_to_bw_6ghz(center_segment0);
+			int opclass;
 
 			if (bw < 0) {
 				wpa_printf(MSG_ERROR,
@@ -455,7 +456,10 @@
 				return -1;
 			}
 
-			freq1 = ieee80211_chan_to_freq(NULL, 131,
+			/* The 6 GHz channel 2 uses a different operating class
+			 */
+			opclass = center_segment0 == 2 ? 136 : 131;
+			freq1 = ieee80211_chan_to_freq(NULL, opclass,
 						       center_segment0);
 			if (freq1 < 0) {
 				wpa_printf(MSG_ERROR,
diff --git a/src/common/ieee802_11_common.c b/src/common/ieee802_11_common.c
index dbe7b29..08ba45b 100644
--- a/src/common/ieee802_11_common.c
+++ b/src/common/ieee802_11_common.c
@@ -1012,7 +1012,7 @@
 			continue;
 		}
 
-		if (sub_elem_len < 3) {
+		if (sub_elem_len < 5) {
 			if (show_errors)
 				wpa_printf(MSG_DEBUG,
 					   "MLD: error: sub_elem_len=%zu < 5",
@@ -1081,7 +1081,8 @@
 			non_inherit_len -= 1 + non_inherit[0];
 			non_inherit += 1 + non_inherit[0];
 
-			if (non_inherit_len < 1UL + non_inherit[0]) {
+			if (non_inherit_len < 1UL ||
+			    non_inherit_len < 1UL + non_inherit[0]) {
 				if (show_errors)
 					wpa_printf(MSG_DEBUG,
 						   "MLD: Invalid inheritance");
@@ -2507,35 +2508,6 @@
 
 
 /**
- * get_ie_nth - Fetch a specified information element from IEs buffer
- * @ies: Information elements buffer
- * @len: Information elements buffer length
- * @eid: Information element identifier (WLAN_EID_*)
- * @nth: Return the nth element of the requested type (2 returns the second)
- * Returns: Pointer to the information element (id field) or %NULL if not found
- *
- * This function returns the nth matching information element in the IEs
- * buffer or %NULL in case the element is not found.
- */
-const u8 * get_ie_nth(const u8 *ies, size_t len, u8 eid, int nth)
-{
-	const struct element *elem;
-	int sofar = 0;
-
-	if (!ies)
-		return NULL;
-
-	for_each_element_id(elem, eid, ies, len) {
-		sofar++;
-		if (sofar == nth)
-			return &elem->id;
-	}
-
-	return NULL;
-}
-
-
-/**
  * get_ie_ext - Fetch a specified extended information element from IEs buffer
  * @ies: Information elements buffer
  * @len: Information elements buffer length
@@ -2874,6 +2846,21 @@
 }
 
 
+bool is_same_band(int freq1, int freq2)
+{
+	if (IS_2P4GHZ(freq1) && IS_2P4GHZ(freq2))
+		return true;
+
+	if (IS_5GHZ(freq1) && IS_5GHZ(freq2))
+		return true;
+
+	if (is_6ghz_freq(freq1) && is_6ghz_freq(freq2))
+		return true;
+
+	return false;
+}
+
+
 int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep,
 				    size_t nei_rep_len)
 {
@@ -3212,8 +3199,39 @@
 }
 
 
-struct wpabuf * ieee802_11_defrag_data(const u8 *data, size_t len,
-				       bool ext_elem)
+/**
+ * chwidth_freq2_to_ch_width - Determine channel width as enum oper_chan_width
+ * @chwidth: Channel width integer
+ * @freq2: Value for frequency 2. 0 is not used
+ * Returns: enum oper_chan_width, -1 on failure
+ */
+int chwidth_freq2_to_ch_width(int chwidth, int freq2)
+{
+	if (freq2 < 0)
+		return -1;
+	if (freq2)
+		return CONF_OPER_CHWIDTH_80P80MHZ;
+
+	switch (chwidth) {
+	case 0:
+	case 20:
+	case 40:
+		return CONF_OPER_CHWIDTH_USE_HT;
+	case 80:
+		return CONF_OPER_CHWIDTH_80MHZ;
+	case 160:
+		return CONF_OPER_CHWIDTH_160MHZ;
+	case 320:
+		return CONF_OPER_CHWIDTH_320MHZ;
+	default:
+		wpa_printf(MSG_DEBUG, "Unknown max oper bandwidth: %d",
+			   chwidth);
+		return -1;
+	}
+}
+
+
+struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem)
 {
 	struct wpabuf *buf;
 	const u8 *pos, *end = data + len;
@@ -3253,43 +3271,42 @@
 }
 
 
-struct wpabuf * ieee802_11_defrag(struct ieee802_11_elems *elems,
-				  u8 eid, u8 eid_ext)
+const u8 * get_ml_ie(const u8 *ies, size_t len, u8 type)
 {
-	const u8 *data;
-	size_t len;
+	const struct element *elem;
 
-	/*
-	 * TODO: Defragmentation mechanism can be supported for all IEs. For now
-	 * handle only those that are used (or use ieee802_11_defrag_data()).
-	 */
-	switch (eid) {
-	case WLAN_EID_EXTENSION:
-		switch (eid_ext) {
-		case WLAN_EID_EXT_FILS_HLP_CONTAINER:
-			data = elems->fils_hlp;
-			len = elems->fils_hlp_len;
-			break;
-		case WLAN_EID_EXT_WRAPPED_DATA:
-			data = elems->wrapped_data;
-			len = elems->wrapped_data_len;
-			break;
-		default:
-			wpa_printf(MSG_DEBUG,
-				   "Defragmentation not supported. eid_ext=%u",
-				   eid_ext);
-			return NULL;
-		}
-		break;
-	default:
-		wpa_printf(MSG_DEBUG,
-			   "Defragmentation not supported. eid=%u", eid);
+	if (!ies)
 		return NULL;
+
+	for_each_element_extid(elem, WLAN_EID_EXT_MULTI_LINK, ies, len) {
+		if (elem->datalen >= 2 &&
+		    (elem->data[1] & MULTI_LINK_CONTROL_TYPE_MASK) == type)
+			return &elem->id;
 	}
 
-	return ieee802_11_defrag_data(data, len, true);
+	return NULL;
 }
 
+
+const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len)
+{
+	const size_t mld_addr_pos =
+		2 /* Control field */ +
+		1 /* Common Info Length field */;
+	const size_t fixed_len = mld_addr_pos +
+		ETH_ALEN /* MLD MAC Address field */;
+
+	if (len < fixed_len)
+		return NULL;
+
+	if ((buf[0] & MULTI_LINK_CONTROL_TYPE_MASK) !=
+	    MULTI_LINK_CONTROL_TYPE_BASIC)
+		return NULL;
+
+	return &buf[mld_addr_pos];
+}
+
+
 /* Parse HT capabilities to get maximum number of supported spatial streams */
 static int parse_ht_mcs_set_for_max_nss(
 				struct ieee80211_ht_capabilities *htcaps,
@@ -3419,6 +3436,7 @@
 	return supported_width;
 }
 
+
 /*
  * Parse VHT operation info fields to get operation channel width
  * note that VHT operation info fields could come from VHT operation IE
@@ -3453,6 +3471,7 @@
 	return channel_width;
 }
 
+
 /* Parse 6GHz operation info fields to get operation channel width */
 static enum chan_width get_6ghz_operation_channel_width(
 				struct ieee80211_6ghz_operation_info * six_ghz_oper_info)
@@ -3525,6 +3544,7 @@
 	return channel_width;
 }
 
+
 /* Parse EHT operation IE to get EHT operation channel width */
 static enum chan_width get_eht_operation_channel_width(
 				struct ieee80211_eht_operation *eht_oper,
@@ -3558,6 +3578,7 @@
 	return channel_width;
 }
 
+
 /* Parse HT/VHT/HE operation IEs to get operation channel width */
 enum chan_width get_operation_channel_width(struct ieee802_11_elems *elems)
 {
@@ -3594,6 +3615,8 @@
 	return channel_width;
 }
 
+
+
 /*
  * Get STA operation channel width from AP's operation channel width and
  *  STA's supported channel width
@@ -3615,78 +3638,6 @@
 	return ap_operation_chan_width;
 }
 
-const u8 * get_ml_ie(const u8 *ies, size_t len, u8 type)
-{
-	const struct element *elem;
-
-	if (!ies)
-		return NULL;
-
-	for_each_element_extid(elem, WLAN_EID_EXT_MULTI_LINK, ies, len) {
-		if (elem->datalen >= 2 &&
-		    (elem->data[1] & MULTI_LINK_CONTROL_TYPE_MASK) == type)
-			return &elem->id;
-	}
-
-	return NULL;
-}
-
-
-const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len)
-{
-	const size_t mld_addr_pos =
-		2 /* Control field */ +
-		1 /* Common Info Length field */;
-	const size_t fixed_len = mld_addr_pos +
-		ETH_ALEN /* MLD MAC Address field */;
-
-	if (len < fixed_len)
-		return NULL;
-
-	if ((buf[0] & MULTI_LINK_CONTROL_TYPE_MASK) !=
-	    MULTI_LINK_CONTROL_TYPE_BASIC)
-		return NULL;
-
-	return &buf[mld_addr_pos];
-}
-
-
-struct wpabuf * ieee802_11_defrag_mle(struct ieee802_11_elems *elems, u8 type)
-{
-	const u8 *data;
-	size_t len;
-
-	switch (type) {
-	case MULTI_LINK_CONTROL_TYPE_BASIC:
-		data = elems->basic_mle;
-		len = elems->basic_mle_len;
-		break;
-	case MULTI_LINK_CONTROL_TYPE_PROBE_REQ:
-		data = elems->probe_req_mle;
-		len = elems->probe_req_mle_len;
-		break;
-	case MULTI_LINK_CONTROL_TYPE_RECONF:
-		data = elems->reconf_mle;
-		len = elems->reconf_mle_len;
-		break;
-	case MULTI_LINK_CONTROL_TYPE_TDLS:
-		data = elems->tdls_mle;
-		len = elems->tdls_mle_len;
-		break;
-	case MULTI_LINK_CONTROL_TYPE_PRIOR_ACCESS:
-		data = elems->prior_access_mle;
-		len = elems->prior_access_mle_len;
-		break;
-	default:
-		wpa_printf(MSG_DEBUG,
-			   "Defragmentation not supported for Multi-Link element type=%u",
-			   type);
-		return NULL;
-	}
-
-	return ieee802_11_defrag_data(data, len, true);
-}
-
 
 unsigned int is_ap_t2lm_negotiation_supported(const u8 *mle, size_t mle_len)
 {
diff --git a/src/common/ieee802_11_common.h b/src/common/ieee802_11_common.h
index 6f4fe80..60260e5 100644
--- a/src/common/ieee802_11_common.h
+++ b/src/common/ieee802_11_common.h
@@ -261,7 +261,6 @@
 extern size_t global_op_class_size;
 
 const u8 * get_ie(const u8 *ies, size_t len, u8 eid);
-const u8 * get_ie_nth(const u8 *ies, size_t len, u8 eid, int nth);
 const u8 * get_ie_ext(const u8 *ies, size_t len, u8 ext);
 const u8 * get_vendor_ie(const u8 *ies, size_t len, u32 vendor_type);
 
@@ -284,6 +283,10 @@
 bool is_6ghz_psc_frequency(int freq);
 int get_6ghz_sec_channel(int channel);
 
+bool is_same_band(int freq1, int freq2);
+#define IS_2P4GHZ(n) (n >= 2412 && n <= 2484)
+#define IS_5GHZ(n) (n > 4000 && n < 5895)
+
 int ieee802_11_parse_candidate_list(const char *pos, u8 *nei_rep,
 				    size_t nei_rep_len);
 
@@ -293,6 +296,7 @@
 bool ieee802_11_rsnx_capab(const u8 *rsnxe, unsigned int capab);
 int op_class_to_bandwidth(u8 op_class);
 enum oper_chan_width op_class_to_ch_width(u8 op_class);
+int chwidth_freq2_to_ch_width(int chwidth, int freq2);
 
 /* element iteration helpers */
 #define for_each_element(_elem, _data, _datalen)			\
@@ -350,11 +354,7 @@
 int ieee802_edmg_is_allowed(struct ieee80211_edmg_config allowed,
 			    struct ieee80211_edmg_config requested);
 
-struct wpabuf * ieee802_11_defrag_data(const u8 *data, size_t len,
-				       bool ext_elem);
-struct wpabuf * ieee802_11_defrag(struct ieee802_11_elems *elems,
-				  u8 eid, u8 eid_ext);
-struct wpabuf * ieee802_11_defrag_mle(struct ieee802_11_elems *elems, u8 type);
+struct wpabuf * ieee802_11_defrag(const u8 *data, size_t len, bool ext_elem);
 const u8 * get_ml_ie(const u8 *ies, size_t len, u8 type);
 const u8 * get_basic_mle_mld_addr(const u8 *buf, size_t len);
 
diff --git a/src/common/ieee802_11_defs.h b/src/common/ieee802_11_defs.h
index a895f9c..619aa19 100644
--- a/src/common/ieee802_11_defs.h
+++ b/src/common/ieee802_11_defs.h
@@ -24,6 +24,13 @@
 #define WLAN_FC_ISWEP		0x4000
 #define WLAN_FC_HTC		0x8000
 
+#define WLAN_FC_S1G_BEACON_NEXT_TBTT	0x0100
+#define WLAN_FC_S1G_BEACON_COMP_SSID	0x0200
+#define WLAN_FC_S1G_BEACON_ANO		0x0400
+#define WLAN_FC_S1G_BEACON_BSS_BW	0x3800
+#define WLAN_FC_S1G_BEACON_SECURITY	0x4000
+#define WLAN_FC_S1G_BEACON_AP_PM	0x8000
+
 #define WLAN_FC_GET_TYPE(fc)	(((fc) & 0x000c) >> 2)
 #define WLAN_FC_GET_STYPE(fc)	(((fc) & 0x00f0) >> 4)
 
@@ -36,6 +43,7 @@
 #define WLAN_FC_TYPE_MGMT		0
 #define WLAN_FC_TYPE_CTRL		1
 #define WLAN_FC_TYPE_DATA		2
+#define WLAN_FC_TYPE_EXT		3
 
 /* management */
 #define WLAN_FC_STYPE_ASSOC_REQ		0
@@ -77,6 +85,10 @@
 #define WLAN_FC_STYPE_QOS_CFPOLL	14
 #define WLAN_FC_STYPE_QOS_CFACKPOLL	15
 
+/* extension */
+#define WLAN_FC_STYPE_DMG_BEACON	0
+#define WLAN_FC_STYPE_S1G_BEACON	1
+
 /* Authentication algorithms */
 #define WLAN_AUTH_OPEN			0
 #define WLAN_AUTH_SHARED_KEY		1
@@ -107,7 +119,7 @@
 #define WLAN_CAPABILITY_DELAYED_BLOCK_ACK BIT(14)
 #define WLAN_CAPABILITY_IMM_BLOCK_ACK BIT(15)
 
-/* Status codes (IEEE Std 802.11-2016, 9.4.1.9, Table 9-46) */
+/* Status codes (IEEE Std 802.11-2020, 9.4.1.9, Table 9-50) */
 #define WLAN_STATUS_SUCCESS 0
 #define WLAN_STATUS_UNSPECIFIED_FAILURE 1
 #define WLAN_STATUS_TDLS_WAKEUP_ALTERNATE 2
@@ -209,11 +221,20 @@
 #define WLAN_STATUS_DENIED_HE_NOT_SUPPORTED 124
 #define WLAN_STATUS_SAE_HASH_TO_ELEMENT 126
 #define WLAN_STATUS_SAE_PK 127
+#define WLAN_STATUS_DENIED_STA_AFF_WITH_MLD_WITH_EXISTING_ASSOC 130
+#define WLAN_STATUS_EPCS_DENIED_UNAUTHORIZED 131
+#define WLAN_STATUS_EPCS_DENIED 132
+#define WLAN_STATUS_DENIED_TID_TO_LINK_MAPPING 133
+#define WLAN_STATUS_PREFERRED_TID_TO_LINK_MAPPING_SUGGESTED 134
+#define WLAN_STATUS_DENIED_EHT_NOT_SUPPORTED 135
 #define WLAN_STATUS_INVALID_PUBLIC_KEY 136
 #define WLAN_STATUS_PASN_BASE_AKMP_FAILED 137
 #define WLAN_STATUS_OCI_MISMATCH 138
+#define WLAN_STATUS_DENIED_TX_LINK_NOT_ACCEPTED 139
+#define WLAN_STATUS_EPCS_DENIED_VERIFICATION_FAILURE 140
+#define WLAN_STATUS_DENIED_OPERATION_PARAMETER_UPDATE 141
 
-/* Reason codes (IEEE Std 802.11-2016, 9.4.1.7, Table 9-45) */
+/* Reason codes (IEEE Std 802.11-2020, 9.4.1.7, Table 9-90) */
 #define WLAN_REASON_UNSPECIFIED 1
 #define WLAN_REASON_PREV_AUTH_NOT_VALID 2
 #define WLAN_REASON_DEAUTH_LEAVING 3
@@ -277,7 +298,7 @@
 #define WLAN_REASON_MESH_CHANNEL_SWITCH_UNSPECIFIED 66
 
 
-/* Information Element IDs (IEEE Std 802.11-2016, 9.4.2.1, Table 9-77) */
+/* Element IDs (IEEE Std 802.11-2020, 9.4.2.1, Table 9-92) */
 #define WLAN_EID_SSID 0
 #define WLAN_EID_SUPP_RATES 1
 #define WLAN_EID_DS_PARAMS 3
@@ -599,7 +620,7 @@
 /* Multiple BSSID element subelements */
 #define WLAN_MBSSID_SUBELEMENT_NONTRANSMITTED_BSSID_PROFILE 0
 
-/* Action frame categories (IEEE Std 802.11-2016, 9.4.1.11, Table 9-76) */
+/* Action frame categories (IEEE Std 802.11-2020, 9.4.1.11, Table 9-51) */
 #define WLAN_ACTION_SPECTRUM_MGMT 0
 #define WLAN_ACTION_QOS 1
 #define WLAN_ACTION_DLS 2
@@ -640,7 +661,7 @@
 #define WLAN_ACTION_VENDOR_SPECIFIC 127
 /* Note: 128-255 used to report errors by setting category | 0x80 */
 
-/* Public action codes (IEEE Std 802.11-2016, 9.6.8.1, Table 9-307) */
+/* Public action codes (IEEE Std 802.11-2020, 9.6.7.1, Table 9-364) */
 #define WLAN_PA_20_40_BSS_COEX 0
 #define WLAN_PA_DSE_ENABLEMENT 1
 #define WLAN_PA_DSE_DEENABLEMENT 2
@@ -691,8 +712,8 @@
 #define WLAN_VHT_ACTION_GROUP_ID_MGMT 1
 #define WLAN_VHT_ACTION_OPMODE_NOTIF 2
 
-/* Protected Dual of Public Action frames (IEEE Std 802.11-2016, 9.6.11,
- * Table 9-332) */
+/* Protected Dual of Public Action frames (IEEE Std 802.11-2020, 9.6.10,
+ * Table 9-404) */
 #define WLAN_PROT_DSE_ENABLEMENT 1
 #define WLAN_PROT_DSE_DEENABLEMENT 2
 #define WLAN_PROT_EXT_CSA 4
@@ -720,7 +741,7 @@
 #define WLAN_PROT_NETWORK_CHANNEL_CONTROL 30
 #define WLAN_PROT_WHITE_SPACE_MAP_ANNOUNCEMENT 31
 
-/* SA Query Action frame (IEEE 802.11w/D8.0, 7.4.9) */
+/* SA Query Action frame (IEEE Std 802.11-2020, 9.6.9) */
 #define WLAN_SA_QUERY_REQUEST 0
 #define WLAN_SA_QUERY_RESPONSE 1
 
@@ -753,7 +774,7 @@
 #define WLAN_PROT_FTM_REPORT 3
 
 /* Radio Measurement capabilities (from RM Enabled Capabilities element)
- * IEEE Std 802.11-2016, 9.4.2.45, Table 9-157 */
+ * IEEE Std 802.11-2020, 9.4.2.44, Table 9-179 */
 /* byte 1 (out of 5) */
 #define WLAN_RRM_CAPS_LINK_MEASUREMENT BIT(0)
 #define WLAN_RRM_CAPS_NEIGHBOR_REPORT BIT(1)
@@ -766,8 +787,8 @@
 #define WLAN_RRM_CAPS_FTM_RANGE_REPORT BIT(2)
 
 /*
- * IEEE P802.11-REVmc/D5.0, 9.4.2.21.19 (Fine Timing Measurement Range
- * request) - Minimum AP count
+ * IEEE Std 802.11-2020, 9.4.2.20.19 (Fine Timing Measurement Range
+ * request) - Minimum AP Count
  */
 #define WLAN_RRM_RANGE_REQ_MAX_MIN_AP 15
 
@@ -776,7 +797,8 @@
 #define WLAN_TIMEOUT_KEY_LIFETIME 2
 #define WLAN_TIMEOUT_ASSOC_COMEBACK 3
 
-/* Interworking element (IEEE 802.11u) - Access Network Options */
+/* Interworking element (IEEE Std 802.11-2020, 9.4.2.91) -
+ * Access Network Options */
 #define INTERWORKING_ANO_ACCESS_NETWORK_MASK 0x0f
 #define INTERWORKING_ANO_INTERNET 0x10
 #define INTERWORKING_ANO_ASRA 0x20
@@ -792,7 +814,7 @@
 #define INTERWORKING_ANT_TEST 6
 #define INTERWORKING_ANT_WILDCARD 15
 
-/* Advertisement Protocol ID definitions (IEEE Std 802.11-2016, Table 9-215) */
+/* Advertisement Protocol ID definitions (IEEE Std 802.11-2020, Table 9-237) */
 enum adv_proto_id {
 	ACCESS_NETWORK_QUERY_PROTOCOL = 0,
 	MIH_INFO_SERVICE = 1,
@@ -802,8 +824,7 @@
 	ADV_PROTO_VENDOR_SPECIFIC = 221
 };
 
-/* Access Network Query Protocol info ID definitions (IEEE Std 802.11-2016,
- * Table 9-271; P802.11ai) */
+/* ANQP-element definitions (IEEE Std 802.11-2020, Table 9-331) */
 enum anqp_info_id {
 	ANQP_QUERY_LIST = 256,
 	ANQP_CAPABILITY_LIST = 257,
@@ -878,7 +899,7 @@
 #define S1G_ACT_TWT_INFORMATION      11
 
 /*
- * IEEE P802.11-REVmc/D5.0 Table 9-81 - Measurement type definitions for
+ * IEEE Std 802.11-2020, Table 9-98 - Measurement type definitions for
  * measurement requests
  */
 enum measure_type {
@@ -902,7 +923,7 @@
 	MEASURE_TYPE_MEASURE_PAUSE = 255,
 };
 
-/* IEEE Std 802.11-2012 Table 8-71 - Location subject definition */
+/* IEEE Std 802.11-2020, Table 9-110 - Location Subject field definition */
 enum location_subject {
 	LOCATION_SUBJECT_LOCAL = 0,
 	LOCATION_SUBJECT_REMOTE = 1,
@@ -910,7 +931,7 @@
 };
 
 /*
- * IEEE P802.11-REVmc/D5.0 Table 9-94 - Optional subelement IDs for LCI request
+ * IEEE Std 802.11-2020, Table 9-111 - Optional subelement IDs for LCI request
  */
 enum lci_req_subelem {
 	LCI_REQ_SUBELEM_AZIMUTH_REQ = 1,
@@ -945,6 +966,23 @@
 
 #define IEEE80211_HDRLEN (sizeof(struct ieee80211_hdr))
 
+struct ieee80211_hdr_s1g_beacon {
+	le16 frame_control;
+	le16 duration_id;
+	u8 sa[6];
+	u8 timestamp[4];
+	u8 change_seq[1];
+	/* followed by:
+	 *   'u8 next_tbtt[3];' if the Next TBTT Present field in the
+	 *			Frame Control field is 1
+	 *   'u8 compressed_ssid[4];' if the Compressed SSID Present field in
+	 *			the Frame Control is 1
+	 *   'u8 ano[1];' if the ANO field in the Frame Control field is 1
+	 */
+} STRUCT_PACKED;
+
+#define IEEE80211_HDRLEN_S1G_BEACON (sizeof(struct ieee80211_hdr_s1g_beacon))
+
 #define IEEE80211_FC(type, stype) host_to_le16((type << 2) | (stype << 4))
 
 struct ieee80211_mgmt {
@@ -1396,7 +1434,11 @@
 #define WFD_OUI_TYPE 10
 #define HS20_IE_VENDOR_TYPE 0x506f9a10
 #define OSEN_IE_VENDOR_TYPE 0x506f9a12
+#define NAN_IE_VENDOR_TYPE 0x506f9a13
+#define NAN_SDF_VENDOR_TYPE 0x506f9a13
+#define NAN_OUI_TYPE 0x13
 #define MBO_IE_VENDOR_TYPE 0x506f9a16
+#define NAN_NAF_VENDOR_TYPE 0x506f9a18
 #define MBO_OUI_TYPE 22
 #define OWE_IE_VENDOR_TYPE 0x506f9a1c
 #define OWE_OUI_TYPE 28
@@ -1882,8 +1924,9 @@
 #define WNM_BSS_TM_REQ_DISASSOC_IMMINENT BIT(2)
 #define WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED BIT(3)
 #define WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT BIT(4)
+#define WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT BIT(5)
 
-/* IEEE Std 802.11-2012 - Table 8-253 */
+/* IEEE Std 802.11-2020, Table 9-428 (BTM status code definitions) */
 enum bss_trans_mgmt_status_code {
 	WNM_BSS_TM_ACCEPT = 0,
 	WNM_BSS_TM_REJECT_UNSPECIFIED = 1,
@@ -1896,9 +1939,36 @@
 	WNM_BSS_TM_REJECT_LEAVING_ESS = 8
 };
 
+/* BSS transition management reasons
+ * IEEE Std 802.11-2020, Table 9-198 (Transition and Transition Query reasons)
+ */
+enum bss_trans_mgmt_reason {
+	WNM_TRANSITION_REASON_UNSPECIFIED = 0,
+	WNM_TRANSITION_REASON_EXCESSIVE_FRAME_LOSS = 1,
+	WNM_TRANSITION_REASON_EXCESSIVE_DELAY = 2,
+	WNM_TRANSITION_REASON_INSUFFICIENT_QOS = 3,
+	WNM_TRANSITION_REASON_FIRST_ESS_ASSOC = 4,
+	WNM_TRANSITION_REASON_LOAD_BALANCING = 5,
+	WNM_TRANSITION_REASON_BETTER_AP_FOUND = 6,
+	WNM_TRANSITION_REASON_DEAUTH_FROM_PREV_AP = 7,
+	WNM_TRANSITION_REASON_AP_FAILED_EAP = 8,
+	WNM_TRANSITION_REASON_AP_FAILED_4WAY_HS = 9,
+	WNM_TRANSITION_REASON_RX_TOO_MANY_REPLAYS = 10,
+	WNM_TRANSITION_REASON_RX_TOO_MANY_MIC_FAILURES = 11,
+	WNM_TRANSITION_REASON_EXCEEDED_MAX_RETRANS = 12,
+	WNM_TRANSITION_REASON_RX_TOO_MANY_BC_DISASSOC = 13,
+	WNM_TRANSITION_REASON_RX_TOO_MANY_BC_DEAUTH = 14,
+	WNM_TRANSITION_REASON_PREV_TRANSITION_FAILED = 15,
+	WNM_TRANSITION_REASON_LOW_RSSI = 16,
+	WNM_TRANSITION_REASON_ROAM_FROM_NON_802_11 = 17,
+	WNM_TRANSITION_REASON_TRANSITION_DUE_TO_BTM_REQ = 18,
+	WNM_TRANSITION_REASON_PREF_TRANSITION_CANDIDATE_LIST = 19,
+	WNM_TRANSITION_REASON_LEAVING_ESS = 20,
+};
+
 /*
- * IEEE P802.11-REVmc/D5.0 Table 9-150 - Optional subelement IDs for
- * neighbor report
+ * IEEE Std 802.11-2020, Table 9-173 - Optional subelement IDs for
+ * Neighbor Report
  */
 #define WNM_NEIGHBOR_TSF                         1
 #define WNM_NEIGHBOR_CONDENSED_COUNTRY_STRING    2
@@ -1925,7 +1995,7 @@
 	QOS_QOS_MAP_CONFIG = 4,
 };
 
-/* IEEE Std 802.11-2012, 8.4.2.62 20/40 BSS Coexistence element */
+/* IEEE Std 802.11-2020, 9.4.2.59 (20/40 BSS Coexistence element) */
 #define WLAN_20_40_BSS_COEX_INFO_REQ            BIT(0)
 #define WLAN_20_40_BSS_COEX_40MHZ_INTOL         BIT(1)
 #define WLAN_20_40_BSS_COEX_20MHZ_WIDTH_REQ     BIT(2)
@@ -1973,7 +2043,7 @@
 	WNM_SLEEP_SUBELEM_BIGTK = 2,
 };
 
-/* WNM notification type (IEEE P802.11-REVmd/D3.0, Table 9-430) */
+/* WNM notification type (IEEE Std 802.11-2020, Table 9-431) */
 enum wnm_notification_Type {
 	WNM_NOTIF_TYPE_FIRMWARE_UPDATE = 0,
 	WNM_NOTIF_TYPE_BEACON_PROTECTION_FAILURE = 2,
@@ -2049,7 +2119,7 @@
 
 #define RRM_CAPABILITIES_IE_LEN 5
 
-/* IEEE Std 802.11-2012, 8.5.7.4 - Link Measurement Request frame format */
+/* IEEE Std 802.11-2020, 9.6.6.4 - Link Measurement Request frame format */
 struct rrm_link_measurement_request {
 	u8 dialog_token;
 	s8 tx_power;
@@ -2057,7 +2127,7 @@
 	u8 variable[0];
 } STRUCT_PACKED;
 
-/* IEEE Std 802.11-2012, 8.5.7.5 - Link Measurement Report frame format */
+/* IEEE Std 802.11-2020, 9.6.6.5 - Link Measurement Report frame format */
 struct rrm_link_measurement_report {
 	u8 dialog_token;
 	struct tpc_report tpc;
@@ -2068,7 +2138,7 @@
 	u8 variable[0];
 } STRUCT_PACKED;
 
-/* IEEE Std 802.11-2016, 9.4.2.21 - Measurement Request element */
+/* IEEE Std 802.11-2020, 9.4.2.20 - Measurement Request element */
 struct rrm_measurement_request_element {
 	u8 eid; /* Element ID */
 	u8 len; /* Length */
@@ -2078,14 +2148,14 @@
 	u8 variable[0]; /* Measurement Request */
 } STRUCT_PACKED;
 
-/* IEEE Std 802.11-2016, Figure 9-148 - Measurement Request Mode field */
+/* IEEE Std 802.11-2020, Figure 9-180 - Measurement Request Mode field format */
 #define MEASUREMENT_REQUEST_MODE_PARALLEL BIT(0)
 #define MEASUREMENT_REQUEST_MODE_ENABLE BIT(1)
 #define MEASUREMENT_REQUEST_MODE_REQUEST BIT(2)
 #define MEASUREMENT_REQUEST_MODE_REPORT BIT(3)
 #define MEASUREMENT_REQUEST_MODE_DURATION_MANDATORY BIT(4)
 
-/* IEEE Std 802.11-2016, 9.4.2.21.7 - Beacon request */
+/* IEEE Std 802.11-2020, 9.4.2.20.7 - Beacon request */
 struct rrm_measurement_beacon_request {
 	u8 oper_class; /* Operating Class */
 	u8 channel; /* Channel Number */
@@ -2097,7 +2167,7 @@
 } STRUCT_PACKED;
 
 /*
- * IEEE Std 802.11-2016, Table 9-87 - Measurement Mode definitions for Beacon
+ * IEEE Std 802.11-2020, Table 9-103 - Measurement Mode definitions for Beacon
  * request
  */
 enum beacon_report_mode {
@@ -2106,20 +2176,19 @@
 	BEACON_REPORT_MODE_TABLE = 2,
 };
 
-/* IEEE Std 802.11-2016, Table 9-88 - Beacon Request subelement IDs */
-/* IEEE P802.11-REVmd/D2.0, Table 9-106 - Optional subelement IDs for
+/* IEEE Std 802.11-2020, Table 9-104 - Optional subelement IDs for
  * Beacon request */
 #define WLAN_BEACON_REQUEST_SUBELEM_SSID	0
 #define WLAN_BEACON_REQUEST_SUBELEM_INFO	1 /* Beacon Reporting */
 #define WLAN_BEACON_REQUEST_SUBELEM_DETAIL	2 /* Reporting Detail */
 #define WLAN_BEACON_REQUEST_SUBELEM_REQUEST	10
+#define WLAN_BEACON_REQUEST_SUBELEM_EXT_REQUEST	11
 #define WLAN_BEACON_REQUEST_SUBELEM_AP_CHANNEL	51 /* AP Channel Report */
+#define WLAN_BEACON_REQUEST_SUBELEM_WIDE_BW_CS	163
 #define WLAN_BEACON_REQUEST_SUBELEM_LAST_INDICATION	164
 #define WLAN_BEACON_REQUEST_SUBELEM_VENDOR	221
 
-/*
- * IEEE Std 802.11-2016, Table 9-90 - Reporting Detail values
- */
+/* IEEE Std 802.11-2020, Table 9-106 - Reporting Detail values */
 enum beacon_report_detail {
 	/* No fixed-length fields or elements */
 	BEACON_REPORT_DETAIL_NONE = 0,
@@ -2131,7 +2200,7 @@
 	BEACON_REPORT_DETAIL_ALL_FIELDS_AND_ELEMENTS = 2,
 };
 
-/* IEEE Std 802.11-2016, 9.4.2.22 - Measurement Report element */
+/* IEEE Std 802.11-2020, 9.4.2.21 - Measurement Report element */
 struct rrm_measurement_report_element {
 	u8 eid; /* Element ID */
 	u8 len; /* Length */
@@ -2141,13 +2210,13 @@
 	u8 variable[0]; /* Measurement Report */
 } STRUCT_PACKED;
 
-/* IEEE Std 802.11-2016, Figure 9-192 - Measurement Report Mode field */
+/* IEEE Std 802.11-2020, Figure 9-223 - Measurement Report Mode field */
 #define MEASUREMENT_REPORT_MODE_ACCEPT 0
 #define MEASUREMENT_REPORT_MODE_REJECT_LATE BIT(0)
 #define MEASUREMENT_REPORT_MODE_REJECT_INCAPABLE BIT(1)
 #define MEASUREMENT_REPORT_MODE_REJECT_REFUSED BIT(2)
 
-/* IEEE Std 802.11-2016, 9.4.2.22.7 - Beacon report */
+/* IEEE Std 802.11-2020, 9.4.2.21.7 - Beacon report */
 struct rrm_measurement_beacon_report {
 	u8 op_class; /* Operating Class */
 	u8 channel; /* Channel Number */
@@ -2163,23 +2232,23 @@
 	u8 variable[0]; /* Optional Subelements */
 } STRUCT_PACKED;
 
-/* IEEE Std 802.11-2016, Table 9-112 - Beacon report Subelement IDs */
-/* IEEE P802.11-REVmd/D2.0, Table 9-130 - Optional subelement IDs for
+/* IEEE Std 802.11-2020, Table 9-130 - Optional subelement IDs for
  * Beacon report */
 #define WLAN_BEACON_REPORT_SUBELEM_FRAME_BODY	1
 #define WLAN_BEACON_REPORT_SUBELEM_FRAME_BODY_FRAGMENT_ID	2
+#define WLAN_BEACON_REPORT_SUBELEM_WID_BW_CS	163
 #define WLAN_BEACON_REPORT_SUBELEM_LAST_INDICATION	164
 #define WLAN_BEACON_REPORT_SUBELEM_VENDOR	221
 
-/* IEEE P802.11-REVmd/D2.0, Table 9-232 - Data field format of the
- * Reported Frame Body Fragment ID subelement */
+/* IEEE Std 802.11-2020, Table 9-232 - Data field format (of the
+ * Reported Frame Body Fragment ID subelement) */
 #define REPORTED_FRAME_BODY_SUBELEM_LEN		4
 #define REPORTED_FRAME_BODY_MORE_FRAGMENTS	BIT(7)
 
-/* IEEE P802.11-REVmd/D2.0, 9.4.2.21.7 - Beacon report  */
+/* IEEE Std 802.11-2020, 9.4.2.21.7 - Beacon report  */
 #define BEACON_REPORT_LAST_INDICATION_SUBELEM_LEN	3
 
-/* IEEE Std 802.11ad-2012 - Multi-band element */
+/* IEEE Std 802.11-2020, 9.4.2.138 - Multi-band element */
 struct multi_band_ie {
 	u8 eid; /* WLAN_EID_MULTI_BAND */
 	u8 len;
@@ -2225,7 +2294,7 @@
 #define MB_CONNECTION_CAPABILITY_TDLS ((u8) (BIT(3)))
 #define MB_CONNECTION_CAPABILITY_IBSS ((u8) (BIT(4)))
 
-/* IEEE Std 802.11ad-2014 - FST Action field */
+/* IEEE Std 802.11-2020, Table 9-479 - FST Action field values */
 enum fst_action {
 	FST_ACTION_SETUP_REQUEST = 0,
 	FST_ACTION_SETUP_RESPONSE = 1,
@@ -2235,21 +2304,23 @@
 	FST_ACTION_ON_CHANNEL_TUNNEL = 5,
 };
 
-/* IEEE Std 802.11ac-2013, Annex C - dot11PHYType */
+/* IEEE Std 802.11-2020, Annex C - dot11PHYType */
 enum phy_type {
 	PHY_TYPE_UNSPECIFIED = 0,
-	PHY_TYPE_FHSS = 1,
 	PHY_TYPE_DSSS = 2,
-	PHY_TYPE_IRBASEBAND = 3,
 	PHY_TYPE_OFDM = 4,
 	PHY_TYPE_HRDSSS = 5,
 	PHY_TYPE_ERP = 6,
 	PHY_TYPE_HT = 7,
 	PHY_TYPE_DMG = 8,
 	PHY_TYPE_VHT = 9,
+	PHY_TYPE_TVHT = 10,
+	PHY_TYPE_S1G = 11,
+	PHY_TYPE_CDMG = 12,
+	PHY_TYPE_CMMG = 13,
 };
 
-/* IEEE P802.11-REVmd/D3.0, 9.4.2.36 - Neighbor Report element */
+/* IEEE Std 802.11-2020, 9.4.2.36 - Neighbor Report element */
 /* BSSID Information Field */
 #define NEI_REP_BSSID_INFO_AP_NOT_REACH BIT(0)
 #define NEI_REP_BSSID_INFO_AP_UNKNOWN_REACH BIT(1)
@@ -2270,7 +2341,7 @@
 #define NEI_REP_BSSID_INFO_EHT BIT(21)
 
 /*
- * IEEE P802.11-REVmc/D5.0 Table 9-152 - HT/VHT Operation Information
+ * IEEE Std 802.11-2020, Table 9-175 - HT/VHT Operation Information
  * subfields.
  * Note: These definitions are not the same as other CHANWIDTH_*.
  */
@@ -2434,16 +2505,31 @@
 #define HE_OPERATION_6GHZ_OPER_INFO_LEN 5
 
 /**
- * enum he_6ghz_ap_type - Allowed Access Point types for 6 GHz Band
+ * enum he_reg_info_6ghz_ap_type - Allowed Access Point types for 6 GHz Band
  *
- * IEEE Std 802.11ax-2021, Table E-12 (Regulatory Info subfield encoding in the
- * United States)
+ * IEEE P802.11-REVme/D4.0, Table E-12 (Regulatory Info subfield encoding)
  */
-enum he_6ghz_ap_type {
-	HE_6GHZ_INDOOR_AP		= 0,
-	HE_6GHZ_STANDARD_POWER_AP	= 1,
+enum he_reg_info_6ghz_ap_type {
+	HE_REG_INFO_6GHZ_AP_TYPE_INDOOR         = 0,
+	HE_REG_INFO_6GHZ_AP_TYPE_SP		= 1,
+	HE_REG_INFO_6GHZ_AP_TYPE_VLP		= 2,
+	HE_REG_INFO_6GHZ_AP_TYPE_INDOOR_ENABLED	= 3,
+	HE_REG_INFO_6GHZ_AP_TYPE_INDOOR_SP	= 4,
+	HE_REG_INFO_6GHZ_AP_TYPE_MAX = HE_REG_INFO_6GHZ_AP_TYPE_INDOOR_SP,
 };
 
+static inline bool he_reg_is_indoor(enum he_reg_info_6ghz_ap_type type)
+{
+	return type == HE_REG_INFO_6GHZ_AP_TYPE_INDOOR ||
+		type == HE_REG_INFO_6GHZ_AP_TYPE_INDOOR_SP;
+}
+
+static inline bool he_reg_is_sp(enum he_reg_info_6ghz_ap_type type)
+{
+	return type == HE_REG_INFO_6GHZ_AP_TYPE_SP ||
+		type == HE_REG_INFO_6GHZ_AP_TYPE_INDOOR_SP;
+}
+
 /* Spatial Reuse defines */
 #define SPATIAL_REUSE_SRP_DISALLOWED		BIT(0)
 #define SPATIAL_REUSE_NON_SRG_OBSS_PD_SR_DISALLOWED	BIT(1)
@@ -2511,7 +2597,18 @@
 #define RNR_BSS_PARAM_MEMBER_CO_LOCATED_ESS         BIT(4)
 #define RNR_BSS_PARAM_UNSOLIC_PROBE_RESP_ACTIVE     BIT(5)
 #define RNR_BSS_PARAM_CO_LOCATED                    BIT(6)
-#define RNR_20_MHZ_PSD_MAX_TXPOWER                  255 /* dBm */
+/* Maximum transmit power in Y/2 dBm (-127..126); 127 indicates no maximum
+ * transmit power is specified for the corresponding 20 MHz channel. */
+#define RNR_20_MHZ_PSD_MAX_TXPOWER                  127
+
+/* IEEE P802.11be/D5.0, Figure 9-704c - MLD Parameters subfield format */
+/* B0..B7: AP MLD ID */
+/* B8..B11: Link ID */
+/* B12..B19: BSS Parameters Change Count */
+/* B20: All Updates Included */
+#define RNR_TBTT_INFO_MLD_PARAM2_ALL_UPDATE_INC 0x10
+/* B21: Disabled Link Indication */
+#define RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED  0x20
 
 /* IEEE P802.11be/D2.3, 9.4.2.311 - EHT Operation element */
 
@@ -2725,6 +2822,17 @@
 #define EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK             0x0400
 #define EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK    0x0800
 
+/* IEEE P802.11be/D4.1, Figure 9-1001x - STA Control field format for the
+ * Reconfiguration Multi-Link element */
+#define EHT_PER_STA_RECONF_CTRL_LINK_ID_MSK        0x000f
+#define EHT_PER_STA_RECONF_CTRL_COMPLETE_PROFILE   0x0010
+#define EHT_PER_STA_RECONF_CTRL_MAC_ADDR           0x0020
+#define EHT_PER_STA_RECONF_CTRL_AP_REMOVAL_TIMER   0x0040
+#define EHT_PER_STA_RECONF_CTRL_OP_UPDATE_TYPE_MSK 0x0780
+#define EHT_PER_STA_RECONF_CTRL_OP_PARAMS          0x0800
+#define EHT_PER_STA_RECONF_CTRL_NSTR_BITMAP_SIZE   0x1000
+#define EHT_PER_STA_RECONF_CTRL_NSTR_INDIC_BITMAP  0x2000
+
 /* IEEE P802.11be/D2.0, 9.4.2.312.2.4 - Per-STA Profile subelement format */
 struct ieee80211_eht_per_sta_profile {
 	le16 sta_control;
@@ -2733,9 +2841,11 @@
 	u8 variable[];
 } STRUCT_PACKED;
 
-/* IEEE P802.11be/D2.0, 9.4.2.312.3 - Probe Request Multi-Link element */
+/* IEEE P802.11be/D4.0, 9.4.2.312.3 - Probe Request Multi-Link element
+ * Presence Bitmap field is B4..B15 of the Multi-Link Control field, i.e.,
+ * B0 in the presence bitmap is B4 in the control field. */
 
-#define EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID 0x0001
+#define EHT_ML_PRES_BM_PROBE_REQ_AP_MLD_ID 0x0010
 
 struct eht_ml_probe_req_common_info {
 	u8 len;
@@ -2869,8 +2979,8 @@
 };
 
 /*
- * IEEE Std 802.11ai-2016, 9.6.8.36 FILS Discovery frame format,
- * Figure 9-687b - FILS Discovery Frame Control subfield format
+ * IEEE Std 802.11-2020, 9.6.7.36 FILS Discovery frame format,
+ * Figure 9-900 - FILS Discovery Frame Control subfield format
  */
 #define FD_FRAME_CTL_CAP_PRESENT			((u16) BIT(5))
 #define FD_FRAME_CTL_SHORT_SSID_PRESENT			((u16) BIT(6))
@@ -2883,8 +2993,8 @@
 #define FD_FRAME_CTL_MD_PRESENT				((u16) BIT(13))
 
 /*
- * IEEE Std 802.11ai-2016, 9.6.8.36 FILS Discovery frame format,
- * Figure 9-687c - FD Capability subfield format
+ * IEEE Std 802.11-2020, 9.6.7.36 FILS Discovery frame format,
+ * Figure 9-901 - FD Capability subfield format
  */
 #define FD_CAP_ESS					BIT(0)
 #define FD_CAP_PRIVACY					BIT(1)
@@ -2973,6 +3083,15 @@
 	u8 data[0];
 } STRUCT_PACKED;
 
+/* S1G Beacon Compatibility element */
+struct ieee80211_s1g_beacon_compat {
+	u8 element_id;
+	u8 length;
+	le16 compat_info;
+	le16 beacon_interval;
+	le32 tsf_completion;
+} STRUCT_PACKED;
+
 #ifdef _MSC_VER
 #pragma pack(pop)
 #endif /* _MSC_VER */
diff --git a/src/common/nan.h b/src/common/nan.h
new file mode 100644
index 0000000..19ab746
--- /dev/null
+++ b/src/common/nan.h
@@ -0,0 +1,98 @@
+/*
+ * NAN (Wi-Fi Aware) definitions
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef NAN_H
+#define NAN_H
+
+enum nan_attr_id {
+	NAN_ATTR_MASTER_INDICATION = 0x00,
+	NAN_ATTR_CLUSTER = 0x01,
+	NAN_ATTR_NAN_ATTR_SERVICE_ID_LIST = 0x02,
+	NAN_ATTR_SDA = 0x03, /* Service Descriptor attribute */
+	NAN_ATTR_CONN_CAPA = 0x04,
+	NAN_ATTR_WLAN_INFRA = 0x05,
+	NAN_ATTR_P2P_OPER = 0x06,
+	NAN_ATTR_IBSS = 0x07,
+	NAN_ATTR_MESH = 0x08,
+	NAN_ATTR_FURTHER_NAN_SD = 0x09,
+	NAN_ATTR_FURTHER_AVAIL_MAP = 0x0A,
+	NAN_ATTR_COUNTRY_CODE = 0x0B,
+	NAN_ATTR_RANGIN = 0x0C,
+	NAN_ATTR_CLUSTER_DISCOVERY = 0x0D,
+	NAN_ATTR_SDEA = 0x0E, /* Service Descriptor Extension attribute */
+	NAN_ATTR_DEVICE_CAPABILITY = 0x0F,
+	NAN_ATTR_NDP = 0x10,
+	NAN_ATTR_NAN_AVAILABILITY = 0x12,
+	NAN_ATTR_NDC = 0x13,
+	NAN_ATTR_NDL = 0x14,
+	NAN_ATTR_NDL_QOS = 0x15,
+	NAN_ATTR_UNALIGNED_SCHEDULE = 0x17,
+	NAN_ATTR_RANGING_INFO = 0x1A,
+	NAN_ATTR_RANGING_SETUP = 0x1B,
+	NAN_ATTR_FTM_RANGING_REPORT = 0x1C,
+	NAN_ATTR_ELEM_CONTAINER = 0x1D,
+	NAN_ATTR_EXT_WLAN_INFRA = 0x1E,
+	NAN_ATTR_EXT_P2P_OPER = 0x1FE,
+	NAN_ATTR_EXT_IBSS = 0x20,
+	NAN_ATTR_EXT_MESH = 0x21,
+	NAN_ATTR_CSIA = 0x22, /* Cipher Suite Info attribute */
+	NAN_ATTR_SCIA = 0x23, /* Security Context Info attribute */
+	NAN_ATTR_SHARED_KEY_DESCR = 0x24,
+	NAN_ATTR_PUBLIC_AVAILABILITY = 0x27,
+	NAN_ATTR_SUBSC_SERVICE_ID_LIST = 0x28,
+	NAN_ATTR_NDP_EXT = 0x29,
+	NAN_ATTR_DCEA = 0x2A, /* Device Capability Extension attribute */
+	NAN_ATTR_NIRA = 0x2B, /* NAN Identity Resolution attribute */
+	NAN_ATTR_BPBA = 0x2C, /* NAN Pairing Bootstrapping attribute */
+	NAN_ATTR_S3 = 0x2D,
+	NAN_ATTR_TPEA = 0x2E, /* Transmit Power Envelope attribute */
+	NAN_ATTR_VENDOR_SPECIFIC = 0xDD,
+};
+
+/* Service Descriptor attribute (SDA) */
+
+/* Service Control field */
+#define NAN_SRV_CTRL_TYPE_MASK (BIT(0) | BIT(1))
+#define NAN_SRV_CTRL_MATCHING_FILTER BIT(2)
+#define NAN_SRV_CTRL_RESP_FILTER BIT(3)
+#define NAN_SRV_CTRL_SRV_INFO BIT(4)
+#define NAN_SRV_CTRL_DISCOVERY_RANGE_LIMITED BIT(5)
+#define NAN_SRV_CTRL_BINDING_BITMAP BIT(6)
+
+enum nan_service_control_type {
+	NAN_SRV_CTRL_PUBLISH = 0,
+	NAN_SRV_CTRL_SUBSCRIBE = 1,
+	NAN_SRV_CTRL_FOLLOW_UP = 2,
+};
+
+/* Service Descriptor Extension attribute (SDEA) */
+
+/* SDEA Control field */
+#define NAN_SDEA_CTRL_FSD_REQ BIT(0)
+#define NAN_SDEA_CTRL_FSD_GAS BIT(1)
+#define NAN_SDEA_CTRL_DATA_PATH_REQ BIT(2)
+#define NAN_SDEA_CTRL_DATA_PATH_TYPE BIT(3)
+#define NAN_SDEA_CTRL_QOS_REQ BIT(5)
+#define NAN_SDEA_CTRL_SECURITY_REQ BIT(6)
+#define NAN_SDEA_CTRL_RANGING_REQ BIT(7)
+#define NAN_SDEA_CTRL_RANGE_LIMIT BIT(8)
+#define NAN_SDEA_CTRL_SRV_UPD_INDIC BIT(9)
+#define NAN_SDEA_CTRL_GTK_REQ BIT(10)
+
+enum nan_service_protocol_type {
+	NAN_SRV_PROTO_BONJOUR = 1,
+	NAN_SRV_PROTO_GENERIC = 2,
+	NAN_SRV_PROTO_CSA_MATTER = 3,
+};
+
+#define NAN_ATTR_HDR_LEN 3
+#define NAN_SERVICE_ID_LEN 6
+
+#define NAN_USD_DEFAULT_FREQ 2437
+
+#endif /* NAN_H */
diff --git a/src/common/nan_de.c b/src/common/nan_de.c
new file mode 100644
index 0000000..e1999a0
--- /dev/null
+++ b/src/common/nan_de.c
@@ -0,0 +1,1389 @@
+/*
+ * NAN Discovery Engine
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "crypto/crypto.h"
+#include "crypto/sha256.h"
+#include "ieee802_11_defs.h"
+#include "nan.h"
+#include "nan_de.h"
+
+static const u8 nan_network_id[ETH_ALEN] =
+{ 0x51, 0x6f, 0x9a, 0x01, 0x00, 0x00 };
+static const u8 wildcard_bssid[ETH_ALEN] =
+{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
+
+enum nan_de_service_type {
+	NAN_DE_PUBLISH,
+	NAN_DE_SUBSCRIBE,
+};
+
+struct nan_de_service {
+	int id;
+	enum nan_de_service_type type;
+	char *service_name;
+	u8 service_id[NAN_SERVICE_ID_LEN];
+	struct nan_publish_params publish;
+	struct nan_subscribe_params subscribe;
+	enum nan_service_protocol_type srv_proto_type;
+	struct wpabuf *ssi;
+	struct wpabuf *elems;
+	struct os_reltime time_started;
+	struct os_reltime end_time;
+	struct os_reltime last_multicast;
+	struct os_reltime first_discovered;
+	struct os_reltime last_followup;
+	bool needs_fsd;
+	unsigned int freq;
+	unsigned int default_freq;
+	int *freq_list;
+
+	/* pauseState information for Publish function */
+	struct os_reltime pause_state_end;
+	u8 sel_peer_id;
+	u8 sel_peer_addr[ETH_ALEN];
+
+	/* Publish state - channel iteration */
+	bool in_multi_chan;
+	bool first_multi_chan;
+	int multi_chan_idx; /* index to freq_list[] */
+	struct os_reltime next_publish_state;
+	struct os_reltime next_publish_chan;
+	unsigned int next_publish_duration;
+};
+
+struct nan_de {
+	u8 nmi[ETH_ALEN];
+	bool ap;
+	struct nan_callbacks cb;
+
+	struct nan_de_service *service[NAN_DE_MAX_SERVICE];
+	unsigned int num_service;
+
+	int next_handle;
+
+	unsigned int ext_listen_freq;
+	unsigned int listen_freq;
+	unsigned int tx_wait_status_freq;
+	unsigned int tx_wait_end_freq;
+};
+
+
+struct nan_de * nan_de_init(const u8 *nmi, bool ap,
+			    const struct nan_callbacks *cb)
+{
+	struct nan_de *de;
+
+	de = os_zalloc(sizeof(*de));
+	if (!de)
+		return NULL;
+
+	os_memcpy(de->nmi, nmi, ETH_ALEN);
+	de->ap = ap;
+	os_memcpy(&de->cb, cb, sizeof(*cb));
+
+	return de;
+}
+
+
+static void nan_de_service_free(struct nan_de_service *srv)
+{
+	os_free(srv->service_name);
+	wpabuf_free(srv->ssi);
+	wpabuf_free(srv->elems);
+	os_free(srv->freq_list);
+	os_free(srv);
+}
+
+
+static void nan_de_service_deinit(struct nan_de *de, struct nan_de_service *srv,
+				  enum nan_de_reason reason)
+{
+	if (!srv)
+		return;
+	if (srv->type == NAN_DE_PUBLISH && de->cb.publish_terminated)
+		de->cb.publish_terminated(de->cb.ctx, srv->id, reason);
+	if (srv->type == NAN_DE_SUBSCRIBE && de->cb.subscribe_terminated)
+		de->cb.subscribe_terminated(de->cb.ctx, srv->id, reason);
+	nan_de_service_free(srv);
+}
+
+
+static void nan_de_clear_pending(struct nan_de *de)
+{
+	de->listen_freq = 0;
+	de->tx_wait_status_freq = 0;
+	de->tx_wait_end_freq = 0;
+}
+
+
+void nan_de_flush(struct nan_de *de)
+{
+	unsigned int i;
+
+	if (!de)
+		return;
+
+	for (i = 0; i < NAN_DE_MAX_SERVICE; i++) {
+		nan_de_service_deinit(de, de->service[i],
+				      NAN_DE_REASON_USER_REQUEST);
+		de->service[i] = NULL;
+	}
+
+	de->num_service = 0;
+	nan_de_clear_pending(de);
+}
+
+
+static void nan_de_pause_state(struct nan_de_service *srv, const u8 *peer_addr,
+			       u8 peer_id)
+{
+	wpa_printf(MSG_DEBUG, "NAN: Start pauseState");
+	os_get_reltime(&srv->pause_state_end);
+	srv->pause_state_end.sec += 60;
+	os_memcpy(srv->sel_peer_addr, peer_addr, ETH_ALEN);
+	srv->sel_peer_id = peer_id;
+}
+
+
+static void nan_de_unpause_state(struct nan_de_service *srv)
+{
+	wpa_printf(MSG_DEBUG, "NAN: Stop pauseState");
+	srv->pause_state_end.sec = 0;
+	srv->pause_state_end.usec = 0;
+	os_memset(srv->sel_peer_addr, 0, ETH_ALEN);
+	srv->sel_peer_id = 0;
+}
+
+
+static struct wpabuf * nan_de_alloc_sdf(size_t len)
+{
+	struct wpabuf *buf;
+
+	buf = wpabuf_alloc(2 + 4 + len);
+	if (buf) {
+		wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC);
+		wpabuf_put_u8(buf, WLAN_PA_VENDOR_SPECIFIC);
+		wpabuf_put_be32(buf, NAN_SDF_VENDOR_TYPE);
+	}
+
+	return buf;
+}
+
+
+static int nan_de_tx(struct nan_de *de, unsigned int freq,
+		     unsigned int wait_time,
+		     const u8 *dst, const u8 *src, const u8 *bssid,
+		     const struct wpabuf *buf)
+{
+	int res;
+
+	if (!de->cb.tx)
+		return -1;
+
+	res = de->cb.tx(de->cb.ctx, freq, wait_time, dst, src, bssid, buf);
+	if (res < 0)
+		return res;
+
+	de->tx_wait_status_freq = freq;
+	de->tx_wait_end_freq = wait_time ? freq : 0;
+
+	return res;
+}
+
+
+static void nan_de_tx_sdf(struct nan_de *de, struct nan_de_service *srv,
+			  unsigned int wait_time,
+			  enum nan_service_control_type type,
+			  const u8 *dst, u8 req_instance_id,
+			  const struct wpabuf *ssi)
+{
+	struct wpabuf *buf;
+	size_t len = 0, sda_len, sdea_len;
+	u8 ctrl = type;
+	u16 sdea_ctrl = 0;
+
+	/* Service Descriptor attribute */
+	sda_len = NAN_SERVICE_ID_LEN + 1 + 1 + 1;
+	len += NAN_ATTR_HDR_LEN + sda_len;
+
+	/* Service Descriptor Extension attribute */
+	sdea_len = 1 + 2;
+	if (ssi)
+		sdea_len += 2 + 4 + wpabuf_len(ssi);
+	len += NAN_ATTR_HDR_LEN + sdea_len;
+
+	/* Element Container attribute */
+	if (srv->elems)
+		len += NAN_ATTR_HDR_LEN + 1 + wpabuf_len(srv->elems);
+
+	buf = nan_de_alloc_sdf(len);
+	if (!buf)
+		return;
+
+	/* Service Descriptor attribute */
+	wpabuf_put_u8(buf, NAN_ATTR_SDA);
+	wpabuf_put_le16(buf, sda_len);
+	wpabuf_put_data(buf, srv->service_id, NAN_SERVICE_ID_LEN);
+	wpabuf_put_u8(buf, srv->id); /* Instance ID */
+	wpabuf_put_u8(buf, req_instance_id); /* Requestor Instance ID */
+	wpabuf_put_u8(buf, ctrl);
+
+	/* Service Descriptor Extension attribute */
+	if (srv->type == NAN_DE_PUBLISH || ssi) {
+		wpabuf_put_u8(buf, NAN_ATTR_SDEA);
+		wpabuf_put_le16(buf, sdea_len);
+		wpabuf_put_u8(buf, srv->id); /* Instance ID */
+		if (srv->type == NAN_DE_PUBLISH) {
+			if (srv->publish.fsd)
+				sdea_ctrl |= NAN_SDEA_CTRL_FSD_REQ;
+			if (srv->publish.fsd_gas)
+				sdea_ctrl |= NAN_SDEA_CTRL_FSD_GAS;
+		}
+		wpabuf_put_le16(buf, sdea_ctrl);
+		if (ssi) {
+			wpabuf_put_le16(buf, 4 + wpabuf_len(ssi));
+			wpabuf_put_be24(buf, OUI_WFA);
+			wpabuf_put_u8(buf, srv->srv_proto_type);
+			wpabuf_put_buf(buf, ssi);
+		}
+	}
+
+	/* Element Container attribute */
+	if (srv->elems) {
+		wpabuf_put_u8(buf, NAN_ATTR_ELEM_CONTAINER);
+		wpabuf_put_le16(buf, 1 + wpabuf_len(srv->elems));
+		wpabuf_put_u8(buf, 0); /* Map ID */
+		wpabuf_put_buf(buf, srv->elems);
+	}
+
+	/* Wi-Fi Aware specification v4.0 uses NAN Cluster ID as A3 for USD,
+	 * but there is no synchronization in USD as as such, no NAN Cluster
+	 * either. Use Wildcard BSSID instead. */
+	nan_de_tx(de, srv->freq, wait_time, dst, de->nmi, wildcard_bssid, buf);
+	wpabuf_free(buf);
+}
+
+
+static int nan_de_time_to_next_chan_change(struct nan_de_service *srv)
+{
+	struct os_reltime tmp, diff, now;
+
+	if (os_reltime_before(&srv->next_publish_state,
+			      &srv->next_publish_chan))
+		tmp = srv->next_publish_state;
+	else if (srv->in_multi_chan)
+		tmp = srv->next_publish_chan;
+	else
+		tmp = srv->next_publish_state;
+
+	os_get_reltime(&now);
+	os_reltime_sub(&tmp, &now, &diff);
+	return os_reltime_in_ms(&diff);
+}
+
+
+static void nan_de_set_publish_times(struct nan_de_service *srv)
+{
+	os_get_reltime(&srv->next_publish_state);
+	srv->next_publish_chan = srv->next_publish_state;
+	/* Swap single/multi channel state in N * 100 TU */
+	os_reltime_add_ms(&srv->next_publish_state,
+			  srv->next_publish_duration * 1024 / 1000);
+
+	/* Swap channel in multi channel state after 150 ms */
+	os_reltime_add_ms(&srv->next_publish_chan, 150);
+}
+
+
+static void nan_de_check_chan_change(struct nan_de_service *srv)
+{
+	if (srv->next_publish_duration) {
+		/* Update end times for the first operation of the publish
+		 * iteration */
+		nan_de_set_publish_times(srv);
+		srv->next_publish_duration = 0;
+	} else if (srv->in_multi_chan) {
+		if (!os_reltime_initialized(&srv->pause_state_end)) {
+			srv->multi_chan_idx++;
+			if (srv->freq_list[srv->multi_chan_idx] == 0)
+				srv->multi_chan_idx = 0;
+			srv->freq = srv->freq_list[srv->multi_chan_idx];
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Publish multi-channel change to %u MHz",
+				   srv->freq);
+		}
+		os_get_reltime(&srv->next_publish_chan);
+		os_reltime_add_ms(&srv->next_publish_chan, 150);
+	}
+}
+
+
+static void nan_de_tx_multicast(struct nan_de *de, struct nan_de_service *srv,
+				u8 req_instance_id)
+{
+	enum nan_service_control_type type;
+	unsigned int wait_time = 100;
+
+	if (srv->type == NAN_DE_PUBLISH) {
+		int ms;
+
+		type = NAN_SRV_CTRL_PUBLISH;
+
+		nan_de_check_chan_change(srv);
+		ms = nan_de_time_to_next_chan_change(srv);
+		if (ms < 100)
+			ms = 100;
+		wait_time = ms;
+	} else if (srv->type == NAN_DE_SUBSCRIBE) {
+		type = NAN_SRV_CTRL_SUBSCRIBE;
+	} else {
+		return;
+	}
+
+	nan_de_tx_sdf(de, srv, wait_time, type, nan_network_id,
+		      req_instance_id, srv->ssi);
+	os_get_reltime(&srv->last_multicast);
+}
+
+
+static void nan_de_add_srv(struct nan_de *de, struct nan_de_service *srv)
+{
+	int ttl;
+
+	os_get_reltime(&srv->time_started);
+	ttl = srv->type == NAN_DE_PUBLISH ? srv->publish.ttl :
+		srv->subscribe.ttl;
+	if (ttl) {
+		srv->end_time = srv->time_started;
+		srv->end_time.sec += ttl;
+	}
+
+	de->service[srv->id - 1] = srv;
+	de->num_service++;
+}
+
+
+static void nan_de_del_srv(struct nan_de *de, struct nan_de_service *srv,
+			   enum nan_de_reason reason)
+{
+	de->service[srv->id - 1] = NULL;
+	nan_de_service_deinit(de, srv, reason);
+	de->num_service--;
+	if (de->num_service == 0)
+		nan_de_clear_pending(de);
+}
+
+
+static bool nan_de_srv_expired(struct nan_de_service *srv,
+			       struct os_reltime *now)
+{
+	if (os_reltime_initialized(&srv->end_time))
+		return os_reltime_before(&srv->end_time, now);
+
+	if (srv->type == NAN_DE_PUBLISH) {
+		/* Time out after one transmission (and wait for FSD) */
+		if (!os_reltime_initialized(&srv->last_multicast))
+			return false;
+		if (!srv->publish.fsd)
+			return true;
+		if (os_reltime_initialized(&srv->last_followup) &&
+		    !os_reltime_expired(now, &srv->last_followup, 1))
+			return false;
+		if (os_reltime_expired(now, &srv->last_multicast, 1))
+			return true;
+	}
+
+	if (srv->type == NAN_DE_SUBSCRIBE) {
+		/* Time out after first DiscoveryResult event (and wait for
+		 * FSD) */
+		if (!os_reltime_initialized(&srv->first_discovered))
+			return false;
+		if (!srv->needs_fsd)
+			return true;
+		if (os_reltime_initialized(&srv->last_followup) &&
+		    !os_reltime_expired(now, &srv->last_followup, 1))
+			return false;
+		if (os_reltime_expired(now, &srv->first_discovered, 1))
+			return true;
+	}
+
+	return false;
+}
+
+
+static int nan_de_next_multicast(struct nan_de *de, struct nan_de_service *srv,
+				 struct os_reltime *now)
+{
+	unsigned int period;
+	struct os_reltime next, diff;
+
+	if (srv->type == NAN_DE_PUBLISH && !srv->publish.unsolicited)
+		return -1;
+	if (srv->type == NAN_DE_SUBSCRIBE && !srv->subscribe.active)
+		return -1;
+
+	if (!os_reltime_initialized(&srv->last_multicast))
+		return 0;
+
+	if (srv->type == NAN_DE_PUBLISH && srv->publish.ttl == 0)
+		return -1;
+
+	if (srv->type == NAN_DE_PUBLISH &&
+	    os_reltime_initialized(&srv->pause_state_end))
+		return -1;
+
+	period = srv->type == NAN_DE_PUBLISH ?
+		srv->publish.announcement_period :
+		srv->subscribe.query_period;
+	if (period == 0)
+		period = 100;
+	next = srv->last_multicast;
+	os_reltime_add_ms(&next, period);
+
+	if (srv->type == NAN_DE_PUBLISH) {
+		if (!de->tx_wait_end_freq && srv->publish.unsolicited &&
+		    os_reltime_before(&next, now))
+			return 0;
+		next = srv->next_publish_state;
+	}
+
+	if (os_reltime_before(&next, now))
+		return 0;
+
+	os_reltime_sub(&next, now, &diff);
+	return os_reltime_in_ms(&diff);
+}
+
+
+static int nan_de_srv_time_to_next(struct nan_de *de,
+				   struct nan_de_service *srv,
+				   struct os_reltime *now)
+{
+	struct os_reltime diff;
+	int next = -1, tmp;
+
+	if (os_reltime_initialized(&srv->end_time)) {
+		os_reltime_sub(&srv->end_time, now, &diff);
+		tmp = os_reltime_in_ms(&diff);
+		if (next == -1 || tmp < next)
+			next = tmp;
+	}
+
+	tmp = nan_de_next_multicast(de, srv, now);
+	if (tmp >= 0 && (next == -1 || tmp < next))
+		next = tmp;
+
+	if (srv->type == NAN_DE_PUBLISH &&
+	    os_reltime_initialized(&srv->last_multicast)) {
+		/* Time out after one transmission (and wait for FSD) */
+		tmp = srv->publish.fsd ? 1000 : 100;
+		if (next == -1 || tmp < next)
+			next = tmp;
+	}
+
+	if (srv->type == NAN_DE_SUBSCRIBE &&
+	    os_reltime_initialized(&srv->first_discovered)) {
+		/* Time out after first DiscoveryResult event (and wait for
+		 * FSD) */
+		tmp = srv->needs_fsd ? 1000 : 100;
+		if (next == -1 || tmp < next)
+			next = tmp;
+	}
+
+	if (os_reltime_initialized(&srv->next_publish_state)) {
+		os_reltime_sub(&srv->next_publish_state, now, &diff);
+		if (diff.sec < 0 || (diff.sec == 0 && diff.usec < 0))
+			tmp = 0;
+		else
+			tmp = os_reltime_in_ms(&diff);
+		if (next == -1 || tmp < next)
+			next = tmp;
+	}
+
+	return next;
+}
+
+
+static void nan_de_start_new_publish_state(struct nan_de_service *srv,
+					   bool force_single)
+{
+	unsigned int n;
+
+	if (force_single || !srv->freq_list || srv->freq_list[0] == 0)
+		srv->in_multi_chan = false;
+	else
+		srv->in_multi_chan = !srv->in_multi_chan;
+
+	/* Use hardcoded Nmin=5 and Nmax=10 and pick a random N from that range.
+	 * Use same values for M. */
+	n = 5 + os_random() % 5;
+	srv->next_publish_duration = n * 100;
+
+	nan_de_set_publish_times(srv);
+
+	if (os_reltime_initialized(&srv->pause_state_end))
+		return;
+
+	if (srv->in_multi_chan && srv->freq_list && srv->freq_list[0]) {
+		if (!srv->first_multi_chan)
+			srv->multi_chan_idx++;
+		if (srv->freq_list[srv->multi_chan_idx] == 0)
+			srv->multi_chan_idx = 0;
+		srv->first_multi_chan = false;
+		srv->freq = srv->freq_list[srv->multi_chan_idx];
+	} else {
+		srv->freq = srv->default_freq;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "NAN: Publish in %s channel state for %u TU; starting with %u MHz",
+		   srv->in_multi_chan ? "multi" : "single", n * 100, srv->freq);
+}
+
+
+static void nan_de_timer(void *eloop_ctx, void *timeout_ctx)
+{
+	struct nan_de *de = eloop_ctx;
+	unsigned int i;
+	int next = -1;
+	bool started = false;
+	struct os_reltime now;
+
+	os_get_reltime(&now);
+
+	for (i = 0; i < NAN_DE_MAX_SERVICE; i++) {
+		struct nan_de_service *srv = de->service[i];
+		int srv_next;
+
+		if (!srv)
+			continue;
+
+		if (nan_de_srv_expired(srv, &now)) {
+			wpa_printf(MSG_DEBUG, "NAN: Service id %d expired",
+				   srv->id);
+			nan_de_del_srv(de, srv, NAN_DE_REASON_TIMEOUT);
+			continue;
+		}
+
+		if (os_reltime_initialized(&srv->next_publish_state) &&
+		    os_reltime_before(&srv->next_publish_state, &now))
+			nan_de_start_new_publish_state(srv, false);
+
+		if (srv->type == NAN_DE_PUBLISH &&
+		    os_reltime_initialized(&srv->pause_state_end) &&
+		    (os_reltime_before(&srv->pause_state_end, &now) ||
+		     (srv->publish.fsd &&
+		      os_reltime_initialized(&srv->last_followup) &&
+		      os_reltime_expired(&now, &srv->last_followup, 1))))
+			nan_de_unpause_state(srv);
+
+		srv_next = nan_de_srv_time_to_next(de, srv, &now);
+		if (srv_next >= 0 && (next == -1 || srv_next < next))
+			next = srv_next;
+
+		if (srv_next == 0 && !started &&
+		    de->listen_freq == 0 && de->ext_listen_freq == 0 &&
+		    de->tx_wait_end_freq == 0 &&
+		    nan_de_next_multicast(de, srv, &now) == 0) {
+			started = true;
+			nan_de_tx_multicast(de, srv, 0);
+		}
+
+		if (!started && de->cb.listen &&
+		    de->listen_freq == 0 && de->ext_listen_freq == 0 &&
+		    de->tx_wait_end_freq == 0 &&
+		    ((srv->type == NAN_DE_PUBLISH &&
+		      !srv->publish.unsolicited && srv->publish.solicited) ||
+		     (srv->type == NAN_DE_SUBSCRIBE &&
+		      !srv->subscribe.active))) {
+			int duration = 1000;
+
+			if (srv->type == NAN_DE_PUBLISH) {
+				nan_de_check_chan_change(srv);
+				duration = nan_de_time_to_next_chan_change(srv);
+				if (duration < 150)
+					duration = 150;
+			}
+
+			started = true;
+			if (de->cb.listen(de->cb.ctx, srv->freq, duration) == 0)
+				de->listen_freq = srv->freq;
+		}
+
+	}
+
+	if (next < 0)
+		return;
+
+	if (next == 0)
+		next = 1;
+	wpa_printf(MSG_DEBUG, "NAN: Next timer in %u ms", next);
+	eloop_register_timeout(next / 1000, (next % 1000) * 1000, nan_de_timer,
+			       de, NULL);
+}
+
+
+static void nan_de_run_timer(struct nan_de *de)
+{
+	eloop_cancel_timeout(nan_de_timer, de, NULL);
+	eloop_register_timeout(0, 0, nan_de_timer, de, NULL);
+}
+
+
+void nan_de_deinit(struct nan_de *de)
+{
+	eloop_cancel_timeout(nan_de_timer, de, NULL);
+	nan_de_flush(de);
+	os_free(de);
+}
+
+
+void nan_de_listen_started(struct nan_de *de, unsigned int freq,
+			   unsigned int duration)
+{
+	if (freq != de->listen_freq)
+		de->ext_listen_freq = freq;
+}
+
+
+void nan_de_listen_ended(struct nan_de *de, unsigned int freq)
+{
+	if (freq == de->ext_listen_freq)
+		de->ext_listen_freq = 0;
+
+	if (freq == de->listen_freq) {
+		de->listen_freq = 0;
+		nan_de_run_timer(de);
+	}
+}
+
+
+void nan_de_tx_status(struct nan_de *de, unsigned int freq, const u8 *dst)
+{
+	if (freq == de->tx_wait_status_freq)
+		de->tx_wait_status_freq = 0;
+}
+
+
+void nan_de_tx_wait_ended(struct nan_de *de)
+{
+	de->tx_wait_end_freq = 0;
+	nan_de_run_timer(de);
+}
+
+
+static const u8 *
+nan_de_get_attr(const u8 *buf, size_t len, enum nan_attr_id id,
+		unsigned int skip)
+{
+	const u8 *pos = buf, *end = buf + len;
+
+	while (end - pos >= NAN_ATTR_HDR_LEN) {
+		const u8 *attr = pos;
+		u8 attr_id;
+		u16 attr_len;
+
+		attr_id = *pos++;
+		attr_len = WPA_GET_LE16(pos);
+		pos += 2;
+		if (attr_len > end - pos) {
+			wpa_printf(MSG_DEBUG,
+				   "NAN: Truncated attribute %u (len %u; left %zu)",
+				   attr_id, attr_len, end - pos);
+			break;
+		}
+
+		if (attr_id == id) {
+			if (skip == 0)
+				return attr;
+			skip--;
+		}
+
+		pos += attr_len;
+	}
+
+	return NULL;
+}
+
+
+static void nan_de_get_sdea(const u8 *buf, size_t len, u8 instance_id,
+			    u16 *sdea_control,
+			    enum nan_service_protocol_type *srv_proto_type,
+			    const u8 **ssi, size_t *ssi_len)
+{
+	unsigned int skip;
+	const u8 *sdea, *end;
+	u16 sdea_len;
+
+	for (skip = 0; ; skip++) {
+		sdea = nan_de_get_attr(buf, len, NAN_ATTR_SDEA, skip);
+		if (!sdea)
+			break;
+
+		sdea++;
+		sdea_len = WPA_GET_LE16(sdea);
+		sdea += 2;
+		if (sdea_len < 1 + 2)
+			continue;
+		end = sdea + sdea_len;
+
+		if (instance_id != *sdea++)
+			continue; /* Mismatching Instance ID */
+
+		*sdea_control = WPA_GET_LE16(sdea);
+		sdea += 2;
+
+		if (*sdea_control & NAN_SDEA_CTRL_RANGE_LIMIT) {
+			if (end - sdea < 4)
+				continue;
+			sdea += 4;
+		}
+
+		if (*sdea_control & NAN_SDEA_CTRL_SRV_UPD_INDIC) {
+			if (end - sdea < 1)
+				continue;
+			sdea++;
+		}
+
+		if (end - sdea >= 2) {
+			u16 srv_info_len;
+
+			srv_info_len = WPA_GET_LE16(sdea);
+			sdea += 2;
+
+			if (srv_info_len > end - sdea)
+				continue;
+
+			if (srv_info_len >= 4 &&
+			    WPA_GET_BE24(sdea) == OUI_WFA) {
+				*srv_proto_type = sdea[3];
+				*ssi = sdea + 4;
+				*ssi_len = srv_info_len - 4;
+			}
+		}
+	}
+}
+
+
+static void nan_de_rx_publish(struct nan_de *de, struct nan_de_service *srv,
+			      const u8 *peer_addr, u8 instance_id,
+			      u8 req_instance_id, u16 sdea_control,
+			      enum nan_service_protocol_type srv_proto_type,
+			      const u8 *ssi, size_t ssi_len)
+{
+	/* Subscribe function processing of a receive Publish message */
+	if (!os_reltime_initialized(&srv->first_discovered)) {
+		os_get_reltime(&srv->first_discovered);
+		srv->needs_fsd = sdea_control & NAN_SDEA_CTRL_FSD_REQ;
+		nan_de_run_timer(de);
+	}
+
+	if (srv->subscribe.active && req_instance_id == 0) {
+		/* Active subscriber replies with a Subscribe message if it
+		 * received a matching unsolicited Publish message. */
+		nan_de_tx_multicast(de, srv, instance_id);
+	}
+
+	if (!srv->subscribe.active && req_instance_id == 0) {
+		/* Passive subscriber replies with a Follow-up message without
+		 * Service Specific Info field if it received a matching
+		 * unsolicited Publish message. */
+		nan_de_transmit(de, srv->id, NULL, NULL, peer_addr,
+				instance_id);
+	}
+
+	if (de->cb.discovery_result)
+		de->cb.discovery_result(
+			de->cb.ctx, srv->id, srv_proto_type,
+			ssi, ssi_len, instance_id,
+			peer_addr,
+			sdea_control & NAN_SDEA_CTRL_FSD_REQ,
+			sdea_control & NAN_SDEA_CTRL_FSD_GAS);
+}
+
+
+static bool nan_de_filter_match(struct nan_de_service *srv,
+				const u8 *matching_filter,
+				size_t matching_filter_len)
+{
+	const u8 *pos, *end;
+
+	/* Since we do not currently support matching_filter_rx values for the
+	 * local Publish function, any matching filter with at least one
+	 * <length,value> pair with length larger than zero implies a mismatch.
+	 */
+
+	if (!matching_filter)
+		return true;
+
+	pos = matching_filter;
+	end = matching_filter + matching_filter_len;
+
+	while (pos < end) {
+		u8 len;
+
+		len = *pos++;
+		if (len > end - pos)
+			break;
+		if (len) {
+			/* A non-empty Matching Filter entry: no match since
+			 * there is no local matching_filter_rx. */
+			return false;
+		}
+	}
+
+	return true;
+}
+
+
+static void nan_de_rx_subscribe(struct nan_de *de, struct nan_de_service *srv,
+				const u8 *peer_addr, u8 instance_id,
+				const u8 *matching_filter,
+				size_t matching_filter_len,
+				enum nan_service_protocol_type srv_proto_type,
+				const u8 *ssi, size_t ssi_len)
+{
+	struct wpabuf *buf;
+	size_t len = 0, sda_len, sdea_len;
+	u8 ctrl = 0;
+	u16 sdea_ctrl = 0;
+
+	/* Publish function processing of a receive Subscribe message */
+
+	if (!nan_de_filter_match(srv, matching_filter, matching_filter_len))
+		return;
+
+	if (!srv->publish.solicited)
+		return;
+
+	if (os_reltime_initialized(&srv->pause_state_end) &&
+	    (!ether_addr_equal(peer_addr, srv->sel_peer_addr) ||
+	     instance_id != srv->sel_peer_id)) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: In pauseState - ignore Subscribe message from another subscriber");
+		return;
+	}
+
+	/* Reply with a solicited Publish message */
+	/* Service Descriptor attribute */
+	sda_len = NAN_SERVICE_ID_LEN + 1 + 1 + 1;
+	len += NAN_ATTR_HDR_LEN + sda_len;
+
+	/* Service Descriptor Extension attribute */
+	sdea_len = 1 + 2;
+	if (srv->ssi)
+		sdea_len += 2 + 4 + wpabuf_len(srv->ssi);
+	len += NAN_ATTR_HDR_LEN + sdea_len;
+
+	/* Element Container attribute */
+	if (srv->elems)
+		len += NAN_ATTR_HDR_LEN + 1 + wpabuf_len(srv->elems);
+
+	buf = nan_de_alloc_sdf(len);
+	if (!buf)
+		return;
+
+	/* Service Descriptor attribute */
+	wpabuf_put_u8(buf, NAN_ATTR_SDA);
+	wpabuf_put_le16(buf, sda_len);
+	wpabuf_put_data(buf, srv->service_id, NAN_SERVICE_ID_LEN);
+	wpabuf_put_u8(buf, srv->id); /* Instance ID */
+	wpabuf_put_u8(buf, instance_id); /* Requestor Instance ID */
+	ctrl |= NAN_SRV_CTRL_PUBLISH;
+	wpabuf_put_u8(buf, ctrl);
+
+	/* Service Descriptor Extension attribute */
+	if (srv->type == NAN_DE_PUBLISH || srv->ssi) {
+		wpabuf_put_u8(buf, NAN_ATTR_SDEA);
+		wpabuf_put_le16(buf, sdea_len);
+		wpabuf_put_u8(buf, srv->id); /* Instance ID */
+		if (srv->type == NAN_DE_PUBLISH) {
+			if (srv->publish.fsd)
+				sdea_ctrl |= NAN_SDEA_CTRL_FSD_REQ;
+			if (srv->publish.fsd_gas)
+				sdea_ctrl |= NAN_SDEA_CTRL_FSD_GAS;
+		}
+		wpabuf_put_le16(buf, sdea_ctrl);
+		if (srv->ssi) {
+			wpabuf_put_le16(buf, 4 + wpabuf_len(srv->ssi));
+			wpabuf_put_be24(buf, OUI_WFA);
+			wpabuf_put_u8(buf, srv->srv_proto_type);
+			wpabuf_put_buf(buf, srv->ssi);
+		}
+	}
+
+	/* Element Container attribute */
+	if (srv->elems) {
+		wpabuf_put_u8(buf, NAN_ATTR_ELEM_CONTAINER);
+		wpabuf_put_le16(buf, 1 + wpabuf_len(srv->elems));
+		wpabuf_put_u8(buf, 0); /* Map ID */
+		wpabuf_put_buf(buf, srv->elems);
+	}
+
+	/* Wi-Fi Aware specification v4.0 uses NAN Cluster ID as A3 for USD,
+	 * but there is no synchronization in USD as as such, no NAN Cluster
+	 * either. Use Wildcard BSSID instead. */
+	nan_de_tx(de, srv->freq, 100,
+		  srv->publish.solicited_multicast ? nan_network_id : peer_addr,
+		  de->nmi, wildcard_bssid, buf);
+	wpabuf_free(buf);
+
+	nan_de_pause_state(srv, peer_addr, instance_id);
+
+	if (!srv->publish.disable_events && de->cb.replied)
+		de->cb.replied(de->cb.ctx, srv->id, peer_addr, instance_id,
+			       srv_proto_type, ssi, ssi_len);
+}
+
+
+static void nan_de_rx_follow_up(struct nan_de *de, struct nan_de_service *srv,
+				const u8 *peer_addr, u8 instance_id,
+				const u8 *ssi, size_t ssi_len)
+{
+	/* Follow-up function processing of a receive Follow-up message for a
+	 * Subscribe or Publish instance */
+
+	if (srv->type == NAN_DE_PUBLISH &&
+	    os_reltime_initialized(&srv->pause_state_end) &&
+	    (!ether_addr_equal(peer_addr, srv->sel_peer_addr) ||
+	     instance_id != srv->sel_peer_id ||
+	     !ssi)) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: In pauseState - ignore Follow-up message from another subscriber or without ssi");
+		return;
+	}
+
+	os_get_reltime(&srv->last_followup);
+
+	if (srv->type == NAN_DE_PUBLISH && !ssi)
+		nan_de_pause_state(srv, peer_addr, instance_id);
+
+	if (de->cb.receive)
+		de->cb.receive(de->cb.ctx, srv->id, instance_id, ssi, ssi_len,
+			       peer_addr);
+}
+
+
+static void nan_de_rx_sda(struct nan_de *de, const u8 *peer_addr,
+			  unsigned int freq, const u8 *buf, size_t len,
+			  const u8 *sda, size_t sda_len)
+{
+	const u8 *service_id;
+	u8 instance_id, req_instance_id, ctrl;
+	u16 sdea_control = 0;
+	unsigned int i;
+	enum nan_service_control_type type = 0;
+	enum nan_service_protocol_type srv_proto_type = 0;
+	const u8 *ssi = NULL;
+	size_t ssi_len = 0;
+	bool first = true;
+	const u8 *end;
+	const u8 *matching_filter = NULL;
+	size_t matching_filter_len = 0;
+
+	if (sda_len < NAN_SERVICE_ID_LEN + 1 + 1 + 1)
+		return;
+	end = sda + sda_len;
+
+	service_id = sda;
+	sda += NAN_SERVICE_ID_LEN;
+	instance_id = *sda++;
+	req_instance_id = *sda++;
+	ctrl = *sda;
+	type = ctrl & NAN_SRV_CTRL_TYPE_MASK;
+	wpa_printf(MSG_DEBUG,
+		   "NAN: SDA - Service ID %02x%02x%02x%02x%02x%02x Instance ID %u Requestor Instance ID %u Service Control 0x%x (Service Control Type %u)",
+		   MAC2STR(service_id), instance_id, req_instance_id,
+		   ctrl, type);
+	if (type != NAN_SRV_CTRL_PUBLISH &&
+	    type != NAN_SRV_CTRL_SUBSCRIBE &&
+	    type != NAN_SRV_CTRL_FOLLOW_UP) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Discard SDF with unknown Service Control Type %u",
+			   type);
+		return;
+	}
+
+	if (ctrl & NAN_SRV_CTRL_BINDING_BITMAP) {
+		if (end - sda < 2)
+			return;
+		sda += 2;
+	}
+
+	if (ctrl & NAN_SRV_CTRL_MATCHING_FILTER) {
+		u8 flen;
+
+		if (end - sda < 1)
+			return;
+		flen = *sda++;
+		if (end - sda < flen)
+			return;
+		matching_filter = sda;
+		matching_filter_len = flen;
+		sda += flen;
+	}
+
+	if (ctrl & NAN_SRV_CTRL_RESP_FILTER) {
+		u8 flen;
+
+		if (end - sda < 1)
+			return;
+		flen = *sda++;
+		if (end - sda < flen)
+			return;
+		sda += flen;
+	}
+
+	if (ctrl & NAN_SRV_CTRL_SRV_INFO) {
+		u8 flen;
+
+		if (end - sda < 1)
+			return;
+		flen = *sda++;
+		if (end - sda < flen)
+			return;
+		if (flen >= 4 && WPA_GET_BE24(sda) == OUI_WFA) {
+			srv_proto_type = sda[3];
+			ssi = sda + 4;
+			ssi_len = flen - 4;
+			wpa_printf(MSG_DEBUG, "NAN: Service Protocol Type %d",
+				   srv_proto_type);
+			wpa_hexdump(MSG_MSGDUMP, "NAN: ssi", ssi, ssi_len);
+		}
+		sda += flen;
+	}
+
+	for (i = 0; i < NAN_DE_MAX_SERVICE; i++) {
+		struct nan_de_service *srv = de->service[i];
+
+		if (!srv)
+			continue;
+		if (os_memcmp(srv->service_id, service_id,
+			      NAN_SERVICE_ID_LEN) != 0)
+			continue;
+		if (type == NAN_SRV_CTRL_PUBLISH) {
+			if (srv->type == NAN_DE_PUBLISH)
+				continue;
+			if (req_instance_id && srv->id != req_instance_id)
+				continue;
+		}
+		if (type == NAN_SRV_CTRL_SUBSCRIBE &&
+		    srv->type == NAN_DE_SUBSCRIBE)
+			continue;
+		wpa_printf(MSG_DEBUG, "NAN: Received SDF matches service ID %u",
+			   i + 1);
+
+		if (first) {
+			first = false;
+			nan_de_get_sdea(buf, len, instance_id, &sdea_control,
+					&srv_proto_type, &ssi, &ssi_len);
+
+			if (ssi) {
+				wpa_printf(MSG_DEBUG,
+					   "NAN: Service Protocol Type %d",
+					   srv_proto_type);
+				wpa_hexdump(MSG_MSGDUMP, "NAN: ssi",
+					    ssi, ssi_len);
+			}
+		}
+
+		switch (type) {
+		case NAN_SRV_CTRL_PUBLISH:
+			nan_de_rx_publish(de, srv, peer_addr, instance_id,
+					  req_instance_id,
+					  sdea_control, srv_proto_type,
+					  ssi, ssi_len);
+			break;
+		case NAN_SRV_CTRL_SUBSCRIBE:
+			nan_de_rx_subscribe(de, srv, peer_addr, instance_id,
+					    matching_filter,
+					    matching_filter_len,
+					    srv_proto_type,
+					    ssi, ssi_len);
+			break;
+		case NAN_SRV_CTRL_FOLLOW_UP:
+			nan_de_rx_follow_up(de, srv, peer_addr, instance_id,
+					    ssi, ssi_len);
+			break;
+		}
+	}
+}
+
+
+void nan_de_rx_sdf(struct nan_de *de, const u8 *peer_addr, unsigned int freq,
+		   const u8 *buf, size_t len)
+{
+	const u8 *sda;
+	u16 sda_len;
+	unsigned int skip;
+
+	if (!de->num_service)
+		return;
+
+	wpa_printf(MSG_DEBUG, "NAN: RX SDF from " MACSTR " freq=%u len=%zu",
+		   MAC2STR(peer_addr), freq, len);
+
+	wpa_hexdump(MSG_MSGDUMP, "NAN: SDF payload", buf, len);
+
+	for (skip = 0; ; skip++) {
+		sda = nan_de_get_attr(buf, len, NAN_ATTR_SDA, skip);
+		if (!sda)
+			break;
+
+		sda++;
+		sda_len = WPA_GET_LE16(sda);
+		sda += 2;
+		nan_de_rx_sda(de, peer_addr, freq, buf, len, sda, sda_len);
+	}
+}
+
+
+static int nan_de_get_handle(struct nan_de *de)
+{
+	int i = de->next_handle;
+
+	if (de->num_service >= NAN_DE_MAX_SERVICE)
+		goto fail;
+
+	do {
+		if (!de->service[i]) {
+			de->next_handle = (i + 1) % NAN_DE_MAX_SERVICE;
+			return i + 1;
+		}
+		i = (i + 1) % NAN_DE_MAX_SERVICE;
+	} while (i != de->next_handle);
+
+fail:
+	wpa_printf(MSG_DEBUG, "NAN: No more room for a new service");
+	return -1;
+}
+
+
+static int nan_de_derive_service_id(struct nan_de_service *srv)
+{
+	u8 hash[SHA256_MAC_LEN];
+	char *name, *pos;
+	int ret;
+	const u8 *addr[1];
+	size_t len[1];
+
+	name = os_strdup(srv->service_name);
+	if (!name)
+		return -1;
+	pos = name;
+	while (*pos) {
+		*pos = tolower(*pos);
+		pos++;
+	}
+
+	addr[0] = (u8 *) name;
+	len[0] = os_strlen(name);
+	ret = sha256_vector(1, addr, len, hash);
+	os_free(name);
+	if (ret == 0)
+		os_memcpy(srv->service_id, hash, NAN_SERVICE_ID_LEN);
+
+	return ret;
+}
+
+
+int nan_de_publish(struct nan_de *de, const char *service_name,
+		   enum nan_service_protocol_type srv_proto_type,
+		   const struct wpabuf *ssi, const struct wpabuf *elems,
+		   struct nan_publish_params *params)
+{
+	int publish_id;
+	struct nan_de_service *srv;
+
+	if (!service_name) {
+		wpa_printf(MSG_DEBUG, "NAN: Publish() - no service_name");
+		return -1;
+	}
+
+	publish_id = nan_de_get_handle(de);
+	if (publish_id < 1)
+		return -1;
+
+	srv = os_zalloc(sizeof(*srv));
+	if (!srv)
+		return -1;
+	srv->type = NAN_DE_PUBLISH;
+	srv->freq = srv->default_freq = params->freq;
+	srv->service_name = os_strdup(service_name);
+	if (!srv->service_name)
+		goto fail;
+	if (nan_de_derive_service_id(srv) < 0)
+		goto fail;
+	os_memcpy(&srv->publish, params, sizeof(*params));
+
+	if (params->freq_list) {
+		size_t len;
+
+		len = (int_array_len(params->freq_list) + 1) * sizeof(int);
+		srv->freq_list = os_memdup(params->freq_list, len);
+		if (!srv->freq_list)
+			goto fail;
+	}
+	srv->publish.freq_list = NULL;
+
+	srv->srv_proto_type = srv_proto_type;
+	if (ssi) {
+		srv->ssi = wpabuf_dup(ssi);
+		if (!srv->ssi)
+			goto fail;
+	}
+	if (elems) {
+		srv->elems = wpabuf_dup(elems);
+		if (!srv->elems)
+			goto fail;
+	}
+
+	/* Prepare for single and multi-channel states; starting with
+	 * single channel */
+	srv->first_multi_chan = true;
+	nan_de_start_new_publish_state(srv, true);
+
+	wpa_printf(MSG_DEBUG, "NAN: Assigned new publish handle %d for %s",
+		   publish_id, service_name);
+	srv->id = publish_id;
+	nan_de_add_srv(de, srv);
+	nan_de_run_timer(de);
+	return publish_id;
+fail:
+	nan_de_service_free(srv);
+	return -1;
+}
+
+
+void nan_de_cancel_publish(struct nan_de *de, int publish_id)
+{
+	struct nan_de_service *srv;
+
+	wpa_printf(MSG_DEBUG, "NAN: CancelPublish(publish_id=%d)", publish_id);
+
+	if (publish_id < 1 || publish_id > NAN_DE_MAX_SERVICE)
+		return;
+	srv = de->service[publish_id - 1];
+	if (!srv || srv->type != NAN_DE_PUBLISH)
+		return;
+	nan_de_del_srv(de, srv, NAN_DE_REASON_USER_REQUEST);
+}
+
+
+int nan_de_update_publish(struct nan_de *de, int publish_id,
+			  const struct wpabuf *ssi)
+{
+	struct nan_de_service *srv;
+
+	wpa_printf(MSG_DEBUG, "NAN: UpdatePublish(publish_id=%d)", publish_id);
+
+	if (publish_id < 1 || publish_id > NAN_DE_MAX_SERVICE)
+		return -1;
+	srv = de->service[publish_id - 1];
+	if (!srv || srv->type != NAN_DE_PUBLISH)
+		return -1;
+
+	wpabuf_free(srv->ssi);
+	srv->ssi = NULL;
+	if (!ssi)
+		return 0;
+	srv->ssi = wpabuf_dup(ssi);
+	if (!srv->ssi)
+		return -1;
+	return 0;
+}
+
+
+int nan_de_subscribe(struct nan_de *de, const char *service_name,
+		     enum nan_service_protocol_type srv_proto_type,
+		     const struct wpabuf *ssi, const struct wpabuf *elems,
+		     struct nan_subscribe_params *params)
+{
+	int subscribe_id;
+	struct nan_de_service *srv;
+
+	if (!service_name) {
+		wpa_printf(MSG_DEBUG, "NAN: Subscribe() - no service_name");
+		return -1;
+	}
+
+	subscribe_id = nan_de_get_handle(de);
+	if (subscribe_id < 1)
+		return -1;
+
+	srv = os_zalloc(sizeof(*srv));
+	if (!srv)
+		return -1;
+	srv->type = NAN_DE_SUBSCRIBE;
+	srv->freq = params->freq;
+	srv->service_name = os_strdup(service_name);
+	if (!srv->service_name)
+		goto fail;
+	if (nan_de_derive_service_id(srv) < 0)
+		goto fail;
+	os_memcpy(&srv->subscribe, params, sizeof(*params));
+	srv->srv_proto_type = srv_proto_type;
+	if (ssi) {
+		srv->ssi = wpabuf_dup(ssi);
+		if (!srv->ssi)
+			goto fail;
+	}
+	if (elems) {
+		srv->elems = wpabuf_dup(elems);
+		if (!srv->elems)
+			goto fail;
+	}
+
+	wpa_printf(MSG_DEBUG, "NAN: Assigned new subscribe handle %d for %s",
+		   subscribe_id, service_name);
+	srv->id = subscribe_id;
+	nan_de_add_srv(de, srv);
+	nan_de_run_timer(de);
+	return subscribe_id;
+fail:
+	nan_de_service_free(srv);
+	return -1;
+}
+
+
+void nan_de_cancel_subscribe(struct nan_de *de, int subscribe_id)
+{
+	struct nan_de_service *srv;
+
+	if (subscribe_id < 1 || subscribe_id > NAN_DE_MAX_SERVICE)
+		return;
+	srv = de->service[subscribe_id - 1];
+	if (!srv || srv->type != NAN_DE_SUBSCRIBE)
+		return;
+	nan_de_del_srv(de, srv, NAN_DE_REASON_USER_REQUEST);
+}
+
+
+int nan_de_transmit(struct nan_de *de, int handle,
+		    const struct wpabuf *ssi, const struct wpabuf *elems,
+		    const u8 *peer_addr, u8 req_instance_id)
+{
+	struct nan_de_service *srv;
+
+	if (handle < 1 || handle > NAN_DE_MAX_SERVICE)
+		return -1;
+
+	srv = de->service[handle - 1];
+	if (!srv)
+		return -1;
+
+	nan_de_tx_sdf(de, srv, 100, NAN_SRV_CTRL_FOLLOW_UP,
+		      peer_addr, req_instance_id, ssi);
+
+	os_get_reltime(&srv->last_followup);
+	return 0;
+}
diff --git a/src/common/nan_de.h b/src/common/nan_de.h
new file mode 100644
index 0000000..6223506
--- /dev/null
+++ b/src/common/nan_de.h
@@ -0,0 +1,145 @@
+/*
+ * NAN Discovery Engine
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef NAN_DE_H
+#define NAN_DE_H
+
+#include "nan.h"
+
+/* Maximum number of active local publish and subscribe instances */
+#ifndef NAN_DE_MAX_SERVICE
+#define NAN_DE_MAX_SERVICE 20
+#endif /* NAN_DE_MAX_SERVICE */
+
+struct nan_de;
+
+enum nan_de_reason {
+	NAN_DE_REASON_TIMEOUT,
+	NAN_DE_REASON_USER_REQUEST,
+	NAN_DE_REASON_FAILURE,
+};
+
+struct nan_callbacks {
+	void *ctx;
+
+	int (*tx)(void *ctx, unsigned int freq, unsigned int wait_time,
+		  const u8 *dst, const u8 *src, const u8 *bssid,
+		  const struct wpabuf *buf);
+	int (*listen)(void *ctx, unsigned int freq, unsigned int duration);
+
+	/* NAN DE Events */
+	void (*discovery_result)(void *ctx, int subscribe_id,
+				 enum nan_service_protocol_type srv_proto_type,
+				 const u8 *ssi, size_t ssi_len,
+				 int peer_publish_id,
+				 const u8 *peer_addr, bool fsd, bool fsd_gas);
+
+	void (*replied)(void *ctx, int publish_id, const u8 *peer_addr,
+			int peer_subscribe_id,
+			enum nan_service_protocol_type srv_proto_type,
+			const u8 *ssi, size_t ssi_len);
+
+	void (*publish_terminated)(void *ctx, int publish_id,
+				    enum nan_de_reason reason);
+
+	void (*subscribe_terminated)(void *ctx, int subscribe_id,
+				     enum nan_de_reason reason);
+
+	void (*receive)(void *ctx, int id, int peer_instance_id,
+			const u8 *ssi, size_t ssi_len,
+			const u8 *peer_addr);
+};
+
+struct nan_de * nan_de_init(const u8 *nmi, bool ap,
+			    const struct nan_callbacks *cb);
+void nan_de_flush(struct nan_de *de);
+void nan_de_deinit(struct nan_de *de);
+
+void nan_de_listen_started(struct nan_de *de, unsigned int freq,
+			   unsigned int duration);
+void nan_de_listen_ended(struct nan_de *de, unsigned int freq);
+void nan_de_tx_status(struct nan_de *de, unsigned int freq, const u8 *dst);
+void nan_de_tx_wait_ended(struct nan_de *de);
+
+void nan_de_rx_sdf(struct nan_de *de, const u8 *peer_addr, unsigned int freq,
+		   const u8 *buf, size_t len);
+
+struct nan_publish_params {
+	/* configuration_parameters */
+
+	/* Publish type */
+	bool unsolicited;
+	bool solicited;
+
+	/* Solicited transmission type */
+	bool solicited_multicast;
+
+	/* Time to live (in seconds); 0 = one TX only */
+	unsigned int ttl;
+
+	/* Event conditions */
+	bool disable_events;
+
+	/* Further Service Discovery flag */
+	bool fsd;
+
+	/* Further Service Discovery function */
+	bool fsd_gas;
+
+	/* Default frequency (defaultPublishChannel) */
+	unsigned int freq;
+
+	/* Multi-channel frequencies (publishChannelList) */
+	const int *freq_list;
+
+	/* Announcement period in ms; 0 = use default */
+	unsigned int announcement_period;
+};
+
+/* Returns -1 on failure or >0 publish_id */
+int nan_de_publish(struct nan_de *de, const char *service_name,
+		   enum nan_service_protocol_type srv_proto_type,
+		   const struct wpabuf *ssi, const struct wpabuf *elems,
+		   struct nan_publish_params *params);
+
+void nan_de_cancel_publish(struct nan_de *de, int publish_id);
+
+int nan_de_update_publish(struct nan_de *de, int publish_id,
+			  const struct wpabuf *ssi);
+
+struct nan_subscribe_params {
+	/* configuration_parameters */
+
+	/* Subscribe type */
+	bool active;
+
+	/* Time to live (in seconds); 0 = until first result */
+	unsigned int ttl;
+
+	/* Selected frequency */
+	unsigned int freq;
+
+	/* Query period in ms; 0 = use default */
+	unsigned int query_period;
+};
+
+/* Returns -1 on failure or >0 subscribe_id */
+int nan_de_subscribe(struct nan_de *de, const char *service_name,
+		     enum nan_service_protocol_type srv_proto_type,
+		     const struct wpabuf *ssi, const struct wpabuf *elems,
+		     struct nan_subscribe_params *params);
+
+void nan_de_cancel_subscribe(struct nan_de *de, int subscribe_id);
+
+/* handle = publish_id or subscribe_id
+ * req_instance_id = peer publish_id or subscribe_id */
+int nan_de_transmit(struct nan_de *de, int handle,
+		    const struct wpabuf *ssi, const struct wpabuf *elems,
+		    const u8 *peer_addr, u8 req_instance_id);
+
+#endif /* NAN_DE_H */
diff --git a/src/common/ptksa_cache.c b/src/common/ptksa_cache.c
index 3b5c0b8..c00f288 100644
--- a/src/common/ptksa_cache.c
+++ b/src/common/ptksa_cache.c
@@ -141,7 +141,7 @@
 		return NULL;
 
 	dl_list_for_each(e, &ptksa->ptksa, struct ptksa_cache_entry, list) {
-		if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) &&
+		if ((!addr || ether_addr_equal(e->addr, addr)) &&
 		    (cipher == WPA_CIPHER_NONE || cipher == e->cipher))
 			return e;
 	}
@@ -238,7 +238,7 @@
 
 	dl_list_for_each_safe(e, next, &ptksa->ptksa, struct ptksa_cache_entry,
 			      list) {
-		if ((!addr || os_memcmp(e->addr, addr, ETH_ALEN) == 0) &&
+		if ((!addr || ether_addr_equal(e->addr, addr)) &&
 		    (cipher == WPA_CIPHER_NONE || cipher == e->cipher)) {
 			wpa_printf(MSG_DEBUG,
 				   "Flush PTKSA cache entry for " MACSTR,
diff --git a/src/common/qca-vendor.h b/src/common/qca-vendor.h
index 7620f03..a5bbc78 100644
--- a/src/common/qca-vendor.h
+++ b/src/common/qca-vendor.h
@@ -1024,6 +1024,30 @@
  *
  *	The attributes used with this subcommand are defined in
  *	enum qca_wlan_vendor_attr_tx_latency.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_REGULATORY_TPC_INFO: Vendor command is used to
+ *	query transmit power information on STA interface from the driver for a
+ *	connected AP. The attributes included in response are defined in
+ *	enum qca_wlan_vendor_attr_tpc_links. In case of MLO STA, multiple links
+ *	TPC info may be returned. The information includes regulatory maximum
+ *	transmit power limit, AP local power constraint advertised from AP's
+ *	Beacon and Probe Response frames. For PSD power mode, the information
+ *	includes PSD power levels for each subchannel of operating bandwidth.
+ *	The information is driver calculated power limits based on the current
+ *	regulatory domain, AP local power constraint, and other IEs. The
+ *	information will be set to target. Target will decide the final TX power
+ *	based on this and chip specific power conformance test limits (CTL), and
+ *	SAR limits.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_FW_PAGE_FAULT_REPORT: Event indication from the
+ *	driver to user space which is carrying firmware page fault related
+ *	summary report. The attributes for this command are defined in
+ *	enum qca_wlan_vendor_attr_fw_page_fault_report.
+ *
+ * @QCA_NL80211_VENDOR_SUBCMD_DISASSOC_PEER: Event indication from the driver
+ *	to user space to disassociate with a peer based on the peer MAC address
+ *	provided. Specify the peer MAC address in
+ *	QCA_WLAN_VENDOR_ATTR_MAC_ADDR. For MLO, MLD MAC address is provided.
  */
 enum qca_nl80211_vendor_subcmds {
 	QCA_NL80211_VENDOR_SUBCMD_UNSPEC = 0,
@@ -1242,6 +1266,12 @@
 	/* 232 - reserved for QCA */
 	QCA_NL80211_VENDOR_SUBCMD_TX_LATENCY = 233,
 	/* 234 - reserved for QCA */
+	QCA_NL80211_VENDOR_SUBCMD_SDWF_PHY_OPS = 235,
+	QCA_NL80211_VENDOR_SUBCMD_SDWF_DEV_OPS = 236,
+	QCA_NL80211_VENDOR_SUBCMD_REGULATORY_TPC_INFO = 237,
+	QCA_NL80211_VENDOR_SUBCMD_FW_PAGE_FAULT_REPORT = 238,
+	QCA_NL80211_VENDOR_SUBCMD_FLOW_POLICY = 239,
+	QCA_NL80211_VENDOR_SUBCMD_DISASSOC_PEER = 240,
 };
 
 /* Compatibility defines for previously used subcmd names.
@@ -1795,6 +1825,11 @@
  * scoring. In case scan was performed on a partial set of channels configured
  * with this command within last QCA_WLAN_VENDOR_ATTR_ACS_LAST_SCAN_AGEOUT_TIME
  * (in ms), scan only the remaining channels.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_ACS_LINK_ID: Mandatory on AP MLD (u8).
+ * Used with command to configure ACS operation for a specific link affiliated
+ * to an AP MLD.
+ *
  */
 enum qca_wlan_vendor_attr_acs_offload {
 	QCA_WLAN_VENDOR_ATTR_ACS_CHANNEL_INVALID = 0,
@@ -1818,6 +1853,7 @@
 	QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP = 18,
 	QCA_WLAN_VENDOR_ATTR_ACS_EHT_ENABLED = 19,
 	QCA_WLAN_VENDOR_ATTR_ACS_LAST_SCAN_AGEOUT_TIME = 20,
+	QCA_WLAN_VENDOR_ATTR_ACS_LINK_ID = 21,
 
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_ACS_AFTER_LAST,
@@ -2179,7 +2215,8 @@
  * &enum qca_tsf_cmd.
  * @QCA_WLAN_VENDOR_ATTR_TSF_TIMER_VALUE: Optional (u64)
  * This attribute contains TSF timer value. This attribute is only available
- * in %QCA_TSF_GET or %QCA_TSF_SYNC_GET response.
+ * in %QCA_TSF_GET, %QCA_TSF_SYNC_GET or %QCA_TSF_SYNC_GET_CSA_TIMESTAMP
+ * response.
  * @QCA_WLAN_VENDOR_ATTR_TSF_SOC_TIMER_VALUE: Optional (u64)
  * This attribute contains SOC timer value at TSF capture. This attribute is
  * only available in %QCA_TSF_GET or %QCA_TSF_SYNC_GET response.
@@ -2223,6 +2260,12 @@
  * userspace can query the TSF and host time mapping via the %QCA_TSF_GET
  * command.
  * @QCA_TSF_SYNC_STOP: Stop periodic TSF sync feature.
+ * @QCA_TSF_SYNC_GET_CSA_TIMESTAMP: Get TSF timestamp when AP will move and
+ * starts beaconing on a new channel. The driver synchronously responds with the
+ * TSF value using attribute %QCA_WLAN_VENDOR_ATTR_TSF_TIMER_VALUE. Userspace
+ * gets the valid CSA TSF after receiving %NL80211_CMD_CH_SWITCH_STARTED_NOTIFY
+ * on the AP interface. This TSF can be sent via OOB mechanism to connected
+ * clients.
  */
 enum qca_tsf_cmd {
 	QCA_TSF_CAPTURE,
@@ -2232,6 +2275,7 @@
 	QCA_TSF_AUTO_REPORT_DISABLE,
 	QCA_TSF_SYNC_START,
 	QCA_TSF_SYNC_STOP,
+	QCA_TSF_SYNC_GET_CSA_TIMESTAMP,
 };
 
 /**
@@ -2373,6 +2417,10 @@
  *	QCA_WLAN_VENDOR_SCAN_PRIORITY_HIGH as the priority of vendor scan.
  * @QCA_WLAN_VENDOR_ATTR_SCAN_PAD: Attribute used for padding for 64-bit
  *	alignment.
+ * @QCA_WLAN_VENDOR_ATTR_SCAN_LINK_ID: This u8 attribute is used for OBSS scan
+ *	when AP is operating as MLD to specify which link is requesting the
+ *	scan or which link the scan result is for. No need of this attribute
+ *	in other cases.
  */
 enum qca_wlan_vendor_attr_scan {
 	QCA_WLAN_VENDOR_ATTR_SCAN_INVALID_PARAM = 0,
@@ -2390,6 +2438,7 @@
 	QCA_WLAN_VENDOR_ATTR_SCAN_DWELL_TIME = 12,
 	QCA_WLAN_VENDOR_ATTR_SCAN_PRIORITY = 13,
 	QCA_WLAN_VENDOR_ATTR_SCAN_PAD = 14,
+	QCA_WLAN_VENDOR_ATTR_SCAN_LINK_ID = 15,
 	QCA_WLAN_VENDOR_ATTR_SCAN_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_SCAN_MAX =
 	QCA_WLAN_VENDOR_ATTR_SCAN_AFTER_LAST - 1
@@ -3302,6 +3351,16 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_COEX_TRAFFIC_SHAPING_MODE = 105,
 
+	/* 8-bit unsigned value.
+	 *
+	 * This attribute is used to specify whether an associated peer is a QCA
+	 * device. The associated peer is specified with
+	 * QCA_WLAN_VENDOR_ATTR_CONFIG_PEER_MAC. For MLO cases, the MLD MAC
+	 * address of the peer is used.
+	 * 1 - QCA device, 0 - non-QCA device.
+	 */
+	QCA_WLAN_VENDOR_ATTR_CONFIG_QCA_PEER = 106,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_CONFIG_MAX =
@@ -9989,6 +10048,13 @@
 	 */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_MLD_ID_ML_PROBE_REQ = 72,
 
+	/* 8-bit unsigned value to configure the SCS traffic description
+	 * support in the EHT capabilities of an Association Request frame.
+	 * 1-enable, 0-disable
+	 * This attribute is used to configure the testbed device.
+	 */
+	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_EHT_SCS_TRAFFIC_SUPPORT = 73,
+
 	/* keep last */
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_AFTER_LAST,
 	QCA_WLAN_VENDOR_ATTR_WIFI_TEST_CONFIG_MAX =
@@ -14169,12 +14235,14 @@
  * @QCA_WLAN_RATEMASK_PARAMS_TYPE_HT: HT rate mask config
  * @QCA_WLAN_RATEMASK_PARAMS_TYPE_VHT: VHT rate mask config
  * @QCA_WLAN_RATEMASK_PARAMS_TYPE_HE: HE rate mask config
+ * @QCA_WLAN_RATEMASK_PARAMS_TYPE_EHT: EHT rate mask config
  */
 enum qca_wlan_ratemask_params_type {
 	QCA_WLAN_RATEMASK_PARAMS_TYPE_CCK_OFDM = 0,
 	QCA_WLAN_RATEMASK_PARAMS_TYPE_HT = 1,
 	QCA_WLAN_RATEMASK_PARAMS_TYPE_VHT = 2,
 	QCA_WLAN_RATEMASK_PARAMS_TYPE_HE = 3,
+	QCA_WLAN_RATEMASK_PARAMS_TYPE_EHT = 4,
 };
 
 /**
@@ -14969,12 +15037,25 @@
  * CTL group but user can choose up to 3 SAR set index only, as the top half
  * of the SAR index (0 to 2) is used for non DBS purpose and the bottom half of
  * the SAR index (3 to 5) is used for DBS mode.
+ *
+ * @QCA_WLAN_VENDOR_SAR_VERSION_4: The firmware supports SAR version 4,
+ * known as SAR Smart Transmit (STX) mode. STX is time averaging algorithmic
+ * for power limit computation in collaboration with WWAN.
+ * In STX mode, firmware has 41 indexes and there is no ctl grouping uses.
+ *
+ * @QCA_WLAN_VENDOR_SAR_VERSION_5: The firmware supports SAR version 5,
+ * known as TAS (Time Averaging SAR) mode. In TAS mode, as name implies
+ * instead of fixed static SAR power limit firmware uses time averaging
+ * to adjust the SAR limit dynamically. It is wlan soc standalone mechanism.
+ * In this mode firmware has up to 43 indexes.
  */
 enum qca_wlan_vendor_sar_version {
 	QCA_WLAN_VENDOR_SAR_VERSION_INVALID = 0,
 	QCA_WLAN_VENDOR_SAR_VERSION_1 = 1,
 	QCA_WLAN_VENDOR_SAR_VERSION_2 = 2,
 	QCA_WLAN_VENDOR_SAR_VERSION_3 = 3,
+	QCA_WLAN_VENDOR_SAR_VERSION_4 = 4,
+	QCA_WLAN_VENDOR_SAR_VERSION_5 = 5,
 };
 
 /**
@@ -16602,4 +16683,115 @@
 	QCA_CHAN_WIDTH_UPDATE_TYPE_TX_RX_EXT = 2,
 };
 
+/**
+ * enum qca_wlan_vendor_attr_tpc_pwr_level - Definition of attributes
+ * used inside nested attribute %QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_FREQUENCY: u32 channel center
+ * frequency (MHz): If PSD power, carries one 20 MHz sub-channel center
+ * frequency. If non PSD power, carries either 20 MHz bandwidth's center
+ * channel frequency or 40 MHz bandwidth's center channel frequency
+ * (or 80/160 MHz bandwidth's center channel frequency).
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_TX_POWER: s8 transmit power limit (dBm).
+ * If PSD power, carries PSD power value of the
+ * QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_FREQUENCY mentioned sub-channel.
+ * If non PSD power, carries EIRP power value of bandwidth mentioned
+ * by QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_FREQUENCY center frequency.
+ */
+enum qca_wlan_vendor_attr_tpc_pwr_level {
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_FREQUENCY = 1,
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_TX_POWER = 2,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_MAX =
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_tpc - Definition of link attributes
+ * used inside nested attribute %QCA_WLAN_VENDOR_ATTR_TPC_LINKS.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_BSSID: 6-bytes AP BSSID.
+ * For MLO STA, AP BSSID indicates the AP's link address.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_PSD_POWER: PSD power flag
+ * Indicates using PSD power mode if this flag is present.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_EIRP_POWER: s8 Regulatory EIRP power
+ * value in dBm
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_POWER_TYPE_6GHZ: u8 power type of 6 GHz
+ * AP, refer to Table E-12-Regulatory Info subfield encoding in
+ * IEEE P802.11-REVme/D4.0. Only present if link is connected to 6 GHz AP.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_AP_CONSTRAINT_POWER: u8 Local Power Constraint
+ * (dBm) advertised by AP in Power Constraint element, refer to
+ * IEEE Std 802.11-2020, 9.4.2.13 Power Constraint element.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL: A nested attribute containing
+ * attributes defined by enum qca_wlan_vendor_attr_tpc_pwr_level.
+ * If PSD power, each power level describes each 20 MHz subchannel PSD
+ * power value. If non PSD power, each power level describes each supported
+ * bandwidth's EIRP power value (up to Max bandwidth of AP operating on),
+ * each level attribute contains corresponding bandwidth's center channel
+ * frequency and its EIRP power value.
+ */
+enum qca_wlan_vendor_attr_tpc {
+	QCA_WLAN_VENDOR_ATTR_TPC_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TPC_BSSID = 1,
+	QCA_WLAN_VENDOR_ATTR_TPC_PSD_POWER = 2,
+	QCA_WLAN_VENDOR_ATTR_TPC_EIRP_POWER = 3,
+	QCA_WLAN_VENDOR_ATTR_TPC_POWER_TYPE_6GHZ = 4,
+	QCA_WLAN_VENDOR_ATTR_TPC_AP_CONSTRAINT_POWER = 5,
+	QCA_WLAN_VENDOR_ATTR_TPC_PWR_LEVEL = 6,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TPC_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TPC_MAX =
+	QCA_WLAN_VENDOR_ATTR_TPC_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_tpc_links - Definition of attributes
+ * for %QCA_NL80211_VENDOR_SUBCMD_REGULATORY_TPC_INFO subcommand
+ *
+ * @QCA_WLAN_VENDOR_ATTR_TPC_LINKS: A nested attribute containing
+ * per-link TPC information of all the active links of MLO STA.
+ * For non MLO STA, only one link TPC information will be returned
+ * for connected AP in this nested attribute.
+ * The attributes used inside this nested attributes are defined
+ * in enum qca_wlan_vendor_attr_tpc.
+ */
+enum qca_wlan_vendor_attr_tpc_links {
+	QCA_WLAN_VENDOR_ATTR_TPC_LINKS_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_TPC_LINKS = 1,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_TPC_LINKS_AFTER_LAST,
+	QCA_WLAN_VENDOR_ATTR_TPC_LINKS_MAX =
+	QCA_WLAN_VENDOR_ATTR_TPC_AFTER_LAST - 1,
+};
+
+/**
+ * enum qca_wlan_vendor_attr_fw_page_fault_report - Used by the vendor
+ * command %QCA_NL80211_VENDOR_SUBCMD_FW_PAGE_FAULT_REPORT.
+ *
+ * @QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_DATA: The binary blob data
+ * associated with the firmware page fault that is expected to contain the
+ * required dump to analyze frequent page faults.
+ * NLA_BINARY attribute, the maximum size is 1024 bytes.
+ */
+enum qca_wlan_vendor_attr_fw_page_fault_report {
+	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_INVALID = 0,
+	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_DATA = 1,
+
+	/* keep last */
+	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_LAST,
+	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_MAX =
+	QCA_WLAN_VENDOR_ATTR_FW_PAGE_FAULT_REPORT_LAST - 1,
+};
+
 #endif /* QCA_VENDOR_H */
diff --git a/src/common/sae.c b/src/common/sae.c
index d4a196f..f1c164e 100644
--- a/src/common/sae.c
+++ b/src/common/sae.c
@@ -458,7 +458,7 @@
 		       * mask */
 	u8 mask;
 	struct crypto_bignum *pwe;
-	size_t prime_len = sae->tmp->prime_len * 8;
+	size_t prime_len = sae->tmp->prime_len;
 	u8 *pwe_buf;
 
 	crypto_bignum_deinit(sae->tmp->pwe_ffc, 1);
diff --git a/src/common/wpa_common.c b/src/common/wpa_common.c
index d897e0e..c82fd0e 100644
--- a/src/common/wpa_common.c
+++ b/src/common/wpa_common.c
@@ -1329,8 +1329,7 @@
 		if (fte_len < 255) {
 			res = wpa_ft_parse_fte(key_mgmt, fte, fte_len, parse);
 		} else {
-			parse->fte_buf = ieee802_11_defrag_data(fte, fte_len,
-								false);
+			parse->fte_buf = ieee802_11_defrag(fte, fte_len, false);
 			if (!parse->fte_buf)
 				goto fail;
 			res = wpa_ft_parse_fte(key_mgmt,
@@ -2894,7 +2893,7 @@
 }
 
 
-int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid)
+int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid, bool replace)
 {
 	u8 *start, *end, *rpos, *rend;
 	int added = 0;
@@ -2957,12 +2956,12 @@
 		if (rend - rpos < 2)
 			return -1;
 		num_pmkid = WPA_GET_LE16(rpos);
+		if (num_pmkid * PMKID_LEN > rend - rpos - 2)
+			return -1;
 		/* PMKID-Count was included; use it */
-		if (num_pmkid != 0) {
+		if (replace && num_pmkid != 0) {
 			u8 *after;
 
-			if (num_pmkid * PMKID_LEN > rend - rpos - 2)
-				return -1;
 			/*
 			 * PMKID may have been included in RSN IE in
 			 * (Re)Association Request frame, so remove the old
@@ -2975,8 +2974,9 @@
 			os_memmove(rpos + 2, after, end - after);
 			start[1] -= num_pmkid * PMKID_LEN;
 			added -= num_pmkid * PMKID_LEN;
+			num_pmkid = 0;
 		}
-		WPA_PUT_LE16(rpos, 1);
+		WPA_PUT_LE16(rpos, num_pmkid + 1);
 		rpos += 2;
 		os_memmove(rpos + PMKID_LEN, rpos, end + added - rpos);
 		os_memcpy(rpos, pmkid, PMKID_LEN);
diff --git a/src/common/wpa_common.h b/src/common/wpa_common.h
index 1269bf9..a2c7033 100644
--- a/src/common/wpa_common.h
+++ b/src/common/wpa_common.h
@@ -555,7 +555,7 @@
 int wpa_compare_rsn_ie(int ft_initial_assoc,
 		       const u8 *ie1, size_t ie1len,
 		       const u8 *ie2, size_t ie2len);
-int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid);
+int wpa_insert_pmkid(u8 *ies, size_t *ies_len, const u8 *pmkid, bool replace);
 
 #define MAX_NUM_MLO_LINKS 15
 
diff --git a/src/common/wpa_ctrl.h b/src/common/wpa_ctrl.h
index 416e0d6..c5bb9ab 100644
--- a/src/common/wpa_ctrl.h
+++ b/src/common/wpa_ctrl.h
@@ -87,6 +87,8 @@
 #define WPA_EVENT_BEACON_LOSS "CTRL-EVENT-BEACON-LOSS "
 /** Regulatory domain channel */
 #define WPA_EVENT_REGDOM_CHANGE "CTRL-EVENT-REGDOM-CHANGE "
+/** Regulatory beacon hint */
+#define WPA_EVENT_REGDOM_BEACON_HINT "CTRL-EVENT-REGDOM-BEACON-HINT "
 /** Channel switch started (followed by freq=<MHz> and other channel parameters)
  */
 #define WPA_EVENT_CHANNEL_SWITCH_STARTED "CTRL-EVENT-STARTED-CHANNEL-SWITCH "
@@ -230,6 +232,13 @@
 #define DPP_EVENT_PB_RESULT "DPP-PB-RESULT "
 #define DPP_EVENT_RELAY_NEEDS_CONTROLLER "DPP-RELAY-NEEDS-CONTROLLER "
 
+/* Wi-Fi Aware (NAN USD) events */
+#define NAN_DISCOVERY_RESULT "NAN-DISCOVERY-RESULT "
+#define NAN_REPLIED "NAN-REPLIED "
+#define NAN_PUBLISH_TERMINATED "NAN-PUBLISH-TERMINATED "
+#define NAN_SUBSCRIBE_TERMINATED "NAN-SUBSCRIBE-TERMINATED "
+#define NAN_RECEIVE "NAN-RECEIVE "
+
 /* MESH events */
 #define MESH_GROUP_STARTED "MESH-GROUP-STARTED "
 #define MESH_GROUP_REMOVED "MESH-GROUP-REMOVED "
diff --git a/src/crypto/crypto_module_tests.c b/src/crypto/crypto_module_tests.c
index 4147f41..ffeddba 100644
--- a/src/crypto/crypto_module_tests.c
+++ b/src/crypto/crypto_module_tests.c
@@ -2446,6 +2446,7 @@
 	wpabuf_free(res_pt);
 	wpabuf_free(res_ct);
 	crypto_ec_key_deinit(own_priv);
+	crypto_ec_key_deinit(peer_pub);
 	return res;
 }
 
diff --git a/src/crypto/crypto_openssl.c b/src/crypto/crypto_openssl.c
index 22f6ab4..427677d 100644
--- a/src/crypto/crypto_openssl.c
+++ b/src/crypto/crypto_openssl.c
@@ -1,6 +1,6 @@
 /*
  * Wrapper functions for OpenSSL libcrypto
- * Copyright (c) 2004-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -31,6 +31,11 @@
 #else /* OpenSSL version >= 3.0 */
 #include <openssl/cmac.h>
 #endif /* OpenSSL version >= 3.0 */
+#ifdef CONFIG_DPP3
+#if OPENSSL_VERSION_NUMBER >= 0x30200000L
+#include <openssl/hpke.h>
+#endif
+#endif /* CONFIG_DPP3 */
 
 #include "common.h"
 #include "utils/const_time.h"
@@ -471,11 +476,11 @@
 	ctx = EVP_CIPHER_CTX_new();
 	if (ctx == NULL)
 		return NULL;
-	if (EVP_EncryptInit_ex(ctx, type, NULL, key, NULL) != 1) {
+	if (EVP_EncryptInit_ex(ctx, type, NULL, key, NULL) != 1 ||
+	    EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
 		EVP_CIPHER_CTX_free(ctx);
 		return NULL;
 	}
-	EVP_CIPHER_CTX_set_padding(ctx, 0);
 	return ctx;
 }
 
@@ -528,11 +533,11 @@
 	ctx = EVP_CIPHER_CTX_new();
 	if (ctx == NULL)
 		return NULL;
-	if (EVP_DecryptInit_ex(ctx, type, NULL, key, NULL) != 1) {
+	if (EVP_DecryptInit_ex(ctx, type, NULL, key, NULL) != 1 ||
+	    EVP_CIPHER_CTX_set_padding(ctx, 0) != 1) {
 		EVP_CIPHER_CTX_free(ctx);
 		return NULL;
 	}
-	EVP_CIPHER_CTX_set_padding(ctx, 0);
 	return ctx;
 }
 
@@ -1313,6 +1318,7 @@
 #else /* OpenSSL version >= 3.0 */
 	HMAC_CTX *ctx;
 #endif /* OpenSSL version >= 3.0 */
+	bool failed;
 };
 
 
@@ -1425,9 +1431,11 @@
 	if (ctx == NULL)
 		return;
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
-	EVP_MAC_update(ctx->ctx, data, len);
+	if (!EVP_MAC_update(ctx->ctx, data, len))
+		ctx->failed = true;
 #else /* OpenSSL version >= 3.0 */
-	HMAC_Update(ctx->ctx, data, len);
+	if (!HMAC_Update(ctx->ctx, data, len))
+		ctx->failed = true;
 #endif /* OpenSSL version >= 3.0 */
 }
 
@@ -1437,6 +1445,7 @@
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
 	size_t mdlen;
 	int res;
+	bool failed;
 
 	if (!ctx)
 		return -2;
@@ -1455,11 +1464,15 @@
 	}
 	res = EVP_MAC_final(ctx->ctx, mac, &mdlen, mdlen);
 	EVP_MAC_CTX_free(ctx->ctx);
+	failed = ctx->failed;
 	bin_clear_free(ctx, sizeof(*ctx));
 
 	if (TEST_FAIL())
 		return -1;
 
+	if (failed)
+		return -2;
+
 	if (res == 1) {
 		*len = mdlen;
 		return 0;
@@ -1469,6 +1482,7 @@
 #else /* OpenSSL version >= 3.0 */
 	unsigned int mdlen;
 	int res;
+	bool failed;
 
 	if (ctx == NULL)
 		return -2;
@@ -1482,11 +1496,15 @@
 	mdlen = *len;
 	res = HMAC_Final(ctx->ctx, mac, &mdlen);
 	HMAC_CTX_free(ctx->ctx);
+	failed = ctx->failed;
 	bin_clear_free(ctx, sizeof(*ctx));
 
 	if (TEST_FAIL())
 		return -1;
 
+	if (failed)
+		return -2;
+
 	if (res == 1) {
 		*len = mdlen;
 		return 0;
@@ -2841,8 +2859,10 @@
 
 	/* Encode using SECG SEC 1, Sec. 2.3.4 format */
 	peer = os_malloc(1 + len);
-	if (!peer)
+	if (!peer) {
+		EVP_PKEY_free(peerkey);
 		return NULL;
+	}
 	peer[0] = inc_y ? 0x04 : 0x02;
 	os_memcpy(peer + 1, key, len);
 
@@ -2997,11 +3017,15 @@
 		NULL, NULL);
 	if (!ctx ||
 	    OSSL_DECODER_from_data(ctx, &der, &der_len) != 1) {
-		wpa_printf(MSG_INFO, "OpenSSL: Decoding EC private key (DER) failed: %s",
+		wpa_printf(MSG_INFO,
+			   "OpenSSL: Decoding EC private key (DER) failed: %s",
 			   ERR_error_string(ERR_get_error(), NULL));
+		if (ctx)
+			OSSL_DECODER_CTX_free(ctx);
 		goto fail;
 	}
 
+	OSSL_DECODER_CTX_free(ctx);
 	return (struct crypto_ec_key *) pkey;
 fail:
 	crypto_ec_key_deinit((struct crypto_ec_key *) pkey);
@@ -5125,13 +5149,13 @@
 }
 
 
-struct wpabuf * hpke_base_seal(enum hpke_kem_id kem_id,
-			       enum hpke_kdf_id kdf_id,
-			       enum hpke_aead_id aead_id,
-			       struct crypto_ec_key *peer_pub,
-			       const u8 *info, size_t info_len,
-			       const u8 *aad, size_t aad_len,
-			       const u8 *pt, size_t pt_len)
+static struct wpabuf * hpke_base_seal_int(enum hpke_kem_id kem_id,
+					  enum hpke_kdf_id kdf_id,
+					  enum hpke_aead_id aead_id,
+					  struct crypto_ec_key *peer_pub,
+					  const u8 *info, size_t info_len,
+					  const u8 *aad, size_t aad_len,
+					  const u8 *pt, size_t pt_len)
 {
 	struct hpke_context *ctx;
 	u8 shared_secret[HPKE_MAX_SHARED_SECRET_LEN];
@@ -5289,13 +5313,13 @@
 }
 
 
-struct wpabuf * hpke_base_open(enum hpke_kem_id kem_id,
-			       enum hpke_kdf_id kdf_id,
-			       enum hpke_aead_id aead_id,
-			       struct crypto_ec_key *own_priv,
-			       const u8 *info, size_t info_len,
-			       const u8 *aad, size_t aad_len,
-			       const u8 *enc_ct, size_t enc_ct_len)
+static struct wpabuf * hpke_base_open_int(enum hpke_kem_id kem_id,
+					  enum hpke_kdf_id kdf_id,
+					  enum hpke_aead_id aead_id,
+					  struct crypto_ec_key *own_priv,
+					  const u8 *info, size_t info_len,
+					  const u8 *aad, size_t aad_len,
+					  const u8 *enc_ct, size_t enc_ct_len)
 {
 	struct hpke_context *ctx;
 	u8 shared_secret[HPKE_MAX_SHARED_SECRET_LEN];
@@ -5324,6 +5348,231 @@
 	return pt;
 }
 
+
+#if OPENSSL_VERSION_NUMBER >= 0x30200000L
+
+static bool hpke_set_suite(OSSL_HPKE_SUITE *suite,
+			   enum hpke_kem_id kem_id,
+			   enum hpke_kdf_id kdf_id,
+			   enum hpke_aead_id aead_id)
+{
+	os_memset(suite, 0, sizeof(*suite));
+
+	switch (kem_id) {
+	case HPKE_DHKEM_P256_HKDF_SHA256:
+		suite->kem_id = OSSL_HPKE_KEM_ID_P256;
+		break;
+	case HPKE_DHKEM_P384_HKDF_SHA384:
+		suite->kem_id = OSSL_HPKE_KEM_ID_P384;
+		break;
+	case HPKE_DHKEM_P521_HKDF_SHA512:
+		suite->kem_id = OSSL_HPKE_KEM_ID_P521;
+		break;
+	default:
+		return false;
+	}
+
+	switch (kdf_id) {
+	case HPKE_KDF_HKDF_SHA256:
+		suite->kdf_id = OSSL_HPKE_KDF_ID_HKDF_SHA256;
+		break;
+	case HPKE_KDF_HKDF_SHA384:
+		suite->kdf_id = OSSL_HPKE_KDF_ID_HKDF_SHA384;
+		break;
+	case HPKE_KDF_HKDF_SHA512:
+		suite->kdf_id = OSSL_HPKE_KDF_ID_HKDF_SHA512;
+		break;
+	default:
+		return false;
+	}
+
+	switch (aead_id) {
+	case HPKE_AEAD_AES_128_GCM:
+		suite->aead_id = OSSL_HPKE_AEAD_ID_AES_GCM_128;
+		break;
+	case HPKE_AEAD_AES_256_GCM:
+		suite->aead_id = OSSL_HPKE_AEAD_ID_AES_GCM_256;
+		break;
+	default:
+		return false;
+	}
+
+	if (!OSSL_HPKE_suite_check(*suite)) {
+		wpa_printf(MSG_INFO,
+			   "OpenSSL: HPKE suite kem_id=%d kdf_id=%d aead_id=%d not supported",
+			   kem_id, kdf_id, aead_id);
+		return false;
+	}
+
+	return true;
+}
+
+
+struct wpabuf * hpke_base_seal(enum hpke_kem_id kem_id,
+			       enum hpke_kdf_id kdf_id,
+			       enum hpke_aead_id aead_id,
+			       struct crypto_ec_key *peer_pub,
+			       const u8 *info, size_t info_len,
+			       const u8 *aad, size_t aad_len,
+			       const u8 *pt, size_t pt_len)
+{
+	OSSL_HPKE_SUITE suite;
+	OSSL_HPKE_CTX *ctx = NULL;
+	struct wpabuf *res = NULL, *buf, *pub = NULL;
+	size_t enc_len, ct_len;
+	int group;
+
+	group = crypto_ec_key_group(peer_pub);
+	if (group == 28 || group == 29 || group == 30) {
+		/* Use the internal routines for the special DPP use case with
+		 * brainpool curves, */
+		return hpke_base_seal_int(kem_id, kdf_id, aead_id, peer_pub,
+					  info, info_len, aad, aad_len,
+					  pt, pt_len);
+	}
+
+
+	if (!hpke_set_suite(&suite, kem_id, kdf_id, aead_id))
+		return NULL;
+
+	enc_len = OSSL_HPKE_get_public_encap_size(suite);
+	ct_len = OSSL_HPKE_get_ciphertext_size(suite, pt_len);
+	buf = wpabuf_alloc(enc_len + ct_len);
+	if (!buf)
+		goto out;
+
+	pub = crypto_ec_key_get_pubkey_point(peer_pub, 1);
+	if (!pub)
+		goto out;
+
+	ctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite,
+				OSSL_HPKE_ROLE_SENDER, NULL, NULL);
+	if (!ctx)
+		goto out;
+
+	if (OSSL_HPKE_encap(ctx, wpabuf_put(buf, 0), &enc_len,
+			    wpabuf_head(pub), wpabuf_len(pub),
+			    info, info_len) != 1) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: OSSL_HPKE_encap failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto out;
+	}
+	wpabuf_put(buf, enc_len);
+
+	if (OSSL_HPKE_seal(ctx, wpabuf_put(buf, 0), &ct_len, aad, aad_len,
+			   pt, pt_len) != 1) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: OSSL_HPKE_seal failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto out;
+	}
+	wpabuf_put(buf, ct_len);
+	res = buf;
+	buf = NULL;
+
+out:
+	OSSL_HPKE_CTX_free(ctx);
+	wpabuf_free(buf);
+	wpabuf_free(pub);
+	return res;
+}
+
+
+struct wpabuf * hpke_base_open(enum hpke_kem_id kem_id,
+			       enum hpke_kdf_id kdf_id,
+			       enum hpke_aead_id aead_id,
+			       struct crypto_ec_key *own_priv,
+			       const u8 *info, size_t info_len,
+			       const u8 *aad, size_t aad_len,
+			       const u8 *enc_ct, size_t enc_ct_len)
+{
+	OSSL_HPKE_SUITE suite;
+	OSSL_HPKE_CTX *ctx;
+	struct wpabuf *buf = NULL, *res = NULL;
+	size_t len, enc_len;
+	int group;
+
+	group = crypto_ec_key_group(own_priv);
+	if (group == 28 || group == 29 || group == 30) {
+		/* Use the internal routines for the special DPP use case with
+		 * brainpool curves, */
+		return hpke_base_open_int(kem_id, kdf_id, aead_id, own_priv,
+					  info, info_len, aad, aad_len,
+					  enc_ct, enc_ct_len);
+	}
+
+	if (!hpke_set_suite(&suite, kem_id, kdf_id, aead_id))
+		return NULL;
+
+	enc_len = OSSL_HPKE_get_public_encap_size(suite);
+	if (enc_ct_len < enc_len) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: Too short HPKE enc_ct data");
+		return NULL;
+	}
+
+	ctx = OSSL_HPKE_CTX_new(OSSL_HPKE_MODE_BASE, suite,
+				OSSL_HPKE_ROLE_RECEIVER, NULL, NULL);
+	if (!ctx)
+		goto out;
+
+	if (OSSL_HPKE_decap(ctx, enc_ct, enc_len, (EVP_PKEY *) own_priv,
+			    info, info_len) != 1) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: OSSL_HPKE_decap failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto out;
+	}
+
+	len = enc_ct_len;
+	buf = wpabuf_alloc(len);
+	if (!buf)
+		goto out;
+
+	if (OSSL_HPKE_open(ctx, wpabuf_put(buf, 0), &len, aad, aad_len,
+			   enc_ct + enc_len, enc_ct_len - enc_len) != 1) {
+		wpa_printf(MSG_DEBUG, "OpenSSL: OSSL_HPKE_open failed: %s",
+			   ERR_error_string(ERR_get_error(), NULL));
+		goto out;
+	}
+
+	wpabuf_put(buf, len);
+	res = buf;
+	buf = NULL;
+
+out:
+	OSSL_HPKE_CTX_free(ctx);
+	wpabuf_free(buf);
+	return res;
+}
+
+#else /* OpenSSL < 3.2 */
+
+struct wpabuf * hpke_base_seal(enum hpke_kem_id kem_id,
+			       enum hpke_kdf_id kdf_id,
+			       enum hpke_aead_id aead_id,
+			       struct crypto_ec_key *peer_pub,
+			       const u8 *info, size_t info_len,
+			       const u8 *aad, size_t aad_len,
+			       const u8 *pt, size_t pt_len)
+{
+	return hpke_base_seal_int(kem_id, kdf_id, aead_id, peer_pub,
+				  info, info_len, aad, aad_len, pt, pt_len);
+}
+
+
+struct wpabuf * hpke_base_open(enum hpke_kem_id kem_id,
+			       enum hpke_kdf_id kdf_id,
+			       enum hpke_aead_id aead_id,
+			       struct crypto_ec_key *own_priv,
+			       const u8 *info, size_t info_len,
+			       const u8 *aad, size_t aad_len,
+			       const u8 *enc_ct, size_t enc_ct_len)
+{
+	return hpke_base_open_int(kem_id, kdf_id, aead_id, own_priv,
+				  info, info_len, aad, aad_len,
+				  enc_ct, enc_ct_len);
+}
+
+#endif /* OpenSSL < 3.2 */
+
 #endif /* CONFIG_DPP3 */
 
 
diff --git a/src/crypto/sha256-internal.c b/src/crypto/sha256-internal.c
index ff1e2ba..81e6e5e 100644
--- a/src/crypto/sha256-internal.c
+++ b/src/crypto/sha256-internal.c
@@ -76,9 +76,6 @@
 #define Sigma1(x)       (S(x, 6) ^ S(x, 11) ^ S(x, 25))
 #define Gamma0(x)       (S(x, 7) ^ S(x, 18) ^ R(x, 3))
 #define Gamma1(x)       (S(x, 17) ^ S(x, 19) ^ R(x, 10))
-#ifndef MIN
-#define MIN(x, y) (((x) < (y)) ? (x) : (y))
-#endif
 
 /* compress 512-bits */
 static int sha256_compress(struct sha256_state *md, unsigned char *buf)
diff --git a/src/crypto/sha512-internal.c b/src/crypto/sha512-internal.c
index c026394..8e98a9c 100644
--- a/src/crypto/sha512-internal.c
+++ b/src/crypto/sha512-internal.c
@@ -97,9 +97,6 @@
 #define Sigma1(x)       (S(x, 14) ^ S(x, 18) ^ S(x, 41))
 #define Gamma0(x)       (S(x, 1) ^ S(x, 8) ^ R(x, 7))
 #define Gamma1(x)       (S(x, 19) ^ S(x, 61) ^ R(x, 6))
-#ifndef MIN
-#define MIN(x, y) (((x) < (y)) ? (x) : (y))
-#endif
 
 #define ROR64c(x, y) \
     ( ((((x) & CONST64(0xFFFFFFFFFFFFFFFF)) >> ((u64) (y) & CONST64(63))) | \
diff --git a/src/crypto/tls_openssl.c b/src/crypto/tls_openssl.c
index 103c333..f5d734d 100644
--- a/src/crypto/tls_openssl.c
+++ b/src/crypto/tls_openssl.c
@@ -19,14 +19,16 @@
 #endif
 #endif
 
+#ifndef OPENSSL_NO_ENGINE
+/* OpenSSL 3.0 has moved away from the engine API */
+#define OPENSSL_SUPPRESS_DEPRECATED
+#include <openssl/engine.h>
+#endif /* OPENSSL_NO_ENGINE */
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/opensslv.h>
 #include <openssl/pkcs12.h>
 #include <openssl/x509v3.h>
-#ifndef OPENSSL_NO_ENGINE
-#include <openssl/engine.h>
-#endif /* OPENSSL_NO_ENGINE */
 #if OPENSSL_VERSION_NUMBER >= 0x30000000L
 #include <openssl/core_names.h>
 #include <openssl/decoder.h>
@@ -152,6 +154,7 @@
 	unsigned int crl_reload_interval;
 	struct os_reltime crl_last_reload;
 	char *check_cert_subject;
+	char *openssl_ciphers;
 };
 
 struct tls_connection {
@@ -1261,6 +1264,7 @@
 	}
 
 	os_free(data->check_cert_subject);
+	os_free(data->openssl_ciphers);
 	os_free(data);
 }
 
@@ -3249,6 +3253,9 @@
 	}
 #endif
 
+	if (!openssl_ciphers)
+		openssl_ciphers = conn->data->openssl_ciphers;
+
 #ifdef CONFIG_SUITEB
 #ifdef OPENSSL_IS_BORINGSSL
 	/* Start with defaults from BoringSSL */
@@ -5749,6 +5756,14 @@
 		return -1;
 	}
 
+	os_free(data->openssl_ciphers);
+	if (params->openssl_ciphers) {
+		data->openssl_ciphers = os_strdup(params->openssl_ciphers);
+		if (!data->openssl_ciphers)
+			return -1;
+	} else {
+		data->openssl_ciphers = NULL;
+	}
 	if (params->openssl_ciphers &&
 	    SSL_CTX_set_cipher_list(ssl_ctx, params->openssl_ciphers) != 1) {
 		wpa_printf(MSG_INFO,
diff --git a/src/drivers/driver.h b/src/drivers/driver.h
index 7ae7d90..a55e8e3 100644
--- a/src/drivers/driver.h
+++ b/src/drivers/driver.h
@@ -949,6 +949,8 @@
 		const u8 *bssid;
 		const u8 *ies;
 		size_t ies_len;
+		int error;
+		bool disabled;
 	} mld_links[MAX_NUM_MLD_LINKS];
 };
 
@@ -2715,6 +2717,7 @@
  * @counter_offset_beacon: Offset to the count field in beacon's tail
  * @counter_offset_presp: Offset to the count field in probe resp.
  * @punct_bitmap - Preamble puncturing bitmap
+ * @link_id: Link ID to determine the link for MLD; -1 for non-MLD
  */
 struct csa_settings {
 	u8 cs_count;
@@ -2728,6 +2731,7 @@
 	u16 counter_offset_presp[2];
 
 	u16 punct_bitmap;
+	int link_id;
 };
 
 /**
@@ -2819,6 +2823,9 @@
 
 	/* Indicates whether EHT is enabled */
 	bool eht_enabled;
+
+	/* Indicates the link if MLO case; -1 otherwise */
+	int link_id;
 };
 
 struct wpa_bss_trans_info {
@@ -6535,6 +6542,7 @@
 	/**
 	 * struct dfs_event - Data for radar detected events
 	 * @freq: Frequency of the channel in MHz
+	 * @link_id: If >= 0, Link ID of the MLO link
 	 */
 	struct dfs_event {
 		int freq;
@@ -6543,6 +6551,7 @@
 		enum chan_width chan_width;
 		int cf1;
 		int cf2;
+		int link_id;
 	} dfs_event;
 
 	/**
@@ -6561,11 +6570,22 @@
 	 * @initiator: Initiator of the regulatory change
 	 * @type: Regulatory change type
 	 * @alpha2: Country code (or "" if not available)
+	 * @beacon_hint_before: Data for frequency attributes before beacon hint
+	 *	event if initiator == REGDOM_BEACON_HINT
+	 * @beacon_hint_after: Data for frequency attributes after beacon hint
+	 *	event if initiator == REGDOM_BEACON_HINT
 	 */
 	struct channel_list_changed {
 		enum reg_change_initiator initiator;
 		enum reg_type type;
 		char alpha2[3];
+		struct frequency_attrs {
+			unsigned int freq;
+			unsigned int max_tx_power;
+			bool disabled;
+			bool no_ir;
+			bool radar;
+		} beacon_hint_before, beacon_hint_after;
 	} channel_list_changed;
 
 	/**
@@ -6608,7 +6628,9 @@
 	 * @ch_width: Selected Channel width by driver. Driver may choose to
 	 *	change hostapd configured ACS channel width due driver internal
 	 *	channel restrictions.
-	 * hw_mode: Selected band (used with hw_mode=any)
+	 * @hw_mode: Selected band (used with hw_mode=any)
+	 * @puncture_bitmap: Indicate the puncturing channels
+	 * @link_id: Indicate the link id if operating as AP MLD; -1 otherwise
 	 */
 	struct acs_selected_channels {
 		unsigned int pri_freq;
@@ -6619,6 +6641,7 @@
 		u16 ch_width;
 		enum hostapd_hw_mode hw_mode;
 		u16 puncture_bitmap;
+		int link_id;
 	} acs_selected_channels;
 
 	/**
diff --git a/src/drivers/driver_atheros.c b/src/drivers/driver_atheros.c
index 59f65b8..ae7f0e5 100644
--- a/src/drivers/driver_atheros.c
+++ b/src/drivers/driver_atheros.c
@@ -659,7 +659,7 @@
 			 &stats, sizeof(stats))) {
 		wpa_printf(MSG_DEBUG, "%s: Failed to fetch STA stats (addr "
 			   MACSTR ")", __func__, MAC2STR(addr));
-		if (os_memcmp(addr, drv->acct_mac, ETH_ALEN) == 0) {
+		if (ether_addr_equal(addr, drv->acct_mac)) {
 			os_memcpy(data, &drv->acct_data, sizeof(*data));
 			return 0;
 		}
@@ -892,7 +892,7 @@
 	}
 
 	if (stype == WLAN_FC_STYPE_ACTION &&
-	    (os_memcmp(drv->own_addr, mgmt->bssid, ETH_ALEN) == 0 ||
+	    (ether_addr_equal(drv->own_addr, mgmt->bssid) ||
 	     is_broadcast_ether_addr(mgmt->bssid))) {
 		os_memset(&event, 0, sizeof(event));
 		event.rx_mgmt.frame = buf;
@@ -901,7 +901,7 @@
 		return;
 	}
 
-	if (os_memcmp(drv->own_addr, mgmt->bssid, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(drv->own_addr, mgmt->bssid)) {
 		wpa_printf(MSG_DEBUG, "%s: BSSID does not match - ignore",
 			   __func__);
 		return;
@@ -1226,7 +1226,7 @@
 no_ie:
 	drv_event_assoc(hapd, addr, iebuf, ielen, NULL, 0, NULL, -1, 0);
 
-	if (os_memcmp(addr, drv->acct_mac, ETH_ALEN) == 0) {
+	if (ether_addr_equal(addr, drv->acct_mac)) {
 		/* Cached accounting data is not valid anymore. */
 		os_memset(drv->acct_mac, 0, ETH_ALEN);
 		os_memset(&drv->acct_data, 0, sizeof(drv->acct_data));
diff --git a/src/drivers/driver_ndis.c b/src/drivers/driver_ndis.c
index b32e009..0351705 100644
--- a/src/drivers/driver_ndis.c
+++ b/src/drivers/driver_ndis.c
@@ -1248,7 +1248,7 @@
 	prev = NULL;
 	entry = drv->pmkid;
 	while (entry) {
-		if (os_memcmp(entry->bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(entry->bssid, bssid))
 			break;
 		prev = entry;
 		entry = entry->next;
@@ -1293,7 +1293,7 @@
 	entry = drv->pmkid;
 	prev = NULL;
 	while (entry) {
-		if (os_memcmp(entry->bssid, bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(entry->bssid, bssid) &&
 		    os_memcmp(entry->pmkid, pmkid, 16) == 0) {
 			if (prev)
 				prev->next = entry->next;
@@ -1434,7 +1434,7 @@
 	pos = (char *) &b->Bssid[0];
 	for (i = 0; i < b->NumberOfItems; i++) {
 		NDIS_WLAN_BSSID_EX *bss = (NDIS_WLAN_BSSID_EX *) pos;
-		if (os_memcmp(drv->bssid, bss->MacAddress, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(drv->bssid, bss->MacAddress) &&
 		    bss->IELength > sizeof(NDIS_802_11_FIXED_IEs)) {
 			data.assoc_info.beacon_ies =
 				((u8 *) bss->IEs) +
@@ -1477,7 +1477,7 @@
 		}
 	} else {
 		/* Connected */
-		if (os_memcmp(drv->bssid, bssid, ETH_ALEN) != 0) {
+		if (!ether_addr_equal(drv->bssid, bssid)) {
 			os_memcpy(drv->bssid, bssid, ETH_ALEN);
 			wpa_driver_ndis_get_associnfo(drv);
 			wpa_supplicant_event(drv->ctx, EVENT_ASSOC, NULL);
diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c
index ac2f457..9b50b6f 100644
--- a/src/drivers/driver_nl80211.c
+++ b/src/drivers/driver_nl80211.c
@@ -12,10 +12,12 @@
 
 #include "includes.h"
 #include <sys/types.h>
+#include <sys/utsname.h>
 #include <fcntl.h>
 #include <net/if.h>
 #include <netlink/genl/genl.h>
 #include <netlink/genl/ctrl.h>
+#include <netlink/genl/family.h>
 #ifdef CONFIG_LIBNL3_ROUTE
 #include <netlink/route/neighbour.h>
 #endif /* CONFIG_LIBNL3_ROUTE */
@@ -170,9 +172,9 @@
 static int nl80211_send_frame_cmd(struct i802_bss *bss,
 				  unsigned int freq, unsigned int wait,
 				  const u8 *buf, size_t buf_len,
-				  int save_cookie,
-				  int no_cck, int no_ack, int offchanok,
-				  const u16 *csa_offs, size_t csa_offs_len);
+				  int save_cookie, int no_cck, int no_ack,
+				  int offchanok, const u16 *csa_offs,
+				  size_t csa_offs_len, int link_id);
 static int wpa_driver_nl80211_probe_req_report(struct i802_bss *bss,
 					       int report);
 
@@ -195,6 +197,7 @@
 
 static int i802_set_iface_flags(struct i802_bss *bss, int up);
 static int nl80211_set_param(void *priv, const char *param);
+static void nl80211_remove_links(struct i802_bss *bss);
 #ifdef CONFIG_MESH
 static int nl80211_put_mesh_config(struct nl_msg *msg,
 				   struct wpa_driver_mesh_bss_params *params);
@@ -345,17 +348,29 @@
 	return NL_SKIP;
 }
 
+struct nl80211_ack_err_args {
+	int err;
+	struct nl_msg *orig_msg;
+	struct nl80211_err_info *err_info;
+};
+
 static int error_handler(struct sockaddr_nl *nla, struct nlmsgerr *err,
 			 void *arg)
 {
+	struct nl80211_ack_err_args *err_args = arg;
 	struct nlmsghdr *nlh = (struct nlmsghdr *) err - 1;
+	struct nlmsghdr *orig_nlh = nlmsg_hdr(err_args->orig_msg);
 	int len = nlh->nlmsg_len;
 	struct nlattr *attrs;
 	struct nlattr *tb[NLMSGERR_ATTR_MAX + 1];
-	int *ret = arg;
 	int ack_len = sizeof(*nlh) + sizeof(int) + sizeof(*nlh);
+	struct nlattr *mlo_links, *link_attr;
+	u32 offset;
+	int rem;
 
-	*ret = err->error;
+	err_args->err = err->error;
+	if (err_args->err_info)
+		err_args->err_info->link_id = -1;
 
 	if (!(nlh->nlmsg_flags & NLM_F_ACK_TLVS))
 		return NL_SKIP;
@@ -377,6 +392,41 @@
 			   len, (char *) nla_data(tb[NLMSGERR_ATTR_MSG]));
 	}
 
+	if (!err_args->err_info)
+		return NL_SKIP;
+
+	/* Check if it was a per-link error report */
+
+	if (!tb[NLMSGERR_ATTR_OFFS] ||
+	    os_memcmp(orig_nlh, &err->msg, sizeof(err->msg)) != 0)
+		return NL_SKIP;
+
+	offset = nla_get_u32(tb[NLMSGERR_ATTR_OFFS]);
+
+	mlo_links = nlmsg_find_attr(orig_nlh, GENL_HDRLEN,
+				    NL80211_ATTR_MLO_LINKS);
+	if (!mlo_links)
+		return NL_SKIP;
+
+	nla_for_each_nested(link_attr, mlo_links, rem) {
+		struct nlattr *link_id;
+		size_t link_offset = (u8 *) link_attr - (u8 *) orig_nlh;
+
+		if (offset < link_offset ||
+		    offset >= link_offset + link_attr->nla_len)
+			continue;
+
+		link_id = nla_find(nla_data(link_attr), nla_len(link_attr),
+				   NL80211_ATTR_MLO_LINK_ID);
+		if (link_id) {
+			err_args->err_info->link_id = nla_get_u8(link_id);
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: kernel reports error for link: %d",
+				   err_args->err_info->link_id);
+			break;
+		}
+	}
+
 	return NL_SKIP;
 }
 
@@ -409,20 +459,61 @@
 }
 
 
-static int send_and_recv(struct nl80211_global *global,
-			 struct nl_sock *nl_handle, struct nl_msg *msg,
-			 int (*valid_handler)(struct nl_msg *, void *),
-			 void *valid_data,
-			 int (*ack_handler_custom)(struct nl_msg *, void *),
-			 void *ack_data)
+static int send_event_marker(struct wpa_driver_nl80211_data *drv)
 {
-	struct nl_cb *cb;
-	int err = -ENOMEM, opt;
+	struct nl_sock *handle;
+	struct nl_msg *msg;
+	struct nlmsghdr *hdr;
+	int res = 0;
+	int err = -NLE_NOMEM;
+
+	msg = nlmsg_alloc();
+	if (!msg)
+		goto out;
+
+	/* We only care about the returned sequence number for matching. */
+	if (!nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_PROTOCOL_FEATURES))
+		goto out;
+
+	handle = (void *) (((intptr_t) drv->global->nl_event) ^
+			   ELOOP_SOCKET_INVALID);
+
+	err = nl_send_auto_complete(handle, msg);
+	if (err < 0)
+		goto out;
+
+	hdr = nlmsg_hdr(msg);
+	res = hdr->nlmsg_seq;
+
+out:
+	nlmsg_free(msg);
+	if (err)
+		wpa_printf(MSG_INFO, "nl80211: %s failed: %s",
+			   __func__, nl_geterror(err));
+	return res;
+}
+
+
+int send_and_recv(struct nl80211_global *global,
+		  struct nl_sock *nl_handle, struct nl_msg *msg,
+		  int (*valid_handler)(struct nl_msg *, void *),
+		  void *valid_data,
+		  int (*ack_handler_custom)(struct nl_msg *, void *),
+		  void *ack_data,
+		  struct nl80211_err_info *err_info)
+{
+	struct nl_cb *cb, *s_nl_cb;
+	struct nl80211_ack_err_args err;
+	int opt;
 
 	if (!msg)
 		return -ENOMEM;
 
-	cb = nl_cb_clone(global->nl_cb);
+	err.err = -ENOMEM;
+
+	s_nl_cb = nl_socket_get_cb(nl_handle);
+	cb = nl_cb_clone(s_nl_cb);
+	nl_cb_put(s_nl_cb);
 	if (!cb)
 		goto out;
 
@@ -436,26 +527,28 @@
 	setsockopt(nl_socket_get_fd(nl_handle), SOL_NETLINK,
 		   NETLINK_CAP_ACK, &opt, sizeof(opt));
 
-	err = nl_send_auto_complete(nl_handle, msg);
-	if (err < 0) {
+	err.err = nl_send_auto_complete(nl_handle, msg);
+	if (err.err < 0) {
 		wpa_printf(MSG_INFO,
 			   "nl80211: nl_send_auto_complete() failed: %s",
-			   nl_geterror(err));
+			   nl_geterror(err.err));
 		/* Need to convert libnl error code to an errno value. For now,
 		 * just hardcode this to EBADF; the real error reason is shown
 		 * in that error print above. */
-		err = -EBADF;
+		err.err = -EBADF;
 		goto out;
 	}
 
-	err = 1;
+	err.err = 1;
+	err.orig_msg = msg;
+	err.err_info = err_info;
 
 	nl_cb_err(cb, NL_CB_CUSTOM, error_handler, &err);
-	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err);
+	nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, finish_handler, &err.err);
 	if (ack_handler_custom) {
 		struct nl80211_ack_ext_arg *ext_arg = ack_data;
 
-		ext_arg->err = &err;
+		ext_arg->err = &err.err;
 		nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM,
 			  ack_handler_custom, ack_data);
 	} else {
@@ -466,7 +559,7 @@
 		nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM,
 			  valid_handler, valid_data);
 
-	while (err > 0) {
+	while (err.err > 0) {
 		int res = nl_recvmsgs(nl_handle, cb);
 
 		if (res == -NLE_DUMP_INTR) {
@@ -481,7 +574,7 @@
 			 * will stop and return an error. */
 			wpa_printf(MSG_DEBUG, "nl80211: %s; convert to -EAGAIN",
 				   nl_geterror(res));
-			err = -EAGAIN;
+			err.err = -EAGAIN;
 		} else if (res < 0) {
 			wpa_printf(MSG_INFO,
 				   "nl80211: %s->nl_recvmsgs failed: %d (%s)",
@@ -493,89 +586,20 @@
 	/* Always clear the message as it can potentially contain keys */
 	nl80211_nlmsg_clear(msg);
 	nlmsg_free(msg);
-	return err;
+	return err.err;
 }
 
 
-int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv,
-		       struct nl_msg *msg,
-		       int (*valid_handler)(struct nl_msg *, void *),
-		       void *valid_data,
-		       int (*ack_handler_custom)(struct nl_msg *, void *),
-		       void *ack_data)
+static int nl80211_put_control_port(struct wpa_driver_nl80211_data *drv,
+				    struct nl_msg *msg)
 {
-	return send_and_recv(drv->global, drv->global->nl, msg,
-			     valid_handler, valid_data,
-			     ack_handler_custom, ack_data);
-}
-
-
-/* Use this method to mark that it is necessary to own the connection/interface
- * for this operation.
- * handle may be set to NULL, to get the same behavior as send_and_recv_msgs().
- * set_owner can be used to mark this socket for receiving control port frames.
- */
-static int send_and_recv_msgs_owner(struct wpa_driver_nl80211_data *drv,
-				    struct nl_msg *msg,
-				    struct nl_sock *handle, int set_owner,
-				    int (*valid_handler)(struct nl_msg *,
-							 void *),
-				    void *valid_data,
-				    int (*ack_handler_custom)(struct nl_msg *,
-							      void *),
-				    void *ack_data)
-{
-	if (!msg)
-		return -ENOMEM;
-
-	/* Control port over nl80211 needs the flags and attributes below.
-	 *
-	 * The Linux kernel has initial checks for them (in nl80211.c) like:
-	 *     validate_pae_over_nl80211(...)
-	 * or final checks like:
-	 *     dev->ieee80211_ptr->conn_owner_nlportid != info->snd_portid
-	 *
-	 * Final operations (e.g., disassociate) don't need to set these
-	 * attributes, but they have to be performed on the socket, which has
-	 * the connection owner property set in the kernel.
-	 */
-	if ((drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) &&
-	    handle && set_owner &&
-	    (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_OVER_NL80211) ||
-	     nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER) ||
-	     nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, ETH_P_PAE) ||
-	     nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_PREAUTH)))
+	if (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT) ||
+	    nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, ETH_P_PAE) ||
+	    ((drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) &&
+	     (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_OVER_NL80211) ||
+	      nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_PREAUTH))))
 		return -1;
-
-	return send_and_recv(drv->global, handle ? handle : drv->global->nl,
-			     msg, valid_handler, valid_data,
-			     ack_handler_custom, ack_data);
-}
-
-
-static int
-send_and_recv_msgs_connect_handle(struct wpa_driver_nl80211_data *drv,
-				  struct nl_msg *msg, struct i802_bss *bss,
-				  int set_owner)
-{
-	struct nl_sock *nl_connect = get_connect_handle(bss);
-
-	if (nl_connect)
-		return send_and_recv_msgs_owner(drv, msg, nl_connect, set_owner,
-						process_bss_event, bss, NULL,
-						NULL);
-	else
-		return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
-}
-
-
-struct nl_sock * get_connect_handle(struct i802_bss *bss)
-{
-	if ((bss->drv->capa.flags2 & WPA_DRIVER_FLAGS2_CONTROL_PORT_RX) ||
-	    bss->use_nl_connect)
-		return bss->nl_connect;
-
-	return NULL;
+	return 0;
 }
 
 
@@ -634,7 +658,7 @@
 	}
 
 	ret = send_and_recv(global, global->nl, msg, family_handler, &res,
-			    NULL, NULL);
+			    NULL, NULL, NULL);
 	if (ret == 0)
 		ret = res.id;
 	return ret;
@@ -762,8 +786,7 @@
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE)))
 		return -1;
 
-	if (send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data,
-			       NULL, NULL) == 0)
+	if (send_and_recv_resp(bss->drv, msg, netdev_info_handler, &data) == 0)
 		return data.wiphy_idx;
 	return -1;
 }
@@ -780,8 +803,7 @@
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE)))
 		return NL80211_IFTYPE_UNSPECIFIED;
 
-	if (send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data,
-			       NULL, NULL) == 0)
+	if (send_and_recv_resp(bss->drv, msg, netdev_info_handler, &data) == 0)
 		return data.nlmode;
 	return NL80211_IFTYPE_UNSPECIFIED;
 }
@@ -797,8 +819,7 @@
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE)))
 		return -1;
 
-	return send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data,
-				  NULL, NULL);
+	return send_and_recv_resp(bss->drv, msg, netdev_info_handler, &data);
 }
 
 
@@ -810,8 +831,7 @@
 	};
 
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_GET_INTERFACE)) ||
-	    send_and_recv_msgs(bss->drv, msg, netdev_info_handler, &data,
-			       NULL, NULL))
+	    send_and_recv_resp(bss->drv, msg, netdev_info_handler, &data))
 		return -1;
 	return data.use_4addr;
 }
@@ -834,7 +854,7 @@
 	}
 
 	ret = send_and_recv(drv->global, w->nl_beacons, msg, NULL, NULL,
-			    NULL, NULL);
+			    NULL, NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Register beacons command "
 			   "failed: ret=%d (%s)",
@@ -1091,8 +1111,8 @@
 		struct nl_msg *msg;
 
 		msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_INTERFACE);
-		if (send_and_recv_msgs(drv, msg, get_mlo_info,
-				       &drv->sta_mlo_info, NULL, NULL))
+		if (send_and_recv_resp(drv, msg, get_mlo_info,
+				       &drv->sta_mlo_info))
 			return -1;
 	}
 
@@ -1249,7 +1269,7 @@
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: %s: failed to re-read MAC address",
 			   bss->ifname);
-	} else if (bss && os_memcmp(addr, bss->addr, ETH_ALEN) != 0) {
+	} else if (bss && !ether_addr_equal(addr, bss->addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Own MAC address on ifindex %d (%s) changed from "
 			   MACSTR " to " MACSTR,
@@ -1637,8 +1657,8 @@
 	msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SCAN);
 	os_memset(&arg, 0, sizeof(arg));
 	arg.drv = drv;
-	ret = send_and_recv_msgs(drv, msg, nl80211_get_assoc_freq_handler,
-				 &arg, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, nl80211_get_assoc_freq_handler,
+				 &arg);
 	if (ret == -EAGAIN) {
 		count++;
 		if (count >= 10) {
@@ -1671,8 +1691,8 @@
 	msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SCAN);
 	os_memset(&arg, 0, sizeof(arg));
 	arg.drv = drv;
-	ret = send_and_recv_msgs(drv, msg, nl80211_get_assoc_freq_handler,
-				 &arg, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, nl80211_get_assoc_freq_handler,
+				 &arg);
 	if (ret == -EAGAIN) {
 		count++;
 		if (count >= 10) {
@@ -1759,8 +1779,7 @@
 	sig_change->frequency = drv->assoc_freq;
 
 	msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SURVEY);
-	return send_and_recv_msgs(drv, msg, get_link_noise, sig_change,
-				  NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_link_noise, sig_change);
 }
 
 
@@ -1824,7 +1843,7 @@
 	struct nl_msg *msg;
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_INTERFACE);
-	return send_and_recv_msgs(drv, msg, get_channel_info, ci, NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_channel_info, ci);
 }
 
 
@@ -1873,7 +1892,7 @@
 		nlmsg_free(msg);
 		return -EINVAL;
 	}
-	if (send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL))
+	if (send_and_recv_cmd(drv, msg))
 		return -EINVAL;
 	return 0;
 }
@@ -1919,8 +1938,7 @@
 	}
 
 	alpha2[0] = '\0';
-	ret = send_and_recv_msgs(drv, msg, nl80211_get_country, alpha2,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, nl80211_get_country, alpha2);
 	if (!alpha2[0])
 		ret = -1;
 
@@ -1930,6 +1948,8 @@
 
 static int wpa_driver_nl80211_init_nl_global(struct nl80211_global *global)
 {
+	struct nl_cache *cache = NULL;
+	struct genl_family *family = NULL;
 	int ret;
 
 	global->nl_cb = nl_cb_alloc(NL_CB_DEFAULT);
@@ -2001,6 +2021,29 @@
 		/* Continue without vendor events */
 	}
 
+	/* Resolve maxattr for kernel support checks */
+	ret = genl_ctrl_alloc_cache(global->nl, &cache);
+	if (ret < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Could not allocate genl cache: %d (%s)",
+			   ret, nl_geterror(ret));
+		goto err;
+	}
+
+	family = genl_ctrl_search(cache, global->nl80211_id);
+	if (!family) {
+		wpa_printf(MSG_DEBUG,
+			   "nl80211: Could not get nl80211 family from cache: %d (%s)",
+			   ret, nl_geterror(ret));
+		goto err;
+	}
+
+	global->nl80211_maxattr = genl_family_get_maxattr(family);
+	wpa_printf(MSG_DEBUG, "nl80211: Maximum supported attribute ID: %u",
+		   global->nl80211_maxattr);
+	genl_family_put(family);
+	nl_cache_free(cache);
+
 	nl_cb_set(global->nl_cb, NL_CB_SEQ_CHECK, NL_CB_CUSTOM,
 		  no_seq_check, NULL);
 	nl_cb_set(global->nl_cb, NL_CB_VALID, NL_CB_CUSTOM,
@@ -2013,6 +2056,8 @@
 	return 0;
 
 err:
+	genl_family_put(family);
+	nl_cache_free(cache);
 	nl_destroy_handles(&global->nl_event);
 	nl_destroy_handles(&global->nl);
 	nl_cb_put(global->nl_cb);
@@ -2251,6 +2296,26 @@
 	struct wpa_driver_nl80211_data *drv;
 	struct i802_bss *bss;
 	unsigned int i;
+	char path[128], buf[200], *pos;
+	ssize_t len;
+	int ret;
+
+	ret = os_snprintf(path, sizeof(path), "/sys/class/net/%s/device/driver",
+			  ifname);
+	if (!os_snprintf_error(sizeof(path), ret)) {
+		len = readlink(path, buf, sizeof(buf));
+		if (len > 0 && (size_t) len < sizeof(buf)) {
+			buf[len] = '\0';
+			pos = strrchr(buf, '/');
+			if (pos)
+				pos++;
+			else
+				pos = buf;
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Initialize interface %s (driver: %s)",
+				   ifname, pos);
+		}
+	}
 
 	if (global_priv == NULL)
 		return NULL;
@@ -2389,7 +2454,7 @@
 	}
 
 	ret = send_and_recv(drv->global, nl_handle, msg, NULL, NULL,
-			    NULL, NULL);
+			    NULL, NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Register frame command "
 			   "failed (type=%u): ret=%d (%s)",
@@ -2505,6 +2570,13 @@
 					  5) < 0)
 		ret = -1;
 #endif /* CONFIG_P2P */
+#ifdef CONFIG_NAN_USD
+	/* NAN SDF Public Action */
+	if (nl80211_register_action_frame(bss,
+					  (u8 *) "\x04\x09\x50\x6f\x9a\x13",
+					  6) < 0)
+		ret = -1;
+#endif /* CONFIG_NAN_USD */
 #ifdef CONFIG_DPP
 	/* DPP Public Action */
 	if (nl80211_register_action_frame(bss,
@@ -2650,7 +2722,7 @@
 
 	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_UNEXPECTED_FRAME);
 	ret = send_and_recv(bss->drv->global, bss->nl_mgmt, msg, NULL, NULL,
-			    NULL, NULL);
+			    NULL, NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Register spurious class3 "
 			   "failed: ret=%d (%s)",
@@ -2804,7 +2876,7 @@
 	int ret;
 
 	msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_DEL_INTERFACE);
-	ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(bss->drv, msg);
 
 	wpa_printf(MSG_DEBUG, "nl80211: Delete P2P Device %s (0x%llx): %s",
 		   bss->ifname, (long long unsigned int) bss->wdev_id,
@@ -2819,7 +2891,7 @@
 
 	msg = nl80211_cmd_msg(bss, 0, start ? NL80211_CMD_START_P2P_DEVICE :
 			      NL80211_CMD_STOP_P2P_DEVICE);
-	ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(bss->drv, msg);
 
 	wpa_printf(MSG_DEBUG, "nl80211: %s P2P Device %s (0x%llx): %s",
 		   start ? "Start" : "Stop",
@@ -2890,8 +2962,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, qca_vendor_test_cmd_handler, drv,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, qca_vendor_test_cmd_handler, drv);
 	wpa_printf(MSG_DEBUG,
 		   "nl80211: QCA vendor test command returned %d (%s)",
 		   ret, strerror(-ret));
@@ -3038,7 +3109,7 @@
 		}
 	}
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -3099,8 +3170,10 @@
 
 	nl80211_remove_monitor_interface(drv);
 
-	if (is_ap_interface(drv->nlmode))
+	if (is_ap_interface(drv->nlmode)) {
 		wpa_driver_nl80211_del_beacon_all(bss);
+		nl80211_remove_links(bss);
+	}
 
 	if (drv->eapol_sock >= 0) {
 		eloop_unregister_read_sock(drv->eapol_sock);
@@ -3296,57 +3369,6 @@
 	return num_suites;
 }
 
-#if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA)
-static int wpa_driver_do_broadcom_acs(struct wpa_driver_nl80211_data *drv,
-				      struct drv_acs_params *params)
-{
-	struct nl_msg *msg;
-	struct nlattr *data;
-	int freq_list_len;
-	int ret = -1;
-
-	freq_list_len = int_array_len(params->freq_list);
-	wpa_printf(MSG_DEBUG, "%s: freq_list_len=%d",
-		    __func__, freq_list_len);
-
-	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR);
-	if (!msg ||
-	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_BRCM) ||
-	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
-			BRCM_VENDOR_SCMD_ACS) ||
-	    !(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
-	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HW_MODE, params->hw_mode) ||
-	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HT_ENABLED,
-		       params->ht_enabled) ||
-	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HT40_ENABLED,
-		       params->ht40_enabled) ||
-		nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_VHT_ENABLED,
-		       params->vht_enabled) ||
-	    nla_put_u16(msg, BRCM_VENDOR_ATTR_ACS_CHWIDTH, params->ch_width) ||
-	    (freq_list_len > 0 &&
-	     nla_put(msg, BRCM_VENDOR_ATTR_ACS_FREQ_LIST,
-		     sizeof(int) * freq_list_len, params->freq_list)))
-		goto fail;
-	nla_nest_end(msg, data);
-
-	wpa_printf(MSG_DEBUG,
-		   "nl80211: ACS Params: HW_MODE: %d HT: %d HT40: %d VHT: %d BW: %d",
-		   params->hw_mode, params->ht_enabled, params->ht40_enabled,
-		   params->vht_enabled, params->ch_width);
-
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
-	if (ret) {
-		wpa_printf(MSG_ERROR,
-			   "nl80211: BRCM Failed to invoke driver ACS function: %s",
-			   strerror(errno));
-	}
-
-	msg = NULL;
-fail:
-	nlmsg_free(msg);
-	return ret;
-}
-#endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */
 
 #if (defined(CONFIG_DRIVER_NL80211_BRCM) && !defined(WIFI_BRCM_OPEN_SOURCE_MULTI_AKM)) ||   \
 	defined(CONFIG_DRIVER_NL80211_SYNA)
@@ -3368,6 +3390,7 @@
 #endif /* (CONFIG_DRIVER_NL80211_BRCM && !WIFI_BRCM_OPEN_SOURCE_MULTI_AKM) ||
 	* CONFIG_DRIVER_NL80211_SYNA */
 
+
 #ifdef CONFIG_DRIVER_NL80211_QCA
 static int issue_key_mgmt_set_key(struct wpa_driver_nl80211_data *drv,
 				  const u8 *key, size_t key_len)
@@ -3387,7 +3410,7 @@
 		nlmsg_free(msg);
 		return -1;
 	}
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Key management set key failed: ret=%d (%s)",
@@ -3419,7 +3442,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, (void *) -1, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Key mgmt set key failed: ret=%d (%s)",
 			ret, strerror(-ret));
@@ -3458,7 +3481,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Set PMK failed: ret=%d (%s)",
 			   ret, strerror(-ret));
@@ -3639,7 +3662,7 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if ((ret == -ENOENT || ret == -ENOLINK) && alg == WPA_ALG_NONE)
 		ret = 0;
 	if (ret)
@@ -3708,7 +3731,7 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: set_key default failed; err=%d %s",
@@ -3821,7 +3844,6 @@
 {
 	int ret;
 	struct nl_msg *msg;
-	struct nl_sock *nl_connect = get_connect_handle(bss);
 
 	if (!(msg = nl80211_drv_msg(drv, 0, cmd)) ||
 	    nla_put_u16(msg, NL80211_ATTR_REASON_CODE, reason_code) ||
@@ -3832,11 +3854,8 @@
 		return -1;
 	}
 
-	if (nl_connect)
-		ret = send_and_recv(drv->global, nl_connect, msg,
-				    process_bss_event, bss, NULL, NULL);
-	else
-		ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv(drv->global, bss->nl_connect, msg,
+			    NULL, NULL, NULL, NULL, NULL);
 	if (ret) {
 		wpa_dbg(drv->ctx, MSG_DEBUG,
 			"nl80211: MLME command failed: reason=%u ret=%d (%s)",
@@ -3851,7 +3870,6 @@
 					 struct i802_bss *bss)
 {
 	int ret;
-	int drv_associated = drv->associated;
 
 	wpa_printf(MSG_DEBUG, "%s(reason_code=%d)", __func__, reason_code);
 	nl80211_mark_disconnected(drv);
@@ -3862,7 +3880,8 @@
 	 * For locally generated disconnect, supplicant already generates a
 	 * DEAUTH event, so ignore the event from NL80211.
 	 */
-	drv->ignore_next_local_disconnect = drv_associated && (ret == 0);
+	if (ret == 0)
+		drv->ignore_next_local_disconnect = send_event_marker(drv);
 
 	return ret;
 }
@@ -3873,7 +3892,6 @@
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	int ret;
-	int drv_associated = drv->associated;
 
 	if (drv->nlmode == NL80211_IFTYPE_ADHOC) {
 		nl80211_mark_disconnected(drv);
@@ -3891,7 +3909,8 @@
 	 * For locally generated deauthenticate, supplicant already generates a
 	 * DEAUTH event, so ignore the event from NL80211.
 	 */
-	drv->ignore_next_local_deauth = drv_associated && (ret == 0);
+	if (ret == 0)
+		drv->ignore_next_local_deauth = send_event_marker(drv);
 
 	return ret;
 }
@@ -4114,7 +4133,7 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_dbg(drv->ctx, MSG_DEBUG,
@@ -4194,6 +4213,7 @@
 {
 	struct wpa_driver_auth_params params;
 	struct i802_bss *bss = drv->first_bss;
+	u8 ap_mld_addr[ETH_ALEN];
 	int i;
 
 	wpa_printf(MSG_DEBUG, "nl80211: Try to authenticate again");
@@ -4219,8 +4239,10 @@
 	params.auth_data_len = drv->auth_data_len;
 	params.mld = drv->auth_mld;
 	params.mld_link_id = drv->auth_mld_link_id;
-	if (drv->auth_mld)
-		params.ap_mld_addr = drv->auth_ap_mld_addr;
+	if (drv->auth_mld) {
+		os_memcpy(ap_mld_addr, drv->auth_ap_mld_addr, ETH_ALEN);
+		params.ap_mld_addr = ap_mld_addr;
+	}
 
 	for (i = 0; i < 4; i++) {
 		if (drv->auth_wep_key_len[i]) {
@@ -4263,7 +4285,7 @@
 	size_t i;
 
 	for (i = 0; i < bss->n_links; i++) {
-		if (os_memcmp(bss->links[i].addr, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(bss->links[i].addr, addr)) {
 			wpa_printf(MSG_DEBUG,
 				   "nl80211: Use link freq=%d for address "
 				   MACSTR,
@@ -4299,9 +4321,11 @@
 
 	mgmt = (struct ieee80211_mgmt *) data;
 	fc = le_to_host16(mgmt->frame_control);
-	wpa_printf(MSG_DEBUG, "nl80211: send_mlme - da=" MACSTR
+	wpa_printf(MSG_DEBUG, "nl80211: send_mlme - da=" MACSTR " sa=" MACSTR
+		   " bssid=" MACSTR
 		   " noack=%d freq=%u no_cck=%d offchanok=%d wait_time=%u no_encrypt=%d fc=0x%x (%s) nlmode=%d",
-		   MAC2STR(mgmt->da), noack, freq, no_cck, offchanok, wait_time,
+		   MAC2STR(mgmt->da), MAC2STR(mgmt->sa), MAC2STR(mgmt->bssid),
+		   noack, freq, no_cck, offchanok, wait_time,
 		   no_encrypt, fc, fc2str(fc), drv->nlmode);
 
 	if ((is_sta_interface(drv->nlmode) ||
@@ -4428,7 +4452,7 @@
 	wpa_printf(MSG_DEBUG, "nl80211: send_mlme -> send_frame_cmd");
 	res = nl80211_send_frame_cmd(bss, freq, wait_time, data, data_len,
 				     use_cookie, no_cck, noack, offchanok,
-				     csa_offs, csa_offs_len);
+				     csa_offs, csa_offs_len, link_id);
 	if (!res)
 		drv->send_frame_link_id = link_id;
 
@@ -4454,7 +4478,7 @@
 
 static int nl80211_set_bss(struct i802_bss *bss, int cts, int preamble,
 			   int slot, int ht_opmode, int ap_isolate,
-			   const int *basic_rates)
+			   const int *basic_rates, int link_id)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct nl_msg *msg;
@@ -4470,12 +4494,14 @@
 	     nla_put_u16(msg, NL80211_ATTR_BSS_HT_OPMODE, ht_opmode)) ||
 	    (ap_isolate >= 0 &&
 	     nla_put_u8(msg, NL80211_ATTR_AP_ISOLATE, ap_isolate)) ||
-	    nl80211_put_basic_rates(msg, basic_rates)) {
+	    nl80211_put_basic_rates(msg, basic_rates) ||
+	    (link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
 	}
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -4534,7 +4560,7 @@
 	}
 	nlmsg_free(acl);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Failed to set MAC ACL: %d (%s)",
 			   ret, strerror(-ret));
@@ -4586,7 +4612,7 @@
 		return ret;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_ERROR,
 			   "nl80211: Mesh config set failed: %d (%s)",
@@ -4732,7 +4758,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 
 	switch (ret) {
 	case 0:
@@ -4926,7 +4952,8 @@
 
 #ifdef CONFIG_DRIVER_NL80211_QCA
 static void qca_set_allowed_ap_freqs(struct wpa_driver_nl80211_data *drv,
-				    const int *freqs, int num_freqs)
+				     const int *freqs, int num_freqs,
+				     int link_id)
 {
 	struct nl_msg *msg;
 	struct nlattr *params, *freqs_list;
@@ -4944,6 +4971,10 @@
 	    !(params = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)))
 		goto err;
 
+	if (link_id != NL80211_DRV_LINK_ID_NA &&
+	    nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_CONFIG_MLO_LINK_ID, link_id))
+		goto err;
+
 	freqs_list = nla_nest_start(
 		msg, QCA_WLAN_VENDOR_ATTR_CONFIG_AP_ALLOWED_FREQ_LIST);
 	if (!freqs_list)
@@ -4957,7 +4988,7 @@
 	nla_nest_end(msg, freqs_list);
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_ERROR,
 			   "nl80211: Failed set AP alllowed frequency list: %d (%s)",
@@ -5225,32 +5256,26 @@
 			 suites))
 		goto fail;
 
-	if ((params->key_mgmt_suites & WPA_KEY_MGMT_PSK) &&
+	if (wpa_key_mgmt_wpa_psk_no_sae(params->key_mgmt_suites) &&
 	    (drv->capa.flags2 & WPA_DRIVER_FLAGS2_4WAY_HANDSHAKE_AP_PSK) &&
-	    params->psk_len) {
-		if (nla_put(msg, NL80211_ATTR_PMK, params->psk_len, params->psk)) {
-			wpa_printf(MSG_ERROR, "nl80211: Setting PSK failed");
-			goto fail;
-		} else
-			wpa_printf(MSG_DEBUG, "nl80211: Setting PSK for offload");
-	}
+	    params->psk_len &&
+	    nla_put(msg, NL80211_ATTR_PMK, params->psk_len, params->psk))
+		goto fail;
 
 	if (wpa_key_mgmt_sae(params->key_mgmt_suites) &&
 	    (drv->capa.flags2 & WPA_DRIVER_FLAGS2_SAE_OFFLOAD_AP) &&
-	    params->sae_password) {
-		if (nla_put(msg, NL80211_ATTR_SAE_PASSWORD,
-		    os_strlen(params->sae_password), params->sae_password)) {
-			wpa_printf(MSG_ERROR, "nl80211: Setting SAE password failed");
-			goto fail;
-		} else
-			wpa_printf(MSG_DEBUG, "nl80211: SAE password for offload");
-	}
+	    params->sae_password &&
+	    nla_put(msg, NL80211_ATTR_SAE_PASSWORD,
+		    os_strlen(params->sae_password), params->sae_password))
+		goto fail;
+
+	if (nl80211_put_control_port(drv, msg) < 0)
+		goto fail;
 
 	if (params->key_mgmt_suites & WPA_KEY_MGMT_IEEE8021X_NO_WPA &&
 	    (!params->pairwise_ciphers ||
 	     params->pairwise_ciphers & (WPA_CIPHER_WEP104 | WPA_CIPHER_WEP40)) &&
-	    (nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, ETH_P_PAE) ||
-	     nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT)))
+	    nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT))
 		goto fail;
 
 	if (drv->device_ap_sme) {
@@ -5269,7 +5294,9 @@
 
 		flags |= NL80211_AP_SETTINGS_SA_QUERY_OFFLOAD_SUPPORT;
 
-		if (nla_put_u32(msg, NL80211_ATTR_AP_SETTINGS_FLAGS, flags))
+		if (nl80211_attr_supported(drv,
+					   NL80211_ATTR_AP_SETTINGS_FLAGS) &&
+		    nla_put_u32(msg, NL80211_ATTR_AP_SETTINGS_FLAGS, flags))
 			goto fail;
 	}
 
@@ -5403,7 +5430,8 @@
 	if (params->freq && nl80211_put_freq_params(msg, params->freq) < 0)
 		goto fail;
 
-	if (params->freq && params->freq->he_enabled) {
+	if (params->freq && params->freq->he_enabled &&
+	    nl80211_attr_supported(drv, NL80211_ATTR_HE_BSS_COLOR)) {
 		struct nlattr *bss_color;
 
 		bss_color = nla_nest_start(msg, NL80211_ATTR_HE_BSS_COLOR);
@@ -5455,10 +5483,15 @@
 #ifdef CONFIG_DRIVER_NL80211_QCA
 	if (cmd == NL80211_CMD_NEW_BEACON && params->allowed_freqs)
 		qca_set_allowed_ap_freqs(drv, params->allowed_freqs,
-					 int_array_len(params->allowed_freqs));
+					 int_array_len(params->allowed_freqs),
+					 params->mld_ap ? params->mld_link_id :
+					 NL80211_DRV_LINK_ID_NA);
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
-	ret = send_and_recv_msgs_connect_handle(drv, msg, bss, 1);
+	if (nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER))
+		goto fail;
+	ret = send_and_recv(drv->global, bss->nl_connect, msg, NULL, NULL, NULL,
+			    NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Beacon set failed: %d (%s)",
 			   ret, strerror(-ret));
@@ -5466,7 +5499,9 @@
 		link->beacon_set = 1;
 		nl80211_set_bss(bss, params->cts_protect, params->preamble,
 				params->short_slot_time, params->ht_opmode,
-				params->isolate, params->basic_rates);
+				params->isolate, params->basic_rates,
+				params->mld_ap ? params->mld_link_id :
+				NL80211_DRV_LINK_ID_NA);
 		nl80211_set_multicast_to_unicast(bss,
 						 params->multicast_to_unicast);
 		if (beacon_set && params->freq &&
@@ -5564,7 +5599,7 @@
 		}
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret == 0) {
 		nl80211_link_set_freq(bss, freq->link_id, freq->freq);
 		return 0;
@@ -5905,7 +5940,7 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: %s result: %d (%s)",
@@ -5976,7 +6011,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	wpa_printf(MSG_DEBUG, "nl80211: sta_remove -> DEL_STATION %s " MACSTR
 		   " --> %d (%s)",
 		   bss->ifname, MAC2STR(addr), ret, strerror(-ret));
@@ -6007,7 +6042,7 @@
 	}
 
 	msg = nl80211_ifindex_msg(drv, ifidx, 0, NL80211_CMD_DEL_INTERFACE);
-	if (send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL) == 0)
+	if (send_and_recv_cmd(drv, msg) == 0)
 		return;
 	wpa_printf(MSG_ERROR, "Failed to remove interface (ifidx=%d)", ifidx);
 }
@@ -6091,7 +6126,7 @@
 	    nla_put(msg, NL80211_ATTR_MAC, ETH_ALEN, addr))
 		goto fail;
 
-	ret = send_and_recv_msgs(drv, msg, handler, arg, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, handler, arg);
 	msg = NULL;
 	if (ret) {
 	fail:
@@ -6238,7 +6273,8 @@
 		nl80211_mgmt_unsubscribe(bss, "AP teardown");
 
 	nl80211_put_wiphy_data_ap(bss);
-	bss->flink->beacon_set = 0;
+	if (bss->flink)
+		bss->flink->beacon_set = 0;
 }
 
 
@@ -6272,8 +6308,8 @@
 
 	os_memset(&ext_arg, 0, sizeof(struct nl80211_ack_ext_arg));
 	ext_arg.ext_data = &cookie;
-	ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL,
-				 ack_handler_cookie, &ext_arg);
+	ret = send_and_recv(bss->drv->global, bss->drv->global->nl, msg,
+			    NULL, NULL, ack_handler_cookie, &ext_arg, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: tx_control_port failed: ret=%d (%s)",
@@ -6440,7 +6476,7 @@
 	if (nla_put(msg, NL80211_ATTR_STA_FLAGS2, sizeof(upd), &upd))
 		goto fail;
 
-	return send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(bss->drv, msg);
 fail:
 	nlmsg_free(msg);
 	return -ENOBUFS;
@@ -6463,7 +6499,7 @@
 	    nla_put_u16(msg, NL80211_ATTR_AIRTIME_WEIGHT, weight))
 		goto fail;
 
-	ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(bss->drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: SET_STATION[AIRTIME_WEIGHT] failed: ret=%d (%s)",
@@ -6513,7 +6549,8 @@
 	int ret;
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_LEAVE_IBSS);
-	ret = send_and_recv_msgs_connect_handle(drv, msg, drv->first_bss, 1);
+	ret = send_and_recv(drv->global, drv->first_bss->nl_connect, msg, NULL,
+			    NULL, NULL, NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Leave IBSS failed: ret=%d "
 			   "(%s)", ret, strerror(-ret));
@@ -6643,7 +6680,7 @@
 	    params->key_mgmt_suite == WPA_KEY_MGMT_PSK_SHA256 ||
 	    params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_SHA384) {
 		wpa_printf(MSG_DEBUG, "  * control port");
-		if (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT))
+		if (nl80211_put_control_port(drv, msg))
 			goto fail;
 	}
 
@@ -6660,7 +6697,10 @@
 	if (ret < 0)
 		goto fail;
 
-	ret = send_and_recv_msgs_connect_handle(drv, msg, drv->first_bss, 1);
+	if (nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER))
+		goto fail;
+	ret = send_and_recv(drv->global, drv->first_bss->nl_connect, msg, NULL,
+			    NULL, NULL, NULL, NULL);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Join IBSS failed: ret=%d (%s)",
@@ -6793,6 +6833,9 @@
 				    mld_params->mld_links[link_id].bssid) ||
 			    nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ,
 					mld_params->mld_links[link_id].freq) ||
+			    (mld_params->mld_links[i].disabled &&
+			     nla_put_flag(msg,
+					  NL80211_ATTR_MLO_LINK_DISABLED)) ||
 			    (mld_params->mld_links[link_id].ies &&
 			     mld_params->mld_links[i].ies_len &&
 			     nla_put(msg, NL80211_ATTR_IE,
@@ -6891,8 +6934,20 @@
 
 		if (params->wpa_proto & WPA_PROTO_WPA)
 			ver |= NL80211_WPA_VERSION_1;
-		if (params->wpa_proto & WPA_PROTO_RSN)
-			ver |= NL80211_WPA_VERSION_2;
+		if (params->wpa_proto & WPA_PROTO_RSN) {
+#if !defined(CONFIG_DRIVER_NL80211_BRCM) && !defined(CONFIG_DRIVER_NL80211_SYNA)
+			/*
+			 * NL80211_ATTR_SAE_PASSWORD is related and was added
+			 * at the same time as NL80211_WPA_VERSION_3.
+			 */
+			if (nl80211_attr_supported(drv,
+						   NL80211_ATTR_SAE_PASSWORD) &&
+			    wpa_key_mgmt_sae(params->key_mgmt_suite))
+				ver |= NL80211_WPA_VERSION_3;
+			else
+#endif
+				ver |= NL80211_WPA_VERSION_2;
+		}
 
 		wpa_printf(MSG_DEBUG, "  * WPA Versions 0x%x", ver);
 		if (nla_put_u32(msg, NL80211_ATTR_WPA_VERSIONS, ver))
@@ -7086,15 +7141,14 @@
 			return -1;
 	}
 
-	if (nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT))
+	if (nl80211_put_control_port(drv, msg))
 		return -1;
 
 	if (params->key_mgmt_suite == WPA_KEY_MGMT_IEEE8021X_NO_WPA &&
 	    (params->pairwise_suite == WPA_CIPHER_NONE ||
 	     params->pairwise_suite == WPA_CIPHER_WEP104 ||
 	     params->pairwise_suite == WPA_CIPHER_WEP40) &&
-	    (nla_put_u16(msg, NL80211_ATTR_CONTROL_PORT_ETHERTYPE, ETH_P_PAE) ||
-	     nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT)))
+	    nla_put_flag(msg, NL80211_ATTR_CONTROL_PORT_NO_ENCRYPT))
 		return -1;
 
 	if (params->rrm_used) {
@@ -7244,7 +7298,10 @@
 	if (ret)
 		goto fail;
 
-	ret = send_and_recv_msgs_connect_handle(drv, msg, bss, 1);
+	if (nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER))
+		goto fail;
+	ret = send_and_recv(drv->global, bss->nl_connect, msg, NULL, NULL, NULL,
+			    NULL, NULL);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: MLME connect failed: ret=%d "
@@ -7302,6 +7359,7 @@
 {
 	struct i802_bss *bss = priv;
 	struct wpa_driver_nl80211_data *drv = bss->drv;
+	struct nl80211_err_info err_info;
 	int ret = -1;
 	struct nl_msg *msg;
 
@@ -7319,11 +7377,6 @@
 
 		if (wpa_driver_nl80211_set_mode(priv, nlmode) < 0)
 			return -1;
-		if (wpa_key_mgmt_sae(params->key_mgmt_suite) ||
-		    wpa_key_mgmt_sae(params->allowed_key_mgmts))
-			bss->use_nl_connect = 1;
-		else
-			bss->use_nl_connect = 0;
 
 		return wpa_driver_nl80211_connect(drv, params, bss);
 	}
@@ -7360,13 +7413,45 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs_connect_handle(drv, msg, drv->first_bss, 1);
-	msg = NULL;
+	if (!TEST_FAIL_TAG("assoc")) {
+		if (nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER))
+			goto fail;
+		ret = send_and_recv(drv->global, drv->first_bss->nl_connect,
+				    msg, NULL, NULL, NULL, NULL, &err_info);
+		msg = NULL;
+	} else {
+		int i;
+
+		/* Error and force TEST_FAIL checking for each link */
+		ret = -EINVAL;
+		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+			if (!(params->mld_params.valid_links & BIT(i)))
+				continue;
+
+			if (TEST_FAIL_TAG("link"))
+				err_info.link_id = i;
+		}
+	}
+
 	if (ret) {
 		wpa_dbg(drv->ctx, MSG_DEBUG,
 			"nl80211: MLME command failed (assoc): ret=%d (%s)",
 			ret, strerror(-ret));
 		nl80211_dump_scan(drv);
+
+		/* Mark failed link within params */
+		if (err_info.link_id >= 0) {
+			if (err_info.link_id >= MAX_NUM_MLD_LINKS ||
+			    !(params->mld_params.valid_links &
+			      BIT(err_info.link_id))) {
+				wpa_printf(MSG_DEBUG,
+					   "nl80211: Invalid errorred link_id %d",
+					   err_info.link_id);
+				goto fail;
+			}
+			params->mld_params.mld_links[err_info.link_id].error =
+				ret;
+		}
 	} else {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Association request send successfully");
@@ -7391,7 +7476,7 @@
 	if (!msg || nla_put_u32(msg, NL80211_ATTR_IFTYPE, mode))
 		goto fail;
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (!ret)
 		return 0;
@@ -7658,7 +7743,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (!ret)
 		return 0;
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to set STA flag: %d (%s)",
@@ -7725,7 +7810,7 @@
 	}
 
 	os_memset(seq, 0, 6);
-	res = send_and_recv_msgs(drv, msg, get_key_handler, seq, NULL, NULL);
+	res = send_and_recv_resp(drv, msg, get_key_handler, seq);
 	if (res) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Failed to get current TX sequence for a key (link_id=%d idx=%d): %d (%s)",
@@ -7755,7 +7840,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (!ret)
 		return 0;
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to set RTS threshold %d: "
@@ -7783,7 +7868,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (!ret)
 		return 0;
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to set fragmentation threshold "
@@ -7805,7 +7890,7 @@
 	 * XXX: FIX! this needs to flush all VLANs too
 	 */
 	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_DEL_STATION);
-	res = send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	res = send_and_recv_cmd(bss->drv, msg);
 	if (res) {
 		wpa_printf(MSG_DEBUG, "nl80211: Station flush failed: ret=%d "
 			   "(%s)", res, strerror(-res));
@@ -8136,7 +8221,7 @@
 		return -ENOBUFS;
 	}
 
-	return send_and_recv_msgs(drv, msg, get_sta_handler, data, NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_sta_handler, data);
 }
 
 
@@ -8152,8 +8237,7 @@
 		return -ENOBUFS;
 	}
 
-	return send_and_recv_msgs(bss->drv, msg, get_sta_handler, data,
-				  NULL, NULL);
+	return send_and_recv_resp(bss->drv, msg, get_sta_handler, data);
 }
 
 
@@ -8215,10 +8299,10 @@
 	    nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id))
 		goto fail;
 
-	res = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	res = send_and_recv_cmd(drv, msg);
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: TX queue param set: queue=%d aifs=%d cw_min=%d cw_max=%d burst_time=%d --> res=%d",
-		   queue, aifs, cw_min, cw_max, burst_time, res);
+		   "nl80211: link=%d: TX queue param set: queue=%d aifs=%d cw_min=%d cw_max=%d burst_time=%d --> res=%d",
+		   link_id, queue, aifs, cw_min, cw_max, burst_time, res);
 	if (res == 0)
 		return 0;
 	msg = NULL;
@@ -8250,7 +8334,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret < 0) {
 		wpa_printf(MSG_ERROR, "nl80211: NL80211_ATTR_STA_VLAN (addr="
 			   MACSTR " ifname=%s vlan_id=%d) failed: %d (%s)",
@@ -8769,7 +8853,7 @@
 	struct wpa_driver_nl80211_data *drv;
 	dl_list_for_each(drv, &global->interfaces,
 			 struct wpa_driver_nl80211_data, list) {
-		if (os_memcmp(addr, drv->first_bss->addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, drv->first_bss->addr))
 			return 1;
 	}
 	return 0;
@@ -9086,7 +9170,7 @@
 				  const u8 *buf, size_t buf_len,
 				  int save_cookie, int no_cck, int no_ack,
 				  int offchanok, const u16 *csa_offs,
-				  size_t csa_offs_len)
+				  size_t csa_offs_len, int link_id)
 {
 	struct wpa_driver_nl80211_data *drv = bss->drv;
 	struct nl_msg *msg;
@@ -9099,6 +9183,8 @@
 	wpa_hexdump(MSG_MSGDUMP, "CMD_FRAME", buf, buf_len);
 
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_FRAME)) ||
+	    ((link_id != NL80211_DRV_LINK_ID_NA) &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id)) ||
 	    (freq && nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq)) ||
 	    (wait && nla_put_u32(msg, NL80211_ATTR_DURATION, wait)) ||
 	    (offchanok && ((drv->capa.flags & WPA_DRIVER_FLAGS_OFFCHANNEL_TX) ||
@@ -9112,7 +9198,7 @@
 		goto fail;
 
 	cookie = 0;
-	ret = send_and_recv_msgs(drv, msg, cookie_handler, &cookie, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, cookie_handler, &cookie);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Frame command failed: ret=%d "
@@ -9170,9 +9256,14 @@
 	    bss->flink->beacon_set)
 		offchanok = 0;
 
-	wpa_printf(MSG_DEBUG, "nl80211: Send Action frame (ifindex=%d, "
-		   "freq=%u MHz wait=%d ms no_cck=%d offchanok=%d)",
-		   drv->ifindex, freq, wait_time, no_cck, offchanok);
+	if (!freq && is_sta_interface(drv->nlmode))
+		offchanok = 0;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Send Action frame (ifindex=%d, freq=%u MHz wait=%d ms no_cck=%d offchanok=%d dst="
+		   MACSTR " src=" MACSTR " bssid=" MACSTR ")",
+		   drv->ifindex, freq, wait_time, no_cck, offchanok,
+		   MAC2STR(dst), MAC2STR(src), MAC2STR(bssid));
 
 	buf = os_zalloc(24 + data_len);
 	if (buf == NULL)
@@ -9185,7 +9276,7 @@
 	os_memcpy(hdr->addr2, src, ETH_ALEN);
 	os_memcpy(hdr->addr3, bssid, ETH_ALEN);
 
-	if (os_memcmp(bss->addr, src, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(bss->addr, src)) {
 		wpa_printf(MSG_DEBUG, "nl80211: Use random TA " MACSTR,
 			   MAC2STR(src));
 		os_memcpy(bss->rand_addr, src, ETH_ALEN);
@@ -9224,8 +9315,9 @@
 						   wait_time, NULL, 0, 0, -1);
 	else
 		ret = nl80211_send_frame_cmd(bss, freq, wait_time, buf,
-					     24 + data_len,
-					     1, no_cck, 0, offchanok, NULL, 0);
+					     24 + data_len, 1, no_cck, 0,
+					     offchanok, NULL, 0,
+					     NL80211_DRV_LINK_ID_NA);
 
 	os_free(buf);
 	return ret;
@@ -9246,7 +9338,7 @@
 		return;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: wait cancel failed: ret=%d "
 			   "(%s)", ret, strerror(-ret));
@@ -9278,6 +9370,46 @@
 }
 
 
+static int nl80211_put_any_link_id(struct nl_msg *msg,
+			       struct driver_sta_mlo_info *mlo,
+			       int freq)
+{
+	int i;
+	int link_id = -1;
+	int any_valid_link_id = -1;
+
+	if (!mlo->valid_links)
+		return 0;
+
+	/* First try to pick a link that uses the same band */
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(mlo->valid_links & BIT(i)))
+			continue;
+
+		if (any_valid_link_id == -1)
+			any_valid_link_id = i;
+
+		if (is_same_band(freq, mlo->links[i].freq)) {
+			link_id = i;
+			break;
+		}
+	}
+
+	/* Use any valid link ID if no band match was found */
+	if (link_id == -1)
+		link_id = any_valid_link_id;
+
+	if (link_id == -1) {
+		wpa_printf(MSG_INFO,
+			   "nl80211: No valid Link ID found for freq %u", freq);
+		return 0;
+	}
+
+	wpa_printf(MSG_DEBUG, "nl80211: Add Link ID %d", link_id);
+	return nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, link_id);
+}
+
+
 static int wpa_driver_nl80211_remain_on_channel(void *priv, unsigned int freq,
 						unsigned int duration)
 {
@@ -9289,13 +9421,14 @@
 
 	if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_REMAIN_ON_CHANNEL)) ||
 	    nla_put_u32(msg, NL80211_ATTR_WIPHY_FREQ, freq) ||
-	    nla_put_u32(msg, NL80211_ATTR_DURATION, duration)) {
+	    nla_put_u32(msg, NL80211_ATTR_DURATION, duration) ||
+	    nl80211_put_any_link_id(msg, &drv->sta_mlo_info, freq)) {
 		nlmsg_free(msg);
 		return -1;
 	}
 
 	cookie = 0;
-	ret = send_and_recv_msgs(drv, msg, cookie_handler, &cookie, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, cookie_handler, &cookie);
 	if (ret == 0) {
 		wpa_printf(MSG_DEBUG, "nl80211: Remain-on-channel cookie "
 			   "0x%llx for freq=%u MHz duration=%u",
@@ -9335,7 +9468,7 @@
 		return -1;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret == 0)
 		return 0;
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to cancel remain-on-channel: "
@@ -9432,7 +9565,7 @@
 
 	nla_nest_end(msg, bands);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Set TX rates failed: ret=%d "
 			   "(%s)", ret, strerror(-ret));
@@ -9454,6 +9587,9 @@
 	int ret;
 	u8 link_id;
 
+	if (bss->n_links == 0)
+		return;
+
 	while (bss->links[0].link_id != NL80211_DRV_LINK_ID_NA) {
 		struct i802_link *link = &bss->links[0];
 
@@ -9489,7 +9625,7 @@
 			return;
 		}
 
-		ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+		ret = send_and_recv_cmd(drv, msg);
 		if (ret) {
 			wpa_printf(MSG_ERROR,
 				   "nl80211: remove link (%d) failed. ret=%d (%s)",
@@ -9588,7 +9724,7 @@
 	}
 	nla_nest_end(msg, cqm);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -9626,7 +9762,7 @@
 	struct nl_msg *msg;
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_INTERFACE);
-	return send_and_recv_msgs(drv, msg, get_channel_width, sig, NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_channel_width, sig);
 }
 
 
@@ -9710,8 +9846,7 @@
 	struct nl_msg *msg;
 
 	msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SURVEY);
-	return send_and_recv_msgs(drv, msg, get_links_noise, mlo_sig,
-				  NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_links_noise, mlo_sig);
 }
 
 
@@ -9765,8 +9900,7 @@
 	struct nl_msg *msg;
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_INTERFACE);
-	return send_and_recv_msgs(drv, msg, get_links_channel_width, mlo_sig,
-				  NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_links_channel_width, mlo_sig);
 }
 
 
@@ -9887,6 +10021,13 @@
 {
 	struct nl80211_global *global;
 	struct netlink_config *cfg;
+	struct utsname name;
+
+	if (uname(&name) == 0) {
+		wpa_printf(MSG_DEBUG, "nl80211: Kernel version: %s %s (%s; %s)",
+			   name.sysname, name.release,
+			   name.version, name.machine);
+	}
 
 	global = os_zalloc(sizeof(*global));
 	if (global == NULL)
@@ -9997,7 +10138,7 @@
 		return -ENOBUFS;
 	}
 
-	return send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(bss->drv, msg);
 }
 
 
@@ -10073,7 +10214,7 @@
 	msg = nl80211_bss_msg(bss, 0, NL80211_CMD_FLUSH_PMKSA);
 	if (!msg)
 		return -ENOBUFS;
-	return send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(bss->drv, msg);
 }
 
 
@@ -10237,8 +10378,8 @@
 
 	do {
 		wpa_printf(MSG_DEBUG, "nl80211: Fetch survey data");
-		err = send_and_recv_msgs(drv, msg, survey_handler,
-					 survey_results, NULL, NULL);
+		err = send_and_recv_resp(drv, msg, survey_handler,
+					 survey_results);
 	} while (err > 0);
 
 	if (err)
@@ -10278,7 +10419,7 @@
 
 	nla_nest_end(msg, replay_nested);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret == -EOPNOTSUPP) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Driver does not support rekey offload");
@@ -10345,7 +10486,7 @@
 		return;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, cookie_handler, &cookie, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, cookie_handler, &cookie);
 	if (ret < 0) {
 		wpa_printf(MSG_DEBUG, "nl80211: Client probe request for "
 			   MACSTR " failed: ret=%d (%s)",
@@ -10371,7 +10512,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(bss->drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(bss->drv, msg);
 	if (ret < 0) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Setting PS state %s failed: %d (%s)",
@@ -10431,7 +10572,7 @@
 		return -1;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret == 0)
 		return 0;
 	wpa_printf(MSG_DEBUG, "nl80211: Failed to start radar detection: "
@@ -10490,7 +10631,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 #else /* CONFIG_DRIVER_NL80211_QCA */
 	wpa_printf(MSG_ERROR,
 		   "nl80211: Setting TX link for TDLS Discovery Response not supported");
@@ -10528,7 +10669,7 @@
 	    nla_put(msg, NL80211_ATTR_IE, len, buf))
 		goto fail;
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 
 fail:
 	nlmsg_free(msg);
@@ -10578,7 +10719,7 @@
 		return -ENOBUFS;
 	}
 
-	res = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	res = send_and_recv_cmd(drv, msg);
 	wpa_printf(MSG_DEBUG, "nl80211: TDLS_OPER: oper=%d mac=" MACSTR
 		   " --> res=%d (%s)", nl80211_oper, MAC2STR(peer), res,
 		   strerror(-res));
@@ -10612,7 +10753,7 @@
 		return ret;
 	}
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -10638,7 +10779,7 @@
 		return -ENOBUFS;
 	}
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 #endif /* CONFIG TDLS */
@@ -10783,7 +10924,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: update_ft_ies failed "
 			   "err=%d (%s)", ret, strerror(-ret));
@@ -10811,7 +10952,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: update_dh_ie failed err=%d (%s)",
@@ -10953,9 +11094,9 @@
 			  drv->retry_auth ? "retry_auth=1\n" : "",
 			  drv->use_monitor ? "use_monitor=1\n" : "",
 			  drv->ignore_next_local_disconnect ?
-			  "ignore_next_local_disconnect=1\n" : "",
+			  "ignore_next_local_disconnect\n" : "",
 			  drv->ignore_next_local_deauth ?
-			  "ignore_next_local_deauth=1\n" : "");
+			  "ignore_next_local_deauth\n" : "");
 	if (os_snprintf_error(end - pos, res))
 		return pos - buf;
 	pos += res;
@@ -11063,8 +11204,8 @@
 	if (msg &&
 	    nl80211_cmd(drv, msg, 0, NL80211_CMD_GET_REG) &&
 	    nla_put_u32(msg, NL80211_ATTR_WIPHY, drv->wiphy_idx) == 0) {
-		if (send_and_recv_msgs(drv, msg, nl80211_get_country,
-				       alpha2, NULL, NULL) == 0 &&
+		if (send_and_recv_resp(drv, msg, nl80211_get_country,
+				       alpha2) == 0 &&
 		    alpha2[0]) {
 			res = os_snprintf(pos, end - pos, "country=%s\n",
 					  alpha2);
@@ -11117,7 +11258,7 @@
 	int i;
 
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: Channel switch request (cs_count=%u block_tx=%u freq=%d channel=%d sec_channel_offset=%d width=%d cf1=%d cf2=%d puncturing_bitmap=0x%04x%s%s%s)",
+		   "nl80211: Channel switch request (cs_count=%u block_tx=%u freq=%d channel=%d sec_channel_offset=%d width=%d cf1=%d cf2=%d puncturing_bitmap=0x%04x link_id=%d%s%s%s)",
 		   settings->cs_count, settings->block_tx,
 		   settings->freq_params.freq,
 		   settings->freq_params.channel,
@@ -11126,6 +11267,7 @@
 		   settings->freq_params.center_freq1,
 		   settings->freq_params.center_freq2,
 		   settings->punct_bitmap,
+		   settings->link_id,
 		   settings->freq_params.ht_enabled ? " ht" : "",
 		   settings->freq_params.vht_enabled ? " vht" : "",
 		   settings->freq_params.he_enabled ? " he" : "");
@@ -11199,7 +11341,9 @@
 	     nla_put_flag(msg, NL80211_ATTR_CH_SWITCH_BLOCK_TX)) ||
 	    (settings->punct_bitmap &&
 	     nla_put_u32(msg, NL80211_ATTR_PUNCT_BITMAP,
-			 settings->punct_bitmap)))
+			 settings->punct_bitmap)) ||
+	    (settings->link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, NL80211_ATTR_MLO_LINK_ID, settings->link_id)))
 		goto error;
 
 	/* beacon_after params */
@@ -11226,7 +11370,7 @@
 		goto fail;
 
 	nla_nest_end(msg, beacon_csa);
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: switch_channel failed err=%d (%s)",
 			   ret, strerror(-ret));
@@ -11307,7 +11451,7 @@
 	}
 
 	nla_nest_end(msg, beacon_cca);
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: switch_color failed err=%d (%s)",
@@ -11348,7 +11492,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: add_ts failed err=%d (%s)",
 			   ret, strerror(-ret));
@@ -11375,7 +11519,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: del_ts failed err=%d (%s)",
 			   ret, strerror(-ret));
@@ -11477,12 +11621,10 @@
 		    0)
 			goto fail;
 		/* This test vendor_cmd can be used with nl80211 commands that
-		 * need the connect nl_sock, so use the owner-setting variant
-		 * of send_and_recv_msgs(). */
-		ret = send_and_recv_msgs_owner(drv, msg,
-					       get_connect_handle(bss), 0,
-					       cmd_reply_handler, buf,
-					       NULL, NULL);
+		 * need the connect nl_sock, so use the variant that takes in
+		 * bss->nl_connect as the handle. */
+		ret = send_and_recv(drv->global, bss->nl_connect, msg,
+				    cmd_reply_handler, buf, NULL, NULL, NULL);
 		if (ret)
 			wpa_printf(MSG_DEBUG, "nl80211: command failed err=%d",
 				   ret);
@@ -11506,8 +11648,7 @@
 		     data_len, data)))
 		goto fail;
 
-	ret = send_and_recv_msgs(drv, msg, vendor_reply_handler, buf,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, vendor_reply_handler, buf);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: vendor command failed err=%d",
 			   ret);
@@ -11536,7 +11677,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: Setting QoS Map failed");
 
@@ -11571,8 +11712,7 @@
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_GET_WOWLAN);
 
-	ret = send_and_recv_msgs(drv, msg, get_wowlan_handler, &wowlan_enabled,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, get_wowlan_handler, &wowlan_enabled);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Getting wowlan status failed");
 		return 0;
@@ -11619,7 +11759,7 @@
 
 	nla_nest_end(msg, wowlan_triggers);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG, "nl80211: Setting wowlan failed");
 
@@ -11658,7 +11798,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -11686,7 +11826,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -11743,7 +11883,7 @@
 	nla_nest_end(msg, nlbssids);
 	nla_nest_end(msg, params);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 
 fail:
 	nlmsg_free(msg);
@@ -11781,7 +11921,7 @@
 	}
 	nla_nest_end(msg, params);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 #endif /* CONFIG_DRIVER_NL80211_QCA */
@@ -11820,7 +11960,7 @@
 		}
 		nla_nest_end(msg, params);
 
-		ret = send_and_recv_msgs(drv, msg, NULL, (void *) -1, NULL, NULL);
+		ret = send_and_recv_cmd(drv, msg);
 		if (ret) {
 			wpa_printf(MSG_ERROR, "nl80211: p2p set macaddr failed: ret=%d (%s)",
 				ret, strerror(-ret));
@@ -11991,7 +12131,10 @@
 	if (nl80211_put_mesh_config(msg, &params->conf) < 0)
 		goto fail;
 
-	ret = send_and_recv_msgs_connect_handle(drv, msg, bss, 1);
+	if (nla_put_flag(msg, NL80211_ATTR_SOCKET_OWNER))
+		return -1;
+	ret = send_and_recv(drv->global, bss->nl_connect, msg, NULL, NULL, NULL,
+			    NULL, NULL);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: mesh join failed: ret=%d (%s)",
@@ -12048,7 +12191,8 @@
 
 	wpa_printf(MSG_DEBUG, "nl80211: mesh leave (ifindex=%d)", drv->ifindex);
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_LEAVE_MESH);
-	ret = send_and_recv_msgs_connect_handle(drv, msg, bss, 0);
+	ret = send_and_recv(drv->global, bss->nl_connect, msg, NULL, NULL, NULL,
+			    NULL, NULL);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: mesh leave failed: ret=%d (%s)",
 			   ret, strerror(-ret));
@@ -12084,7 +12228,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: mesh link probe to " MACSTR
 			   " failed: ret=%d (%s)",
@@ -12489,19 +12633,22 @@
 	    add_acs_ch_list(msg, params->freq_list) ||
 	    add_acs_freq_list(msg, params->freq_list) ||
 	    (params->edmg_enabled &&
-	     nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_EDMG_ENABLED))) {
+	     nla_put_flag(msg, QCA_WLAN_VENDOR_ATTR_ACS_EDMG_ENABLED)) ||
+	    (params->link_id != NL80211_DRV_LINK_ID_NA &&
+	     nla_put_u8(msg, QCA_WLAN_VENDOR_ATTR_ACS_LINK_ID,
+			params->link_id))) {
 		nlmsg_free(msg);
 		return -ENOBUFS;
 	}
 	nla_nest_end(msg, data);
 
 	wpa_printf(MSG_DEBUG,
-		   "nl80211: ACS Params: HW_MODE: %d HT: %d HT40: %d VHT: %d EHT: %d BW: %d EDMG: %d",
+		   "nl80211: ACS Params: HW_MODE: %d HT: %d HT40: %d VHT: %d EHT: %d BW: %d EDMG: %d, link_id: %d",
 		   params->hw_mode, params->ht_enabled, params->ht40_enabled,
 		   params->vht_enabled, params->eht_enabled, params->ch_width,
-		   params->edmg_enabled);
+		   params->edmg_enabled, params->link_id);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Failed to invoke driver ACS function: %s",
@@ -12559,7 +12706,7 @@
 	}
 	nla_nest_end(msg, data);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Driver setband function failed: %s",
@@ -12761,11 +12908,10 @@
 
 	if (freq_list)
 		os_memset(freq_list, 0, *num * sizeof(struct weighted_pcl));
-	ret = send_and_recv_msgs(drv, msg, preferred_freq_info_handler, &param,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, preferred_freq_info_handler, &param);
 	if (ret) {
 		wpa_printf(MSG_ERROR,
-			   "%s: err in send_and_recv_msgs", __func__);
+			   "%s: err in send_and_recv_resp", __func__);
 		return ret;
 	}
 
@@ -12816,10 +12962,10 @@
 	}
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
-		wpa_printf(MSG_ERROR, "%s: err in send_and_recv_msgs",
+		wpa_printf(MSG_ERROR, "%s: err in send_and_recv_cmd",
 			   __func__);
 		return ret;
 	}
@@ -12872,7 +13018,7 @@
 		goto fail;
 
 	nla_nest_end(msg, container);
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
@@ -12907,7 +13053,7 @@
 		return -1;
 	}
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 }
 
 
@@ -12946,7 +13092,7 @@
 
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_ERROR,
@@ -13137,9 +13283,9 @@
 	nla_nest_end(msg, attr1);
 	nla_nest_end(msg, attr);
 
-	ret = send_and_recv_msgs(drv, msg,
+	ret = send_and_recv_resp(drv, msg,
 				 nl80211_get_bss_transition_status_handler,
-				 info, NULL, NULL);
+				 info);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_ERROR,
@@ -13192,7 +13338,7 @@
 
 	nla_nest_end(msg, attr);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_ERROR,
@@ -13263,7 +13409,7 @@
 	nla_nest_end(msg, nlpeers);
 	nla_nest_end(msg, attr);
 
-	return send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	return send_and_recv_cmd(drv, msg);
 
 fail:
 	nlmsg_free(msg);
@@ -13359,7 +13505,7 @@
 	}
 	nla_nest_end(msg, attr);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret)
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Set secure ranging context failed: ret=%d (%s)",
@@ -13374,6 +13520,60 @@
 
 #endif /* CONFIG_DRIVER_NL80211_QCA */
 
+
+#if defined(CONFIG_DRIVER_NL80211_BRCM) || defined(CONFIG_DRIVER_NL80211_SYNA)
+static int wpa_driver_do_broadcom_acs(struct wpa_driver_nl80211_data *drv,
+				      struct drv_acs_params *params)
+{
+	struct nl_msg *msg;
+	struct nlattr *data;
+	int freq_list_len;
+	int ret = -1;
+
+	freq_list_len = int_array_len(params->freq_list);
+	wpa_printf(MSG_DEBUG, "%s: freq_list_len=%d",
+		   __func__, freq_list_len);
+
+	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_VENDOR);
+	if (!msg ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, OUI_BRCM) ||
+	    nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD,
+			BRCM_VENDOR_SCMD_ACS) ||
+	    !(data = nla_nest_start(msg, NL80211_ATTR_VENDOR_DATA)) ||
+	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HW_MODE, params->hw_mode) ||
+	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HT_ENABLED,
+		       params->ht_enabled) ||
+	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_HT40_ENABLED,
+		       params->ht40_enabled) ||
+	    nla_put_u8(msg, BRCM_VENDOR_ATTR_ACS_VHT_ENABLED,
+		       params->vht_enabled) ||
+	    nla_put_u16(msg, BRCM_VENDOR_ATTR_ACS_CHWIDTH, params->ch_width) ||
+	    (freq_list_len > 0 &&
+	     nla_put(msg, BRCM_VENDOR_ATTR_ACS_FREQ_LIST,
+		     sizeof(int) * freq_list_len, params->freq_list)))
+		goto fail;
+	nla_nest_end(msg, data);
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: ACS Params: HW_MODE: %d HT: %d HT40: %d VHT: %d BW: %d",
+		   params->hw_mode, params->ht_enabled, params->ht40_enabled,
+		   params->vht_enabled, params->ch_width);
+
+	ret = send_and_recv_cmd(drv, msg);
+	if (ret) {
+		wpa_printf(MSG_ERROR,
+			   "nl80211: BRCM Failed to invoke driver ACS function: %s",
+			   strerror(errno));
+	}
+
+	msg = NULL;
+fail:
+	nlmsg_free(msg);
+	return ret;
+}
+#endif /* CONFIG_DRIVER_NL80211_BRCM || CONFIG_DRIVER_NL80211_SYNA */
+
+
 static int nl80211_do_acs(void *priv, struct drv_acs_params *params)
 {
 #if defined(CONFIG_DRIVER_NL80211_QCA) || defined(CONFIG_DRIVER_NL80211_BRCM) \
@@ -13629,7 +13829,7 @@
 	    nl80211_put_fils_connect_params(drv, params, msg))
 		goto fail;
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret)
 		wpa_dbg(drv->ctx, MSG_DEBUG,
@@ -13672,7 +13872,7 @@
 	    (params->bssid &&
 	     nla_put(msg, NL80211_ATTR_BSSID, ETH_ALEN, params->bssid)))
 		goto fail;
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
@@ -13712,7 +13912,7 @@
 		bss->added_if_into_bridge = 0;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret && val && nl80211_get_4addr(bss) == 1) {
 		wpa_printf(MSG_DEBUG,
@@ -13776,7 +13976,7 @@
 	nla_nest_end(msg, params);
 	wpa_printf(MSG_DEBUG, "nl80211: Transition Disable Policy %d\n", td_policy);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, (void *) -1, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Transition Disable setting failed: ret=%d (%s)",
 		ret, strerror(-ret));
@@ -13840,7 +14040,7 @@
 		return -ENOBUFS;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: add link failed. ret=%d (%s)",
 			   ret, strerror(-ret));
diff --git a/src/drivers/driver_nl80211.h b/src/drivers/driver_nl80211.h
index d922912..048c9a3 100644
--- a/src/drivers/driver_nl80211.h
+++ b/src/drivers/driver_nl80211.h
@@ -32,6 +32,7 @@
 	struct nl_cb *nl_cb;
 	struct nl_sock *nl;
 	int nl80211_id;
+	unsigned int nl80211_maxattr;
 	int nlctrl_id;
 	int ioctl_sock; /* socket for ioctl() use */
 	struct nl_sock *nl_event;
@@ -81,7 +82,6 @@
 	unsigned int wdev_id_set:1;
 	unsigned int added_if:1;
 	unsigned int static_ap:1;
-	unsigned int use_nl_connect:1;
 
 	u8 addr[ETH_ALEN];
 	u8 prev_addr[ETH_ALEN];
@@ -165,8 +165,6 @@
 	unsigned int scan_for_auth:1;
 	unsigned int retry_auth:1;
 	unsigned int use_monitor:1;
-	unsigned int ignore_next_local_disconnect:1;
-	unsigned int ignore_next_local_deauth:1;
 	unsigned int hostapd:1;
 	unsigned int start_mode_sta:1;
 	unsigned int start_iface_up:1;
@@ -199,10 +197,14 @@
 	unsigned int qca_do_acs:1;
 	unsigned int brcm_do_acs:1;
 	unsigned int uses_6ghz:1;
+	unsigned int uses_s1g:1;
 	unsigned int secure_ranging_ctx_vendor_cmd_avail:1;
 	unsigned int puncturing:1;
 	unsigned int qca_ap_allowed_freqs:1;
 
+	u32 ignore_next_local_disconnect;
+	u32 ignore_next_local_deauth;
+
 	u64 vendor_scan_cookie;
 	u64 remain_on_chan_cookie;
 	u64 send_frame_cookie;
@@ -271,18 +273,43 @@
 
 struct nl_msg;
 
+struct nl80211_err_info {
+	int link_id;
+};
+
 void * nl80211_cmd(struct wpa_driver_nl80211_data *drv,
 		   struct nl_msg *msg, int flags, uint8_t cmd);
 struct nl_msg * nl80211_cmd_msg(struct i802_bss *bss, int flags, uint8_t cmd);
 struct nl_msg * nl80211_drv_msg(struct wpa_driver_nl80211_data *drv, int flags,
 				uint8_t cmd);
 struct nl_msg * nl80211_bss_msg(struct i802_bss *bss, int flags, uint8_t cmd);
-int send_and_recv_msgs(struct wpa_driver_nl80211_data *drv, struct nl_msg *msg,
-		       int (*valid_handler)(struct nl_msg *, void *),
-		       void *valid_data,
-		       int (*ack_handler_custom)(struct nl_msg *, void *),
-		       void *ack_data);
-struct nl_sock * get_connect_handle(struct i802_bss *bss);
+
+int send_and_recv(struct nl80211_global *global,
+		  struct nl_sock *nl_handle, struct nl_msg *msg,
+		  int (*valid_handler)(struct nl_msg *, void *),
+		  void *valid_data,
+		  int (*ack_handler_custom)(struct nl_msg *, void *),
+		  void *ack_data,
+		  struct nl80211_err_info *err_info);
+
+static inline int
+send_and_recv_cmd(struct wpa_driver_nl80211_data *drv,
+		  struct nl_msg *msg)
+{
+	return send_and_recv(drv->global, drv->global->nl, msg,
+			     NULL, NULL, NULL, NULL, NULL);
+}
+
+static inline int
+send_and_recv_resp(struct wpa_driver_nl80211_data *drv,
+		   struct nl_msg *msg,
+		   int (*valid_handler)(struct nl_msg *, void *),
+		   void *valid_data)
+{
+	return send_and_recv(drv->global, drv->global->nl, msg,
+			     valid_handler, valid_data, NULL, NULL, NULL);
+}
+
 int nl80211_create_iface(struct wpa_driver_nl80211_data *drv,
 			 const char *ifname, enum nl80211_iftype iftype,
 			 const u8 *addr, int wds,
@@ -330,6 +357,12 @@
 void nl80211_restore_ap_mode(struct i802_bss *bss);
 struct i802_link * nl80211_get_link(struct i802_bss *bss, s8 link_id);
 
+static inline bool
+nl80211_attr_supported(struct wpa_driver_nl80211_data *drv, unsigned int attr)
+{
+	return attr <= drv->global->nl80211_maxattr;
+}
+
 #ifdef ANDROID
 int android_nl_socket_set_nonblocking(struct nl_sock *handle);
 int android_pno_start(struct i802_bss *bss,
diff --git a/src/drivers/driver_nl80211_capa.c b/src/drivers/driver_nl80211_capa.c
index 11a32af..0b6f511 100644
--- a/src/drivers/driver_nl80211_capa.c
+++ b/src/drivers/driver_nl80211_capa.c
@@ -49,8 +49,7 @@
 		return 0;
 	}
 
-	if (send_and_recv_msgs(drv, msg, protocol_feature_handler, &feat,
-			       NULL, NULL) == 0)
+	if (send_and_recv_resp(drv, msg, protocol_feature_handler, &feat) == 0)
 		return feat;
 
 	return 0;
@@ -1207,7 +1206,7 @@
 		return -1;
 	}
 
-	if (send_and_recv_msgs(drv, msg, wiphy_info_handler, info, NULL, NULL))
+	if (send_and_recv_resp(drv, msg, wiphy_info_handler, info))
 		return -1;
 
 	if (info->auth_supported)
@@ -1320,8 +1319,7 @@
 		return;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, dfs_info_handler, &dfs_capability,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, dfs_info_handler, &dfs_capability);
 	if (!ret && dfs_capability)
 		drv->capa.flags |= WPA_DRIVER_FLAGS_DFS_OFFLOAD;
 }
@@ -1408,8 +1406,7 @@
 
 	os_memset(&info, 0, sizeof(info));
 	info.capa = &drv->capa;
-	ret = send_and_recv_msgs(drv, msg, features_info_handler, &info,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, features_info_handler, &info);
 	if (ret || !info.flags)
 		return;
 
@@ -2549,8 +2546,7 @@
 		}
 	}
 
-	return send_and_recv_msgs(drv, msg, nl80211_get_reg, results,
-				  NULL, NULL);
+	return send_and_recv_resp(drv, msg, nl80211_get_reg, results);
 }
 
 
@@ -2592,6 +2588,8 @@
 
 			if (is_6ghz_freq(chan->freq))
 				drv->uses_6ghz = true;
+			if (chan->freq >= 900 && chan->freq < 1000)
+				drv->uses_s1g = true;
 			res = os_snprintf(pos, end - pos, " %d%s%s%s",
 					  chan->freq,
 					  (chan->flag & HOSTAPD_CHAN_DISABLED) ?
@@ -2642,8 +2640,7 @@
 		return NULL;
 	}
 
-	if (send_and_recv_msgs(drv, msg, phy_info_handler, &result,
-			       NULL, NULL) == 0) {
+	if (send_and_recv_resp(drv, msg, phy_info_handler, &result) == 0) {
 		struct hostapd_hw_modes *modes;
 
 		nl80211_set_regulatory_flags(drv, &result);
diff --git a/src/drivers/driver_nl80211_event.c b/src/drivers/driver_nl80211_event.c
index fdaf07b..4163f79 100644
--- a/src/drivers/driver_nl80211_event.c
+++ b/src/drivers/driver_nl80211_event.c
@@ -385,9 +385,9 @@
 		return 0;
 	}
 
-	ret = send_and_recv_msgs(drv, msg,
+	ret = send_and_recv_resp(drv, msg,
 				 qca_drv_connect_fail_reason_code_handler,
-				 &reason_code, NULL, NULL);
+				 &reason_code);
 	if (ret)
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Get connect fail reason_code failed: ret=%d (%s)",
@@ -442,7 +442,7 @@
 	wpa_printf(MSG_DEBUG, "nl80211: AP MLD address " MACSTR
 		   " received in link reconfig event", MAC2STR(ap_mld));
 	if (!drv->sta_mlo_info.valid_links ||
-	    os_memcmp(drv->sta_mlo_info.ap_mld_addr, ap_mld, ETH_ALEN) != 0) {
+	    !ether_addr_equal(drv->sta_mlo_info.ap_mld_addr, ap_mld)) {
 		if (drv->pending_link_reconfig_data == data) {
 			wpa_printf(MSG_DEBUG,
 				   "nl80211: Drop pending link reconfig event since AP MLD not matched even after new connect/roam event");
@@ -659,7 +659,8 @@
 		return -1;
 	}
 
-	mle = ieee802_11_defrag_mle(&req_elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mle = ieee802_11_defrag(req_elems.basic_mle, req_elems.basic_mle_len,
+				true);
 	if (!mle) {
 		wpa_printf(MSG_INFO,
 			   "nl80211: MLO: Basic Multi-Link element not found in Association Request");
@@ -670,7 +671,8 @@
 					 &req_info);
 	wpabuf_free(mle);
 
-	mle = ieee802_11_defrag_mle(&resp_elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mle = ieee802_11_defrag(resp_elems.basic_mle, resp_elems.basic_mle_len,
+				true);
 	if (!mle) {
 		wpa_printf(MSG_ERROR,
 			   "nl80211: MLO: Basic Multi-Link element not found in Association Response");
@@ -809,7 +811,7 @@
 	wpa_printf(MSG_DEBUG, "nl80211: AP MLD address " MACSTR
 		   " received in TID to link mapping event", MAC2STR(ap_mld));
 	if (!drv->sta_mlo_info.valid_links ||
-	    os_memcmp(drv->sta_mlo_info.ap_mld_addr, ap_mld, ETH_ALEN) != 0) {
+	    !ether_addr_equal(drv->sta_mlo_info.ap_mld_addr, ap_mld)) {
 		if (drv->pending_t2lm_data == data) {
 			wpa_printf(MSG_DEBUG,
 				   "nl80211: Drop pending TID-to-link mapping event since AP MLD not matched even after new connect/roam event");
@@ -957,9 +959,8 @@
 		if (drv->ignore_next_local_disconnect) {
 			drv->ignore_next_local_disconnect = 0;
 			if (!event.assoc_reject.bssid ||
-			    (os_memcmp(event.assoc_reject.bssid,
-				       drv->auth_attempt_bssid,
-				       ETH_ALEN) != 0)) {
+			    !ether_addr_equal(event.assoc_reject.bssid,
+					      drv->auth_attempt_bssid)) {
 				/*
 				 * Ignore the event that came without a BSSID or
 				 * for the old connection since this is likely
@@ -1270,12 +1271,16 @@
 	if (finished)
 		bss->flink->freq = data.ch_switch.freq;
 
+	if (link)
+		data.ch_switch.link_id = nla_get_u8(link);
+	else
+		data.ch_switch.link_id = NL80211_DRV_LINK_ID_NA;
+
 	if (link && is_sta_interface(drv->nlmode)) {
-		u8 link_id = nla_get_u8(link);
+		u8 link_id = data.ch_switch.link_id;
 
 		if (link_id < MAX_NUM_MLD_LINKS &&
 		    drv->sta_mlo_info.valid_links & BIT(link_id)) {
-			data.ch_switch.link_id = link_id;
 			drv->sta_mlo_info.links[link_id].freq =
 				data.ch_switch.freq;
 			wpa_supplicant_event(
@@ -1451,9 +1456,9 @@
 
 		if ((drv->capa.flags & WPA_DRIVER_FLAGS_SME) &&
 		    !drv->associated &&
-		    os_memcmp(bssid, drv->auth_bssid, ETH_ALEN) != 0 &&
-		    os_memcmp(bssid, drv->auth_attempt_bssid, ETH_ALEN) != 0 &&
-		    os_memcmp(bssid, drv->prev_bssid, ETH_ALEN) == 0) {
+		    !ether_addr_equal(bssid, drv->auth_bssid) &&
+		    !ether_addr_equal(bssid, drv->auth_attempt_bssid) &&
+		    ether_addr_equal(bssid, drv->prev_bssid)) {
 			/*
 			 * Avoid issues with some roaming cases where
 			 * disconnection event for the old AP may show up after
@@ -1462,11 +1467,10 @@
 			 * ignore_next_local_deauth as well, to avoid next local
 			 * deauth event be wrongly ignored.
 			 */
-			if (os_memcmp(mgmt->sa, drv->first_bss->addr,
-				      ETH_ALEN) == 0 ||
+			if (ether_addr_equal(mgmt->sa, drv->first_bss->addr) ||
 			    (!is_zero_ether_addr(drv->first_bss->prev_addr) &&
-			     os_memcmp(mgmt->sa, drv->first_bss->prev_addr,
-				       ETH_ALEN) == 0)) {
+			     ether_addr_equal(mgmt->sa,
+					      drv->first_bss->prev_addr))) {
 				wpa_printf(MSG_DEBUG,
 					   "nl80211: Received a locally generated deauth event. Clear ignore_next_local_deauth flag");
 				drv->ignore_next_local_deauth = 0;
@@ -1481,8 +1485,8 @@
 
 		if (!(drv->capa.flags & WPA_DRIVER_FLAGS_SME) &&
 		    drv->connect_reassoc && drv->associated &&
-		    os_memcmp(bssid, drv->prev_bssid, ETH_ALEN) == 0 &&
-		    os_memcmp(bssid, drv->auth_attempt_bssid, ETH_ALEN) != 0) {
+		    ether_addr_equal(bssid, drv->prev_bssid) &&
+		    !ether_addr_equal(bssid, drv->auth_attempt_bssid)) {
 			/*
 			 * Avoid issues with some roaming cases where
 			 * disconnection event for the old AP may show up after
@@ -1498,8 +1502,8 @@
 		}
 
 		if (drv->associated != 0 &&
-		    os_memcmp(bssid, drv->bssid, ETH_ALEN) != 0 &&
-		    os_memcmp(bssid, drv->auth_bssid, ETH_ALEN) != 0) {
+		    !ether_addr_equal(bssid, drv->bssid) &&
+		    !ether_addr_equal(bssid, drv->auth_bssid)) {
 			/*
 			 * We have presumably received this deauth as a
 			 * response to a clear_state_mismatch() outgoing
@@ -1521,7 +1525,7 @@
 
 	if (type == EVENT_DISASSOC) {
 		event.disassoc_info.locally_generated =
-			!os_memcmp(mgmt->sa, drv->first_bss->addr, ETH_ALEN);
+			ether_addr_equal(mgmt->sa, drv->first_bss->addr);
 		event.disassoc_info.addr = bssid;
 		event.disassoc_info.reason_code = reason_code;
 		if (frame + len > mgmt->u.disassoc.variable) {
@@ -1531,7 +1535,7 @@
 		}
 	} else {
 		event.deauth_info.locally_generated =
-			!os_memcmp(mgmt->sa, drv->first_bss->addr, ETH_ALEN);
+			ether_addr_equal(mgmt->sa, drv->first_bss->addr);
 		if (drv->ignore_deauth_event) {
 			wpa_printf(MSG_DEBUG, "nl80211: Ignore deauth event due to previous forced deauth-during-auth");
 			drv->ignore_deauth_event = 0;
@@ -1701,15 +1705,14 @@
 			   bss->ifname);
 	} else if (cmd != NL80211_CMD_FRAME_TX_STATUS  &&
 		   !(data[4] & 0x01) &&
-		   os_memcmp(bss->addr, data + 4, ETH_ALEN) != 0 &&
+		   !ether_addr_equal(bss->addr, data + 4) &&
 		   (is_zero_ether_addr(bss->rand_addr) ||
-		    os_memcmp(bss->rand_addr, data + 4, ETH_ALEN) != 0) &&
-		   os_memcmp(bss->addr, data + 4 + ETH_ALEN, ETH_ALEN) != 0 &&
+		    !ether_addr_equal(bss->rand_addr, data + 4)) &&
+		   !ether_addr_equal(bss->addr, data + 4 + ETH_ALEN) &&
 		   (is_zero_ether_addr(drv->first_bss->prev_addr) ||
-		    os_memcmp(bss->prev_addr, data + 4 + ETH_ALEN,
-			      ETH_ALEN) != 0) &&
+		    !ether_addr_equal(bss->prev_addr, data + 4 + ETH_ALEN)) &&
 		   (!mld_link ||
-		    os_memcmp(mld_link->addr, data + 4, ETH_ALEN) != 0)) {
+		    !ether_addr_equal(mld_link->addr, data + 4))) {
 		wpa_printf(MSG_MSGDUMP, "nl80211: %s: Ignore MLME frame event "
 			   "for foreign address", bss->ifname);
 		return;
@@ -2449,14 +2452,23 @@
 {
 	union wpa_event_data data;
 	enum nl80211_radar_event event_type;
+	struct i802_link *mld_link = NULL;
 
 	if (!tb[NL80211_ATTR_WIPHY_FREQ] || !tb[NL80211_ATTR_RADAR_EVENT])
 		return;
 
 	os_memset(&data, 0, sizeof(data));
+	data.dfs_event.link_id = NL80211_DRV_LINK_ID_NA;
 	data.dfs_event.freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
 	event_type = nla_get_u32(tb[NL80211_ATTR_RADAR_EVENT]);
 
+	if (data.dfs_event.freq) {
+		mld_link = nl80211_get_mld_link_by_freq(drv->first_bss,
+							data.dfs_event.freq);
+		if (mld_link)
+			data.dfs_event.link_id = mld_link->link_id;
+	}
+
 	/* Check HT params */
 	if (tb[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
 		data.dfs_event.ht_enabled = 1;
@@ -2487,10 +2499,12 @@
 	if (tb[NL80211_ATTR_CENTER_FREQ2])
 		data.dfs_event.cf2 = nla_get_u32(tb[NL80211_ATTR_CENTER_FREQ2]);
 
-	wpa_printf(MSG_DEBUG, "nl80211: DFS event on freq %d MHz, ht: %d, offset: %d, width: %d, cf1: %dMHz, cf2: %dMHz",
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: DFS event on freq %d MHz, ht: %d, offset: %d, width: %d, cf1: %dMHz, cf2: %dMHz, link_id=%d",
 		   data.dfs_event.freq, data.dfs_event.ht_enabled,
 		   data.dfs_event.chan_offset, data.dfs_event.chan_width,
-		   data.dfs_event.cf1, data.dfs_event.cf2);
+		   data.dfs_event.cf1, data.dfs_event.cf2,
+		   data.dfs_event.link_id);
 
 	switch (event_type) {
 	case NL80211_RADAR_DETECTED:
@@ -2722,8 +2736,14 @@
 	if (tb[QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP])
 		event.acs_selected_channels.puncture_bitmap =
 			nla_get_u16(tb[QCA_WLAN_VENDOR_ATTR_ACS_PUNCTURE_BITMAP]);
+	if (tb[QCA_WLAN_VENDOR_ATTR_ACS_LINK_ID])
+		event.acs_selected_channels.link_id =
+			nla_get_u8(tb[QCA_WLAN_VENDOR_ATTR_ACS_LINK_ID]);
+	else
+		event.acs_selected_channels.link_id = -1;
+
 	wpa_printf(MSG_INFO,
-		   "nl80211: ACS Results: PFreq: %d SFreq: %d BW: %d VHT0: %d VHT1: %d HW_MODE: %d EDMGCH: %d PUNCBITMAP: 0x%x",
+		   "nl80211: ACS Results: PFreq: %d SFreq: %d BW: %d VHT0: %d VHT1: %d HW_MODE: %d EDMGCH: %d PUNCBITMAP: 0x%x, LinkId: %d",
 		   event.acs_selected_channels.pri_freq,
 		   event.acs_selected_channels.sec_freq,
 		   event.acs_selected_channels.ch_width,
@@ -2731,7 +2751,8 @@
 		   event.acs_selected_channels.vht_seg1_center_ch,
 		   event.acs_selected_channels.hw_mode,
 		   event.acs_selected_channels.edmg_channel,
-		   event.acs_selected_channels.puncture_bitmap);
+		   event.acs_selected_channels.puncture_bitmap,
+		   event.acs_selected_channels.link_id);
 
 	/* Ignore ACS channel list check for backwards compatibility */
 
@@ -2810,6 +2831,7 @@
 {
 	union wpa_event_data data;
 	struct nlattr *tb[NL80211_ATTR_MAX + 1];
+	struct i802_link *mld_link = NULL;
 
 	wpa_printf(MSG_DEBUG,
 		   "nl80211: DFS offload radar vendor event received");
@@ -2826,9 +2848,17 @@
 
 	os_memset(&data, 0, sizeof(data));
 	data.dfs_event.freq = nla_get_u32(tb[NL80211_ATTR_WIPHY_FREQ]);
+	data.dfs_event.link_id = NL80211_DRV_LINK_ID_NA;
 
-	wpa_printf(MSG_DEBUG, "nl80211: DFS event on freq %d MHz",
-		   data.dfs_event.freq);
+	if (data.dfs_event.freq) {
+		mld_link = nl80211_get_mld_link_by_freq(drv->first_bss,
+							data.dfs_event.freq);
+		if (mld_link)
+			data.dfs_event.link_id = mld_link->link_id;
+	}
+
+	wpa_printf(MSG_DEBUG, "nl80211: DFS event on freq %d MHz, link=%d",
+		   data.dfs_event.freq, data.dfs_event.link_id);
 
 	/* Check HT params */
 	if (tb[NL80211_ATTR_WIPHY_CHANNEL_TYPE]) {
@@ -3386,7 +3416,8 @@
 }
 
 
-static void nl80211_dump_freq(const char *title, struct nlattr *nl_freq)
+static void nl80211_parse_freq_attrs(const char *title, struct nlattr *nl_freq,
+				     struct frequency_attrs *attrs)
 {
 	static struct nla_policy freq_policy[NL80211_FREQUENCY_ATTR_MAX + 1] = {
 		[NL80211_FREQUENCY_ATTR_FREQ] = { .type = NLA_U32 },
@@ -3413,6 +3444,15 @@
 		   tb[NL80211_FREQUENCY_ATTR_DISABLED] ? " disabled" : "",
 		   tb[NL80211_FREQUENCY_ATTR_NO_IR] ? " no-IR" : "",
 		   tb[NL80211_FREQUENCY_ATTR_RADAR] ? " radar" : "");
+
+	attrs->freq = freq;
+	attrs->max_tx_power = max_tx_power;
+	if (tb[NL80211_FREQUENCY_ATTR_DISABLED])
+		attrs->disabled = true;
+	if (tb[NL80211_FREQUENCY_ATTR_NO_IR])
+		attrs->no_ir = true;
+	if (tb[NL80211_FREQUENCY_ATTR_RADAR])
+		attrs->radar = true;
 }
 
 
@@ -3426,9 +3466,13 @@
 	data.channel_list_changed.initiator = REGDOM_BEACON_HINT;
 
 	if (tb[NL80211_ATTR_FREQ_BEFORE])
-		nl80211_dump_freq("before", tb[NL80211_ATTR_FREQ_BEFORE]);
+		nl80211_parse_freq_attrs(
+			"before", tb[NL80211_ATTR_FREQ_BEFORE],
+			&data.channel_list_changed.beacon_hint_before);
 	if (tb[NL80211_ATTR_FREQ_AFTER])
-		nl80211_dump_freq("after", tb[NL80211_ATTR_FREQ_AFTER]);
+		nl80211_parse_freq_attrs(
+			"after", tb[NL80211_ATTR_FREQ_AFTER],
+			&data.channel_list_changed.beacon_hint_after);
 
 	wpa_supplicant_event(drv->ctx, EVENT_CHANNEL_LIST_CHANGED, &data);
 }
@@ -3494,8 +3538,6 @@
 {
 	const u8 *addr;
 	union wpa_event_data event;
-	const u8 *connected_addr = drv->sta_mlo_info.valid_links ?
-				drv->sta_mlo_info.ap_mld_addr : drv->bssid;
 
 	os_memset(&event, 0, sizeof(event));
 
@@ -3512,13 +3554,19 @@
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Port authorized for STA addr "  MACSTR,
 			MAC2STR(addr));
-	} else if (is_sta_interface(drv->nlmode) &&
-		   os_memcmp(addr, connected_addr, ETH_ALEN) != 0) {
-		wpa_printf(MSG_DEBUG,
-			   "nl80211: Ignore port authorized event for " MACSTR
-			   " (not the currently connected BSSID " MACSTR ")",
-			   MAC2STR(addr), MAC2STR(connected_addr));
-		return;
+	} else if (is_sta_interface(drv->nlmode)) {
+		const u8 *connected_addr;
+
+		connected_addr = drv->sta_mlo_info.valid_links ?
+			drv->sta_mlo_info.ap_mld_addr : drv->bssid;
+		if (!ether_addr_equal(addr, connected_addr)) {
+			wpa_printf(MSG_DEBUG,
+				   "nl80211: Ignore port authorized event for "
+				   MACSTR " (not the currently connected BSSID "
+				   MACSTR ")",
+				   MAC2STR(addr), MAC2STR(connected_addr));
+			return;
+		}
 	}
 
 	if (tb[NL80211_ATTR_TD_BITMAP]) {
@@ -3881,6 +3929,7 @@
 	case NL80211_CMD_ASSOCIATE:
 	case NL80211_CMD_DEAUTHENTICATE:
 	case NL80211_CMD_DISASSOCIATE:
+	case NL80211_CMD_FRAME:
 	case NL80211_CMD_FRAME_TX_STATUS:
 	case NL80211_CMD_UNPROT_DEAUTHENTICATE:
 	case NL80211_CMD_UNPROT_DISASSOCIATE:
@@ -4041,6 +4090,9 @@
 		nl80211_color_change_announcement_completed(bss);
 		break;
 #endif /* CONFIG_IEEE80211AX */
+	case NL80211_CMD_LINKS_REMOVED:
+		wpa_supplicant_event(drv->ctx, EVENT_LINK_RECONFIG, NULL);
+		break;
 	default:
 		wpa_dbg(drv->ctx, MSG_DEBUG, "nl80211: Ignored unknown event "
 			"(cmd=%d)", cmd);
@@ -4049,6 +4101,21 @@
 }
 
 
+static bool nl80211_drv_in_list(struct nl80211_global *global,
+				struct wpa_driver_nl80211_data *drv)
+{
+	struct wpa_driver_nl80211_data *tmp;
+
+	dl_list_for_each(tmp, &global->interfaces,
+			 struct wpa_driver_nl80211_data, list) {
+		if (drv == tmp)
+			return true;
+	}
+
+	return false;
+}
+
+
 int process_global_event(struct nl_msg *msg, void *arg)
 {
 	struct nl80211_global *global = arg;
@@ -4060,6 +4127,31 @@
 	u64 wdev_id = 0;
 	int wdev_id_set = 0;
 	int wiphy_idx_set = 0;
+	bool processed = false;
+
+	/* Event marker, all prior events have been processed */
+	if (gnlh->cmd == NL80211_CMD_GET_PROTOCOL_FEATURES) {
+		u32 seq = nlmsg_hdr(msg)->nlmsg_seq;
+
+		dl_list_for_each_safe(drv, tmp, &global->interfaces,
+				      struct wpa_driver_nl80211_data, list) {
+			if (drv->ignore_next_local_deauth > 0 &&
+			    drv->ignore_next_local_deauth <= seq) {
+				wpa_printf(MSG_DEBUG,
+					   "nl80211: No DEAUTHENTICATE event was ignored");
+				drv->ignore_next_local_deauth = 0;
+			}
+
+			if (drv->ignore_next_local_disconnect > 0 &&
+			    drv->ignore_next_local_disconnect <= seq) {
+				wpa_printf(MSG_DEBUG,
+					   "nl80211: No DISCONNECT event was ignored");
+				drv->ignore_next_local_disconnect = 0;
+			}
+		}
+
+		return NL_SKIP;
+	}
 
 	nla_parse(tb, NL80211_ATTR_MAX, genlmsg_attrdata(gnlh, 0),
 		  genlmsg_attrlen(gnlh, 0), NULL);
@@ -4084,16 +4176,28 @@
 			    (wiphy_idx_set && wiphy_idx == wiphy_idx_rx) ||
 			    (wdev_id_set && bss->wdev_id_set &&
 			     wdev_id == bss->wdev_id)) {
+				processed = true;
 				do_process_drv_event(bss, gnlh->cmd, tb);
-				return NL_SKIP;
+				if (!wiphy_idx_set)
+					return NL_SKIP;
+				/* The driver instance could have been removed,
+				 * e.g., due to NL80211_CMD_RADAR_DETECT event,
+				 * so need to stop the loop if that has
+				 * happened. */
+				if (!nl80211_drv_in_list(global, drv))
+					break;
 			}
 		}
-		wpa_printf(MSG_DEBUG,
-			   "nl80211: Ignored event %d (%s) for foreign interface (ifindex %d wdev 0x%llx)",
-			   gnlh->cmd, nl80211_command_to_string(gnlh->cmd),
-			   ifidx, (long long unsigned int) wdev_id);
 	}
 
+	if (processed)
+		return NL_SKIP;
+
+	wpa_printf(MSG_DEBUG,
+		   "nl80211: Ignored event %d (%s) for foreign interface (ifindex %d wdev 0x%llx wiphy %d)",
+		   gnlh->cmd, nl80211_command_to_string(gnlh->cmd),
+		   ifidx, (long long unsigned int) wdev_id, wiphy_idx_rx);
+
 	return NL_SKIP;
 }
 
diff --git a/src/drivers/driver_nl80211_scan.c b/src/drivers/driver_nl80211_scan.c
index 4907e3c..68ae579 100644
--- a/src/drivers/driver_nl80211_scan.c
+++ b/src/drivers/driver_nl80211_scan.c
@@ -82,8 +82,7 @@
 
 	os_memset(info, 0, sizeof(*info));
 	msg = nl80211_drv_msg(drv, NLM_F_DUMP, NL80211_CMD_GET_SURVEY);
-	return send_and_recv_msgs(drv, msg, get_noise_for_scan_results, info,
-				  NULL, NULL);
+	return send_and_recv_resp(drv, msg, get_noise_for_scan_results, info);
 }
 
 
@@ -95,7 +94,7 @@
 
 	wpa_printf(MSG_DEBUG, "nl80211: Abort scan");
 	msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_ABORT_SCAN);
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Abort scan failed: ret=%d (%s)",
 			   ret, strerror(-ret));
@@ -126,7 +125,7 @@
 
 	nla_nest_end(msg, params);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_INFO,
@@ -397,7 +396,7 @@
 			goto fail;
 	}
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG, "nl80211: Scan trigger failed: ret=%d "
@@ -428,7 +427,9 @@
 	drv->scan_state = SCAN_REQUESTED;
 	/* Not all drivers generate "scan completed" wireless event, so try to
 	 * read results after a timeout. */
-	timeout = drv->uses_6ghz ? 15 : 10;
+	timeout = drv->uses_6ghz ? 20 : 10;
+	if (drv->uses_s1g)
+		timeout += 5;
 	if (drv->scan_complete_events) {
 		/*
 		 * The driver seems to deliver events to notify when scan is
@@ -650,7 +651,7 @@
 			params->sched_scan_start_delay))
 		goto fail;
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 
 	/* TODO: if we get an error here, we should fall back to normal scan */
 
@@ -687,7 +688,7 @@
 #endif /* ANDROID */
 
 	msg = nl80211_drv_msg(drv, 0, NL80211_CMD_STOP_SCHED_SCAN);
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Sched scan stop failed: ret=%d (%s)",
@@ -922,14 +923,14 @@
 			   "nl80211: Local state (not associated) does not match with BSS state");
 		clear_state_mismatch(drv, r->bssid);
 	} else if (is_sta_interface(drv->nlmode) &&
-		   os_memcmp(drv->bssid, r->bssid, ETH_ALEN) != 0) {
+		   !ether_addr_equal(drv->bssid, r->bssid)) {
 		wpa_printf(MSG_DEBUG,
 			   "nl80211: Local state (associated with " MACSTR
 			   ") does not match with BSS state",
 			   MAC2STR(drv->bssid));
 
-		if (os_memcmp(drv->sta_mlo_info.ap_mld_addr, drv->bssid,
-			      ETH_ALEN) != 0) {
+		if (!ether_addr_equal(drv->sta_mlo_info.ap_mld_addr,
+				      drv->bssid)) {
 			clear_state_mismatch(drv, r->bssid);
 
 			if (!is_zero_ether_addr(drv->sta_mlo_info.ap_mld_addr))
@@ -992,7 +993,7 @@
 
 	arg.drv = drv;
 	arg.res = res;
-	ret = send_and_recv_msgs(drv, msg, bss_info_handler, &arg, NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, bss_info_handler, &arg);
 	if (ret == -EAGAIN) {
 		count++;
 		if (count >= 10) {
@@ -1076,8 +1077,7 @@
 	ctx.idx = 0;
 	msg = nl80211_cmd_msg(drv->first_bss, NLM_F_DUMP, NL80211_CMD_GET_SCAN);
 	if (msg)
-		send_and_recv_msgs(drv, msg, nl80211_dump_scan_handler, &ctx,
-				   NULL, NULL);
+		send_and_recv_resp(drv, msg, nl80211_dump_scan_handler, &ctx);
 }
 
 
@@ -1270,8 +1270,7 @@
 
 	nla_nest_end(msg, attr);
 
-	ret = send_and_recv_msgs(drv, msg, scan_cookie_handler, &cookie,
-				 NULL, NULL);
+	ret = send_and_recv_resp(drv, msg, scan_cookie_handler, &cookie);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_DEBUG,
@@ -1334,7 +1333,7 @@
 
 	nla_nest_end(msg, attr);
 
-	ret = send_and_recv_msgs(drv, msg, NULL, NULL, NULL, NULL);
+	ret = send_and_recv_cmd(drv, msg);
 	msg = NULL;
 	if (ret) {
 		wpa_printf(MSG_ERROR,
diff --git a/src/drivers/driver_roboswitch.c b/src/drivers/driver_roboswitch.c
index 9beb6c4..23c7086 100644
--- a/src/drivers/driver_roboswitch.c
+++ b/src/drivers/driver_roboswitch.c
@@ -175,7 +175,7 @@
 	struct wpa_driver_roboswitch_data *drv = priv;
 
 	if (len > 14 && WPA_GET_BE16(buf + 12) == ETH_P_EAPOL &&
-	    os_memcmp(buf, drv->own_addr, ETH_ALEN) == 0)
+	    ether_addr_equal(buf, drv->own_addr))
 		drv_event_eapol_rx(drv->ctx, src_addr, buf + 14, len - 14);
 }
 
diff --git a/src/drivers/driver_wext.c b/src/drivers/driver_wext.c
index cf201fe..2c656fb 100644
--- a/src/drivers/driver_wext.c
+++ b/src/drivers/driver_wext.c
@@ -452,9 +452,10 @@
 				   MAC2STR((u8 *) iwe->u.ap_addr.sa_data));
 			if (is_zero_ether_addr(
 				    (const u8 *) iwe->u.ap_addr.sa_data) ||
-			    os_memcmp(iwe->u.ap_addr.sa_data,
-				      "\x44\x44\x44\x44\x44\x44", ETH_ALEN) ==
-			    0) {
+			    ether_addr_equal((const u8 *)
+					     iwe->u.ap_addr.sa_data,
+					     (const u8 *)
+					     "\x44\x44\x44\x44\x44\x44")) {
 				os_free(drv->assoc_req_ies);
 				drv->assoc_req_ies = NULL;
 				os_free(drv->assoc_resp_ies);
diff --git a/src/drivers/linux_ioctl.c b/src/drivers/linux_ioctl.c
index 7edb9df..29abc0c 100644
--- a/src/drivers/linux_ioctl.c
+++ b/src/drivers/linux_ioctl.c
@@ -161,11 +161,20 @@
 	ifr.ifr_ifindex = ifindex;
 	if (ioctl(sock, SIOCBRADDIF, &ifr) < 0) {
 		int saved_errno = errno;
+		char in_br[IFNAMSIZ];
 
 		wpa_printf(MSG_DEBUG, "Could not add interface %s into bridge "
 			   "%s: %s", ifname, brname, strerror(errno));
 		errno = saved_errno;
-		return -1;
+
+		/* If ioctl() returns EBUSY when adding an interface into the
+		 * bridge, the interface might have already been added by an
+		 * external operation, so check whether the interface is
+		 * currently on the right bridge and ignore the error if it is.
+		 */
+		if (errno != EBUSY || linux_br_get(in_br, ifname) != 0 ||
+		    os_strcmp(in_br, brname) != 0)
+			return -1;
 	}
 
 	return 0;
diff --git a/src/eap_peer/eap.c b/src/eap_peer/eap.c
index 4a09908..1d8ad62 100644
--- a/src/eap_peer/eap.c
+++ b/src/eap_peer/eap.c
@@ -1771,12 +1771,13 @@
 		return NULL;
 
 	wpabuf_put_data(resp, identity, identity_len);
-	wpabuf_free(privacy_identity);
 
 	os_free(sm->identity);
 	sm->identity = os_memdup(identity, identity_len);
 	sm->identity_len = identity_len;
 
+	wpabuf_free(privacy_identity);
+
 	return resp;
 }
 
diff --git a/src/fst/fst_group.c b/src/fst/fst_group.c
index 255d0fd..61c7299 100644
--- a/src/fst/fst_group.c
+++ b/src/fst/fst_group.c
@@ -364,8 +364,7 @@
 				cur_mbie, this_band_id);
 			if (!this_peer_addr)
 				continue;
-			if (os_memcmp(this_peer_addr, peer_addr, ETH_ALEN) ==
-			    0) {
+			if (ether_addr_equal(this_peer_addr, peer_addr)) {
 				os_memcpy(other_peer_addr, cur_peer_addr,
 					  ETH_ALEN);
 				return other_iface;
diff --git a/src/fst/fst_iface.c b/src/fst/fst_iface.c
index 90c5fc0..96b2847 100644
--- a/src/fst/fst_iface.c
+++ b/src/fst/fst_iface.c
@@ -56,7 +56,7 @@
 	const u8 *a = fst_iface_get_peer_first(iface, &ctx, mb_only);
 
 	for (; a != NULL; a = fst_iface_get_peer_next(iface, &ctx, mb_only))
-		if (os_memcmp(addr, a, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, a))
 			return true;
 
 	return false;
diff --git a/src/fst/fst_session.c b/src/fst/fst_session.c
index e42a85c..49886ff 100644
--- a/src/fst/fst_session.c
+++ b/src/fst/fst_session.c
@@ -238,10 +238,8 @@
 
 	foreach_fst_session(s) {
 		if (s->group == g &&
-		    (os_memcmp(s->data.old_peer_addr, peer_addr,
-			       ETH_ALEN) == 0 ||
-		     os_memcmp(s->data.new_peer_addr, peer_addr,
-			       ETH_ALEN) == 0) &&
+		    (ether_addr_equal(s->data.old_peer_addr, peer_addr) ||
+		     ether_addr_equal(s->data.new_peer_addr, peer_addr)) &&
 		    fst_session_is_in_progress(s))
 			return s;
 	}
diff --git a/src/l2_packet/l2_packet_linux.c b/src/l2_packet/l2_packet_linux.c
index 7897bc0..5da0505 100644
--- a/src/l2_packet/l2_packet_linux.c
+++ b/src/l2_packet/l2_packet_linux.c
@@ -179,7 +179,7 @@
 		 * authorization has been completed (in dormant state).
 		 */
 		if (l2->num_rx_br <= 1 &&
-		    (os_memcmp(eth->h_dest, l2->own_addr, ETH_ALEN) == 0 ||
+		    (ether_addr_equal(eth->h_dest, l2->own_addr) ||
 		     is_multicast_ether_addr(eth->h_dest))) {
 			wpa_printf(MSG_DEBUG,
 				   "l2_packet_receive: Main packet socket for %s seems to have working RX - close workaround bridge socket",
@@ -241,7 +241,7 @@
 	wpa_printf(MSG_DEBUG, "%s: src=" MACSTR " len=%d",
 		   __func__, MAC2STR(ll.sll_addr), (int) res);
 
-	if (os_memcmp(ll.sll_addr, l2->own_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(ll.sll_addr, l2->own_addr)) {
 		wpa_printf(MSG_DEBUG, "%s: Drop RX of own frame", __func__);
 		return;
 	}
diff --git a/src/p2p/p2p.c b/src/p2p/p2p.c
index 267399d..0f9c924 100644
--- a/src/p2p/p2p.c
+++ b/src/p2p/p2p.c
@@ -301,6 +301,8 @@
 		    ies) < 0) {
 		p2p_dbg(p2p, "Failed to start listen mode");
 		p2p->pending_listen_freq = 0;
+	} else {
+		p2p->pending_listen_wait_drv = true;
 	}
 	wpabuf_free(ies);
 }
@@ -350,6 +352,7 @@
 		wpabuf_free(ies);
 		return -1;
 	}
+	p2p->pending_listen_wait_drv = true;
 	wpabuf_free(ies);
 
 	p2p_set_state(p2p, P2P_LISTEN_ONLY);
@@ -378,7 +381,7 @@
 {
 	struct p2p_device *dev;
 	dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-		if (os_memcmp(dev->info.p2p_device_addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(dev->info.p2p_device_addr, addr))
 			return dev;
 	}
 	return NULL;
@@ -396,7 +399,7 @@
 {
 	struct p2p_device *dev;
 	dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-		if (os_memcmp(dev->interface_addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(dev->interface_addr, addr))
 			return dev;
 	}
 	return NULL;
@@ -486,8 +489,8 @@
 	 * group, the information will be restored in the loop following this.
 	 */
 	dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-		if (os_memcmp(dev->member_in_go_iface, go_interface_addr,
-			      ETH_ALEN) == 0) {
+		if (ether_addr_equal(dev->member_in_go_iface,
+				     go_interface_addr)) {
 			os_memset(dev->member_in_go_iface, 0, ETH_ALEN);
 			os_memset(dev->member_in_go_dev, 0, ETH_ALEN);
 		}
@@ -495,8 +498,7 @@
 
 	for (c = 0; c < info.num_clients; c++) {
 		struct p2p_client_info *cli = &info.client[c];
-		if (os_memcmp(cli->p2p_device_addr, p2p->cfg->dev_addr,
-			      ETH_ALEN) == 0)
+		if (ether_addr_equal(cli->p2p_device_addr, p2p->cfg->dev_addr))
 			continue; /* ignore our own entry */
 		dev = p2p_get_device(p2p, cli->p2p_device_addr);
 		if (dev) {
@@ -754,7 +756,7 @@
 	}
 
 	if (!is_zero_ether_addr(p2p->peer_filter) &&
-	    os_memcmp(p2p_dev_addr, p2p->peer_filter, ETH_ALEN) != 0) {
+	    !ether_addr_equal(p2p_dev_addr, p2p->peer_filter)) {
 		p2p_dbg(p2p, "Do not add peer filter for " MACSTR
 			" due to peer filter", MAC2STR(p2p_dev_addr));
 		p2p_parse_free(&msg);
@@ -799,7 +801,7 @@
 	dev->flags &= ~(P2P_DEV_PROBE_REQ_ONLY | P2P_DEV_GROUP_CLIENT_ONLY |
 			P2P_DEV_LAST_SEEN_AS_GROUP_CLIENT);
 
-	if (os_memcmp(addr, p2p_dev_addr, ETH_ALEN) != 0)
+	if (!ether_addr_equal(addr, p2p_dev_addr))
 		os_memcpy(dev->interface_addr, addr, ETH_ALEN);
 	if (msg.ssid &&
 	    msg.ssid[1] <= sizeof(dev->oper_ssid) &&
@@ -1030,6 +1032,7 @@
 		return;
 	}
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 
 	if (p2p->find_pending_full &&
 	    (p2p->find_type == P2P_FIND_PROGRESSIVE ||
@@ -1247,6 +1250,7 @@
 		p2p->pending_listen_freq = 0;
 	}
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 	p2p->find_pending_full = 0;
 	p2p->find_type = type;
 	if (freq != 2412 && freq != 2437 && freq != 2462 && freq != 60480)
@@ -1341,6 +1345,10 @@
 
 void p2p_stop_listen_for_freq(struct p2p_data *p2p, int freq)
 {
+	p2p_dbg(p2p,
+		"%s(freq=%d) pending_listen_freq=%d in_listen=%d drv_in_listen=%d",
+		__func__, freq, p2p->pending_listen_freq, p2p->in_listen,
+		p2p->drv_in_listen);
 	if (freq > 0 &&
 	    ((p2p->drv_in_listen == freq && p2p->in_listen) ||
 	     p2p->pending_listen_freq == (unsigned int) freq)) {
@@ -1360,7 +1368,15 @@
 		p2p_dbg(p2p, "Clear drv_in_listen (%d)", p2p->drv_in_listen);
 		p2p->drv_in_listen = 0;
 	}
+	if (p2p->pending_listen_freq &&
+	    p2p->pending_listen_freq != (unsigned int) freq &&
+	    !p2p->drv_in_listen && p2p->pending_listen_wait_drv) {
+		p2p_dbg(p2p,
+			"Clear pending_listen_freq since the started listen did not complete before being stopped");
+		p2p->pending_listen_freq = 0;
+	}
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 }
 
 
@@ -2029,6 +2045,7 @@
 		p2p->pending_listen_freq = 0;
 	}
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 	p2p->go_neg_peer->status = P2P_SC_SUCCESS;
 	/*
 	 * Set new timeout to make sure a previously set one does not expire
@@ -2049,6 +2066,7 @@
 		p2p->pending_listen_freq = 0;
 	}
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 	p2p_invite_send(p2p, p2p->invite_peer, p2p->invite_go_dev_addr,
 			p2p->invite_dev_pw_id);
 }
@@ -2383,7 +2401,7 @@
 	}
 
 	if (dst && !is_broadcast_ether_addr(dst) &&
-	    os_memcmp(dst, p2p->cfg->dev_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(dst, p2p->cfg->dev_addr)) {
 		/* Not sent to the broadcast address or our P2P Device Address
 		 */
 		p2p_dbg(p2p, "Probe Req DA " MACSTR " not ours - ignore it",
@@ -2467,7 +2485,7 @@
 	}
 
 	if (msg.device_id &&
-	    os_memcmp(msg.device_id, p2p->cfg->dev_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(msg.device_id, p2p->cfg->dev_addr)) {
 		/* Device ID did not match */
 		p2p_dbg(p2p, "Probe Req requested Device ID " MACSTR " did not match - ignore it",
 			MAC2STR(msg.device_id));
@@ -2556,8 +2574,7 @@
 	 */
 	if ((p2p->state == P2P_CONNECT || p2p->state == P2P_CONNECT_LISTEN) &&
 	    p2p->go_neg_peer &&
-	    os_memcmp(addr, p2p->go_neg_peer->info.p2p_device_addr, ETH_ALEN)
-	    == 0 &&
+	    ether_addr_equal(addr, p2p->go_neg_peer->info.p2p_device_addr) &&
 	    !(p2p->go_neg_peer->flags & P2P_DEV_WAIT_GO_NEG_CONFIRM)) {
 		/* Received a Probe Request from GO Negotiation peer */
 		p2p_dbg(p2p, "Found GO Negotiation peer - try to start GO negotiation from timeout");
@@ -2569,8 +2586,7 @@
 	if ((p2p->state == P2P_INVITE || p2p->state == P2P_INVITE_LISTEN) &&
 	    p2p->invite_peer &&
 	    (p2p->invite_peer->flags & P2P_DEV_WAIT_INV_REQ_ACK) &&
-	    os_memcmp(addr, p2p->invite_peer->info.p2p_device_addr, ETH_ALEN)
-	    == 0) {
+	    ether_addr_equal(addr, p2p->invite_peer->info.p2p_device_addr)) {
 		/* Received a Probe Request from Invite peer */
 		p2p_dbg(p2p, "Found Invite peer - try to start Invite from timeout");
 		eloop_cancel_timeout(p2p_invite_start, p2p, NULL);
@@ -2923,8 +2939,7 @@
 		return; /* No pending Group Formation */
 	}
 
-	if (os_memcmp(mac_addr, p2p->go_neg_peer->intended_addr, ETH_ALEN) !=
-	    0) {
+	if (!ether_addr_equal(mac_addr, p2p->go_neg_peer->intended_addr)) {
 		p2p_dbg(p2p, "Ignore WPS registration success notification for "
 			MACSTR " (GO Negotiation peer " MACSTR ")",
 			MAC2STR(mac_addr),
@@ -3397,8 +3412,8 @@
 	 */
 
 	dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-		if (os_memcmp(p2p->pending_pd_devaddr,
-			      dev->info.p2p_device_addr, ETH_ALEN) != 0)
+		if (!ether_addr_equal(p2p->pending_pd_devaddr,
+				      dev->info.p2p_device_addr))
 			continue;
 		if (!dev->req_config_methods)
 			continue;
@@ -3881,6 +3896,7 @@
 	p2p_dbg(p2p, "Starting Listen timeout(%u,%u) on freq=%u based on callback",
 		p2p->pending_listen_sec, p2p->pending_listen_usec,
 		p2p->pending_listen_freq);
+	p2p->pending_listen_wait_drv = false;
 	p2p->in_listen = 1;
 	p2p->drv_in_listen = freq;
 	if (p2p->pending_listen_sec || p2p->pending_listen_usec) {
@@ -4036,6 +4052,7 @@
 
 	p2p_dbg(p2p, "Go to Listen state while waiting for the peer to become ready for GO Negotiation");
 	p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+	p2p->pending_listen_wait_drv = false;
 	if (p2p->pending_listen_freq) {
 		p2p_dbg(p2p, "Clear pending_listen_freq for %s", __func__);
 		p2p->pending_listen_freq = 0;
@@ -4089,8 +4106,8 @@
 		int for_join = 0;
 
 		dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-			if (os_memcmp(p2p->pending_pd_devaddr,
-				      dev->info.p2p_device_addr, ETH_ALEN) != 0)
+			if (!ether_addr_equal(p2p->pending_pd_devaddr,
+					      dev->info.p2p_device_addr))
 				continue;
 			if (dev->req_config_methods &&
 			    (dev->flags & P2P_DEV_PD_FOR_JOIN))
@@ -4163,6 +4180,7 @@
 	if (p2p->drv_in_listen) {
 		p2p_dbg(p2p, "Driver is still in listen state - stop it");
 		p2p->cfg->stop_listen(p2p->cfg->cb_ctx);
+		p2p->pending_listen_wait_drv = false;
 	}
 
 	switch (p2p->state) {
@@ -4557,8 +4575,8 @@
 	p2p_dbg(p2p, "Received P2P Action - P2P Presence Request");
 
 	for (g = 0; g < p2p->num_groups; g++) {
-		if (os_memcmp(da, p2p_group_get_interface_addr(p2p->groups[g]),
-			      ETH_ALEN) == 0) {
+		if (ether_addr_equal(
+			    da, p2p_group_get_interface_addr(p2p->groups[g]))) {
 			group = p2p->groups[g];
 			break;
 		}
@@ -4656,15 +4674,6 @@
 				       p2p_ext_listen_timeout, p2p, NULL);
 	}
 
-	if ((p2p->cfg->is_p2p_in_progress &&
-	     p2p->cfg->is_p2p_in_progress(p2p->cfg->cb_ctx)) ||
-	    (p2p->pending_action_state == P2P_PENDING_PD &&
-	     p2p->pd_retries > 0)) {
-		p2p_dbg(p2p, "Operation in progress - skip Extended Listen timeout (%s)",
-			p2p_state_txt(p2p->state));
-		return;
-	}
-
 	if (p2p->state == P2P_LISTEN_ONLY && p2p->ext_listen_only) {
 		/*
 		 * This should not really happen, but it looks like the Listen
@@ -4677,6 +4686,15 @@
 		p2p_set_state(p2p, P2P_IDLE);
 	}
 
+	if ((p2p->cfg->is_p2p_in_progress &&
+	     p2p->cfg->is_p2p_in_progress(p2p->cfg->cb_ctx)) ||
+	    (p2p->pending_action_state == P2P_PENDING_PD &&
+	     p2p->pd_retries > 0)) {
+		p2p_dbg(p2p, "Operation in progress - skip Extended Listen timeout (%s)",
+			p2p_state_txt(p2p->state));
+		return;
+	}
+
 	if (p2p->state != P2P_IDLE) {
 		p2p_dbg(p2p, "Skip Extended Listen timeout in active state (%s)", p2p_state_txt(p2p->state));
 		return;
diff --git a/src/p2p/p2p_dev_disc.c b/src/p2p/p2p_dev_disc.c
index 98805fe..ff2bdfc 100644
--- a/src/p2p/p2p_dev_disc.c
+++ b/src/p2p/p2p_dev_disc.c
@@ -223,7 +223,7 @@
 
 	go = p2p->pending_client_disc_go;
 	if (go == NULL ||
-	    os_memcmp(sa, go->info.p2p_device_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sa, go->info.p2p_device_addr)) {
 		p2p_dbg(p2p, "Ignore unexpected Device Discoverability Response");
 		return;
 	}
@@ -249,10 +249,10 @@
 	p2p_dbg(p2p, "Device Discoverability Response status %u", status);
 
 	if (p2p->go_neg_peer == NULL ||
-	    os_memcmp(p2p->pending_client_disc_addr,
-		      p2p->go_neg_peer->info.p2p_device_addr, ETH_ALEN) != 0 ||
-	    os_memcmp(p2p->go_neg_peer->member_in_go_dev,
-		      go->info.p2p_device_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(p2p->pending_client_disc_addr,
+			      p2p->go_neg_peer->info.p2p_device_addr) ||
+	    !ether_addr_equal(p2p->go_neg_peer->member_in_go_dev,
+			      go->info.p2p_device_addr)) {
 		p2p_dbg(p2p, "No pending operation with the client discoverability peer anymore");
 		return;
 	}
diff --git a/src/p2p/p2p_go_neg.c b/src/p2p/p2p_go_neg.c
index 55db8a5..30901b3 100644
--- a/src/p2p/p2p_go_neg.c
+++ b/src/p2p/p2p_go_neg.c
@@ -859,7 +859,7 @@
 		goto fail;
 	}
 
-	if (os_memcmp(msg.p2p_device_addr, sa, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(msg.p2p_device_addr, sa)) {
 		p2p_dbg(p2p, "Unexpected GO Negotiation Request SA=" MACSTR
 			" != dev_addr=" MACSTR,
 			MAC2STR(sa), MAC2STR(msg.p2p_device_addr));
diff --git a/src/p2p/p2p_group.c b/src/p2p/p2p_group.c
index aa18af6..c036f92 100644
--- a/src/p2p/p2p_group.c
+++ b/src/p2p/p2p_group.c
@@ -579,7 +579,7 @@
 	m = group->members;
 	prev = NULL;
 	while (m) {
-		if (os_memcmp(m->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(m->addr, addr))
 			break;
 		prev = m;
 		m = m->next;
@@ -785,11 +785,11 @@
 	if (!msg.device_id)
 		return 1; /* No filter on Device ID */
 
-	if (os_memcmp(msg.device_id, group->p2p->cfg->dev_addr, ETH_ALEN) == 0)
+	if (ether_addr_equal(msg.device_id, group->p2p->cfg->dev_addr))
 		return 1; /* Match with our P2P Device Address */
 
 	for (m = group->members; m; m = m->next) {
-		if (os_memcmp(msg.device_id, m->dev_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(msg.device_id, m->dev_addr))
 			return 1; /* Match with group client P2P Device Address */
 	}
 
@@ -844,7 +844,7 @@
 	struct p2p_group_member *m;
 
 	for (m = group->members; m; m = m->next) {
-		if (os_memcmp(dev_id, m->dev_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(dev_id, m->dev_addr))
 			return m;
 	}
 
@@ -872,7 +872,7 @@
 	struct p2p_group_member *m;
 
 	for (m = group->members; m; m = m->next) {
-		if (os_memcmp(interface_addr, m->addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(interface_addr, m->addr))
 			return m;
 	}
 
@@ -1038,7 +1038,7 @@
 	struct p2p_group_member *m;
 
 	for (m = group->members; m; m = m->next) {
-		if (os_memcmp(m->dev_addr, dev_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(m->dev_addr, dev_addr))
 			return 1;
 	}
 
@@ -1051,7 +1051,7 @@
 {
 	if (group_id_len != ETH_ALEN + group->cfg->ssid_len)
 		return 0;
-	if (os_memcmp(group_id, group->p2p->cfg->dev_addr, ETH_ALEN) != 0)
+	if (!ether_addr_equal(group_id, group->p2p->cfg->dev_addr))
 		return 0;
 	return os_memcmp(group_id + ETH_ALEN, group->cfg->ssid,
 			 group->cfg->ssid_len) == 0;
diff --git a/src/p2p/p2p_i.h b/src/p2p/p2p_i.h
index 235467e..f24544c 100644
--- a/src/p2p/p2p_i.h
+++ b/src/p2p/p2p_i.h
@@ -400,6 +400,7 @@
 	unsigned int pending_listen_freq;
 	unsigned int pending_listen_sec;
 	unsigned int pending_listen_usec;
+	bool pending_listen_wait_drv;
 
 	u8 dev_capab;
 
diff --git a/src/p2p/p2p_invitation.c b/src/p2p/p2p_invitation.c
index 941ec00..70a7f6f 100644
--- a/src/p2p/p2p_invitation.c
+++ b/src/p2p/p2p_invitation.c
@@ -33,8 +33,8 @@
 		for (i = 0; i < p2p->num_groups; i++) {
 			struct p2p_group *g = p2p->groups[i];
 			struct wpabuf *ie;
-			if (os_memcmp(p2p_group_get_interface_addr(g),
-				      p2p->inv_bssid, ETH_ALEN) != 0)
+			if (!ether_addr_equal(p2p_group_get_interface_addr(g),
+					      p2p->inv_bssid))
 				continue;
 			ie = p2p_group_get_wfd_ie(g);
 			if (ie) {
@@ -127,8 +127,8 @@
 		for (i = 0; i < p2p->num_groups; i++) {
 			struct p2p_group *g = p2p->groups[i];
 			struct wpabuf *ie;
-			if (os_memcmp(p2p_group_get_interface_addr(g),
-				      group_bssid, ETH_ALEN) != 0)
+			if (!ether_addr_equal(p2p_group_get_interface_addr(g),
+					      group_bssid))
 				continue;
 			ie = p2p_group_get_wfd_ie(g);
 			if (ie) {
diff --git a/src/p2p/p2p_pd.c b/src/p2p/p2p_pd.c
index f75cee8..542521e 100644
--- a/src/p2p/p2p_pd.c
+++ b/src/p2p/p2p_pd.c
@@ -544,7 +544,7 @@
 	if (msg->persistent_dev) {
 		channel_list = 1;
 		config_timeout = 1;
-		if (os_memcmp(msg->persistent_dev, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(msg->persistent_dev, addr)) {
 			intended_addr = 1;
 			operating_channel = 1;
 		}
@@ -730,7 +730,7 @@
 	if (!msg.status) {
 		unsigned int forced_freq, pref_freq;
 
-		if (os_memcmp(p2p->cfg->dev_addr, msg.adv_mac, ETH_ALEN)) {
+		if (!ether_addr_equal(p2p->cfg->dev_addr, msg.adv_mac)) {
 			p2p_dbg(p2p,
 				"P2PS PD adv mac does not match the local one");
 			reject = P2P_SC_FAIL_INCOMPATIBLE_PARAMS;
@@ -892,14 +892,14 @@
 		goto out;
 
 	if (p2p->p2ps_prov->adv_id != adv_id ||
-	    os_memcmp(p2p->p2ps_prov->adv_mac, msg.adv_mac, ETH_ALEN)) {
+	    !ether_addr_equal(p2p->p2ps_prov->adv_mac, msg.adv_mac)) {
 		p2p_dbg(p2p,
 			"P2PS Follow-on PD with mismatch Advertisement ID/MAC");
 		goto out;
 	}
 
 	if (p2p->p2ps_prov->session_id != session_id ||
-	    os_memcmp(p2p->p2ps_prov->session_mac, msg.session_mac, ETH_ALEN)) {
+	    !ether_addr_equal(p2p->p2ps_prov->session_mac, msg.session_mac)) {
 		p2p_dbg(p2p, "P2PS Follow-on PD with mismatch Session ID/MAC");
 		goto out;
 	}
@@ -1239,8 +1239,7 @@
 		return -1;
 	}
 
-	if (os_memcmp(p2p->p2ps_prov->session_mac, msg->session_mac,
-		      ETH_ALEN)) {
+	if (!ether_addr_equal(p2p->p2ps_prov->session_mac, msg->session_mac)) {
 		p2p_dbg(p2p,
 			"Ignore PD Response with unexpected Session MAC");
 		return -1;
@@ -1252,7 +1251,7 @@
 		return -1;
 	}
 
-	if (os_memcmp(p2p->p2ps_prov->adv_mac, msg->adv_mac, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(p2p->p2ps_prov->adv_mac, msg->adv_mac)) {
 		p2p_dbg(p2p,
 			"Ignore PD Response with unexpected Advertisement MAC");
 		return -1;
@@ -1396,7 +1395,7 @@
 	 * was sent earlier, we reset that state info here.
 	 */
 	if (p2p->user_initiated_pd &&
-	    os_memcmp(p2p->pending_pd_devaddr, sa, ETH_ALEN) == 0)
+	    ether_addr_equal(p2p->pending_pd_devaddr, sa))
 		p2p_reset_pending_pd(p2p);
 
 	if (msg.wps_config_methods != req_config_methods) {
@@ -1758,8 +1757,8 @@
 	struct p2p_device *dev;
 
 	dl_list_for_each(dev, &p2p->devices, struct p2p_device, list) {
-		if (os_memcmp(p2p->pending_pd_devaddr,
-			      dev->info.p2p_device_addr, ETH_ALEN))
+		if (!ether_addr_equal(p2p->pending_pd_devaddr,
+				      dev->info.p2p_device_addr))
 			continue;
 		if (!dev->req_config_methods)
 			continue;
diff --git a/src/p2p/p2p_sd.c b/src/p2p/p2p_sd.c
index b9e753f..d6882e4 100644
--- a/src/p2p/p2p_sd.c
+++ b/src/p2p/p2p_sd.c
@@ -79,8 +79,7 @@
 			count++;
 		}
 		if (!q->for_all_peers &&
-		    os_memcmp(q->peer, dev->info.p2p_device_addr, ETH_ALEN) ==
-		    0)
+		    ether_addr_equal(q->peer, dev->info.p2p_device_addr))
 			goto found;
 	}
 
@@ -289,8 +288,7 @@
 	if (query == NULL)
 		return -1;
 	if (p2p->state == P2P_SEARCH &&
-	    os_memcmp(p2p->sd_query_no_ack, dev->info.p2p_device_addr,
-		      ETH_ALEN) == 0) {
+	    ether_addr_equal(p2p->sd_query_no_ack, dev->info.p2p_device_addr)) {
 		p2p_dbg(p2p, "Do not start Service Discovery with " MACSTR
 			" due to it being the first no-ACK peer in this search iteration",
 			MAC2STR(dev->info.p2p_device_addr));
@@ -491,12 +489,21 @@
 	u16 slen;
 	u16 update_indic;
 
-	if (p2p->state != P2P_SD_DURING_FIND || p2p->sd_peer == NULL ||
-	    os_memcmp(sa, p2p->sd_peer->info.p2p_device_addr, ETH_ALEN) != 0) {
+	if ((p2p->state != P2P_SD_DURING_FIND && p2p->state != P2P_SEARCH) ||
+	    !p2p->sd_peer ||
+	    !ether_addr_equal(sa, p2p->sd_peer->info.p2p_device_addr)) {
 		p2p_dbg(p2p, "Ignore unexpected GAS Initial Response from "
 			MACSTR, MAC2STR(sa));
 		return;
 	}
+	if (p2p->state == P2P_SEARCH) {
+		/* It is possible for the TX status and RX response events to be
+		 * reordered, so assume the request was ACKed if a response is
+		 * received. */
+		p2p_dbg(p2p,
+			"GAS Initial Request had not yet received TX status - process the response anyway");
+		p2p_set_state(p2p, P2P_SD_DURING_FIND);
+	}
 	p2p->cfg->send_action_done(p2p->cfg->cb_ctx);
 	p2p_clear_timeout(p2p);
 
@@ -642,7 +649,7 @@
 		p2p_dbg(p2p, "No pending SD response fragment available");
 		return;
 	}
-	if (os_memcmp(sa, p2p->sd_resp_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sa, p2p->sd_resp_addr)) {
 		p2p_dbg(p2p, "No pending SD response fragment for " MACSTR,
 			MAC2STR(sa));
 		return;
@@ -707,7 +714,7 @@
 	wpa_hexdump(MSG_DEBUG, "P2P: RX GAS Comeback Response", data, len);
 
 	if (p2p->state != P2P_SD_DURING_FIND || p2p->sd_peer == NULL ||
-	    os_memcmp(sa, p2p->sd_peer->info.p2p_device_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sa, p2p->sd_peer->info.p2p_device_addr)) {
 		p2p_dbg(p2p, "Ignore unexpected GAS Comeback Response from "
 			MACSTR, MAC2STR(sa));
 		return;
diff --git a/src/pae/ieee802_1x_kay.c b/src/pae/ieee802_1x_kay.c
index f75fd8f..bc33a25 100644
--- a/src/pae/ieee802_1x_kay.c
+++ b/src/pae/ieee802_1x_kay.c
@@ -1215,7 +1215,7 @@
 			/* My message id is used by other participant */
 			if (peer_mn > participant->mn &&
 			    !reset_participant_mi(participant))
-				wpa_printf(MSG_DEBUG, "KaY: Could not update mi");
+				wpa_printf(MSG_DEBUG, "KaY: Could not update MI");
 			continue;
 		}
 	}
@@ -2560,7 +2560,7 @@
 	}
 
 	if (ieee802_1x_kay_encode_mkpdu(participant, buf)) {
-		wpa_printf(MSG_ERROR, "KaY: encode mkpdu fail");
+		wpa_printf(MSG_ERROR, "KaY: encode MKPDU fail");
 		return -1;
 	}
 
@@ -2628,9 +2628,7 @@
 			      struct ieee802_1x_kay_peer, list) {
 		if (now.sec > peer->expire) {
 			wpa_printf(MSG_DEBUG, "KaY: Live peer removed");
-			wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi,
-				    sizeof(peer->mi));
-			wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn);
+			ieee802_1x_kay_dump_peer(peer);
 			dl_list_for_each_safe(rxsc, pre_rxsc,
 					      &participant->rxsc_list,
 					      struct receive_sc, list) {
@@ -2659,10 +2657,10 @@
 	if (key_server_removed) {
 		if (!reset_participant_mi(participant))
 			wpa_printf(MSG_WARNING,
-				   "KaY: Could not update mi on key server removal");
+				   "KaY: Could not update MI on key server removal");
 		else
 			wpa_printf(MSG_DEBUG,
-				   "KaY: Update mi on key server removal");
+				   "KaY: Update MI on key server removal");
 	}
 
 	if (lp_changed) {
@@ -2706,9 +2704,7 @@
 			      struct ieee802_1x_kay_peer, list) {
 		if (now.sec > peer->expire) {
 			wpa_printf(MSG_DEBUG, "KaY: Potential peer removed");
-			wpa_hexdump(MSG_DEBUG, "\tMI: ", peer->mi,
-				    sizeof(peer->mi));
-			wpa_printf(MSG_DEBUG, "\tMN: %d", peer->mn);
+			ieee802_1x_kay_dump_peer(peer);
 			dl_list_del(&peer->list);
 			os_free(peer);
 		}
@@ -3165,7 +3161,7 @@
 		   be_to_host16(eth_hdr->ethertype));
 
 	/* the destination address shall not be an individual address */
-	if (os_memcmp(eth_hdr->dest, pae_group_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(eth_hdr->dest, pae_group_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "KaY: ethernet destination address is not PAE group address");
 		return -1;
@@ -3413,7 +3409,7 @@
 			   "KaY: Discarding Rx MKPDU: decode of parameter set type (%d) failed",
 			   MKA_SAK_USE);
 		if (!reset_participant_mi(participant))
-			wpa_printf(MSG_DEBUG, "KaY: Could not update mi");
+			wpa_printf(MSG_DEBUG, "KaY: Could not update MI");
 		else
 			wpa_printf(MSG_DEBUG,
 				   "KaY: Selected a new random MI: %s",
@@ -3630,7 +3626,7 @@
 		goto error;
 	}
 
-	wpa_printf(MSG_DEBUG, "KaY: secy init macsec done");
+	wpa_printf(MSG_DEBUG, "KaY: SecY init MACsec done");
 
 	/* init CP */
 	kay->cp = ieee802_1x_cp_sm_init(kay);
@@ -3721,7 +3717,7 @@
 		   kay->if_name, mode_txt(mode), yes_no(is_authenticator));
 
 	if (!kay || !ckn || !cak) {
-		wpa_printf(MSG_ERROR, "KaY: ckn or cak is null");
+		wpa_printf(MSG_ERROR, "KaY: CKN or CAK is null");
 		return NULL;
 	}
 
diff --git a/src/pasn/pasn_initiator.c b/src/pasn/pasn_initiator.c
index 1f9a508..35c6206 100644
--- a/src/pasn/pasn_initiator.c
+++ b/src/pasn/pasn_initiator.c
@@ -378,8 +378,8 @@
 		return -1;
 	}
 
-	fils_wd = ieee802_11_defrag(&elems, WLAN_EID_EXTENSION,
-				    WLAN_EID_EXT_WRAPPED_DATA);
+	fils_wd = ieee802_11_defrag(elems.wrapped_data, elems.wrapped_data_len,
+				    true);
 
 	if (!fils_wd) {
 		wpa_printf(MSG_DEBUG,
@@ -1024,15 +1024,15 @@
 		return false;
 
 	/* Not our frame; do nothing */
-	if (os_memcmp(mgmt->bssid, pasn->bssid, ETH_ALEN) != 0)
+	if (!ether_addr_equal(mgmt->bssid, pasn->bssid))
 		return false;
 
-	if (rx && (os_memcmp(mgmt->da, pasn->own_addr, ETH_ALEN) != 0 ||
-		   os_memcmp(mgmt->sa, pasn->peer_addr, ETH_ALEN) != 0))
+	if (rx && (!ether_addr_equal(mgmt->da, pasn->own_addr) ||
+		   !ether_addr_equal(mgmt->sa, pasn->peer_addr)))
 		return false;
 
-	if (!rx && (os_memcmp(mgmt->sa, pasn->own_addr, ETH_ALEN) != 0 ||
-		    os_memcmp(mgmt->da, pasn->peer_addr, ETH_ALEN) != 0))
+	if (!rx && (!ether_addr_equal(mgmt->sa, pasn->own_addr) ||
+		    !ether_addr_equal(mgmt->da, pasn->peer_addr)))
 		return false;
 
 	/* Not PASN; do nothing */
@@ -1200,9 +1200,9 @@
 	}
 
 	if (pasn_params->wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) {
-		wrapped_data = ieee802_11_defrag(&elems,
-						 WLAN_EID_EXTENSION,
-						 WLAN_EID_EXT_WRAPPED_DATA);
+		wrapped_data = ieee802_11_defrag(elems.wrapped_data,
+						 elems.wrapped_data_len,
+						 true);
 
 		if (!wrapped_data) {
 			wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data");
diff --git a/src/pasn/pasn_responder.c b/src/pasn/pasn_responder.c
index 47be403..7501e7a 100644
--- a/src/pasn/pasn_responder.c
+++ b/src/pasn/pasn_responder.c
@@ -753,9 +753,8 @@
 
 	derive_keys = true;
 	if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) {
-		wrapped_data = ieee802_11_defrag(&elems,
-						 WLAN_EID_EXTENSION,
-						 WLAN_EID_EXT_WRAPPED_DATA);
+		wrapped_data = ieee802_11_defrag(elems.wrapped_data,
+						 elems.wrapped_data_len, true);
 		if (!wrapped_data) {
 			wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data");
 			status = WLAN_STATUS_UNSPECIFIED_FAILURE;
@@ -979,9 +978,9 @@
 	}
 
 	if (pasn_params.wrapped_data_format != WPA_PASN_WRAPPED_DATA_NO) {
-		wrapped_data = ieee802_11_defrag(&elems,
-						 WLAN_EID_EXTENSION,
-						 WLAN_EID_EXT_WRAPPED_DATA);
+		wrapped_data = ieee802_11_defrag(elems.wrapped_data,
+						 elems.wrapped_data_len,
+						 true);
 
 		if (!wrapped_data) {
 			wpa_printf(MSG_DEBUG, "PASN: Missing wrapped data");
diff --git a/src/radius/radius_client.c b/src/radius/radius_client.c
index ee9e46d..18aaec1 100644
--- a/src/radius/radius_client.c
+++ b/src/radius/radius_client.c
@@ -1556,7 +1556,7 @@
 	entry = radius->msgs;
 	while (entry) {
 		if (entry->msg_type == RADIUS_AUTH &&
-		    os_memcmp(entry->addr, addr, ETH_ALEN) == 0) {
+		    ether_addr_equal(entry->addr, addr)) {
 			hostapd_logger(radius->ctx, addr,
 				       HOSTAPD_MODULE_RADIUS,
 				       HOSTAPD_LEVEL_DEBUG,
diff --git a/src/rsn_supp/pmksa_cache.c b/src/rsn_supp/pmksa_cache.c
index b2c4809..e243756 100644
--- a/src/rsn_supp/pmksa_cache.c
+++ b/src/rsn_supp/pmksa_cache.c
@@ -302,8 +302,8 @@
 	pos = pmksa->pmksa;
 	prev = NULL;
 	while (pos) {
-		if (os_memcmp(entry->aa, pos->aa, ETH_ALEN) == 0 &&
-		    os_memcmp(entry->spa, pos->spa, ETH_ALEN) == 0) {
+		if (ether_addr_equal(entry->aa, pos->aa) &&
+		    ether_addr_equal(entry->spa, pos->spa)) {
 			if (pos->pmk_len == entry->pmk_len &&
 			    os_memcmp_const(pos->pmk, entry->pmk,
 					    entry->pmk_len) == 0 &&
@@ -489,8 +489,8 @@
 {
 	struct rsn_pmksa_cache_entry *entry = pmksa->pmksa;
 	while (entry) {
-		if ((aa == NULL || os_memcmp(entry->aa, aa, ETH_ALEN) == 0) &&
-		    (!spa || os_memcmp(entry->spa, spa, ETH_ALEN) == 0) &&
+		if ((aa == NULL || ether_addr_equal(entry->aa, aa)) &&
+		    (!spa || ether_addr_equal(entry->spa, spa)) &&
 		    (pmkid == NULL ||
 		     os_memcmp(entry->pmkid, pmkid, PMKID_LEN) == 0) &&
 		    (!akmp || akmp == entry->akmp) &&
diff --git a/src/rsn_supp/preauth.c b/src/rsn_supp/preauth.c
index 1a28884..026a98d 100644
--- a/src/rsn_supp/preauth.c
+++ b/src/rsn_supp/preauth.c
@@ -69,7 +69,7 @@
 
 	if (sm->preauth_eapol == NULL ||
 	    is_zero_ether_addr(sm->preauth_bssid) ||
-	    os_memcmp(sm->preauth_bssid, src_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sm->preauth_bssid, src_addr)) {
 		wpa_printf(MSG_WARNING, "RSN pre-auth frame received from "
 			   "unexpected source " MACSTR " - dropped",
 			   MAC2STR(src_addr));
@@ -333,7 +333,7 @@
 		struct rsn_pmksa_cache_entry *p = NULL;
 		p = pmksa_cache_get(sm->pmksa, candidate->bssid, sm->own_addr,
 				    NULL, NULL, 0);
-		if (os_memcmp(sm->bssid, candidate->bssid, ETH_ALEN) != 0 &&
+		if (!ether_addr_equal(sm->bssid, candidate->bssid) &&
 		    (p == NULL || p->opportunistic)) {
 			wpa_msg(sm->ctx->msg_ctx, MSG_DEBUG, "RSN: PMKSA "
 				"candidate " MACSTR
@@ -395,7 +395,7 @@
 	cand = NULL;
 	dl_list_for_each(pos, &sm->pmksa_candidates,
 			 struct rsn_pmksa_candidate, list) {
-		if (os_memcmp(pos->bssid, bssid, ETH_ALEN) == 0) {
+		if (ether_addr_equal(pos->bssid, bssid)) {
 			cand = pos;
 			break;
 		}
@@ -487,7 +487,7 @@
 	    os_memcmp(ssid + 2, sm->ssid, sm->ssid_len) != 0)
 		return; /* Not for the current SSID */
 
-	if (os_memcmp(bssid, sm->bssid, ETH_ALEN) == 0)
+	if (ether_addr_equal(bssid, sm->bssid))
 		return; /* Ignore current AP */
 
 	if (wpa_parse_wpa_ie(rsn, 2 + rsn[1], &ie))
diff --git a/src/rsn_supp/tdls.c b/src/rsn_supp/tdls.c
index e6f5877..8a75091 100644
--- a/src/rsn_supp/tdls.c
+++ b/src/rsn_supp/tdls.c
@@ -294,7 +294,7 @@
 		return 0; /* No retries */
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, dest, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, dest))
 			break;
 	}
 
@@ -791,7 +791,7 @@
 
 	/* Find the node and free from the list */
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -880,7 +880,7 @@
 		return -1;
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -913,7 +913,7 @@
 	struct wpa_tdls_peer *peer;
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -949,7 +949,7 @@
 		return "disabled";
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -976,7 +976,7 @@
 
 	/* Find the node and free from the list */
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, src_addr))
 			break;
 	}
 
@@ -1072,7 +1072,7 @@
 	if (existing)
 		*existing = 0;
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(peer->addr, addr)) {
 			if (existing)
 				*existing = 1;
 			return peer; /* re-use existing entry */
@@ -1564,15 +1564,15 @@
 	*link_id = -1;
 
 	if (!sm->mlo.valid_links) {
-		if (os_memcmp(sm->bssid, lnkid->bssid, ETH_ALEN) != 0)
+		if (!ether_addr_equal(sm->bssid, lnkid->bssid))
 			return false;
 	} else {
 		int i;
 
 		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
 			if ((sm->mlo.valid_links & BIT(i)) &&
-			    os_memcmp(lnkid->bssid, sm->mlo.links[i].bssid,
-				      ETH_ALEN) == 0) {
+			    ether_addr_equal(lnkid->bssid,
+					     sm->mlo.links[i].bssid)) {
 				*link_id = i;
 				break;
 			}
@@ -2371,7 +2371,7 @@
 	wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Response / TPK M2 "
 		   "(Peer " MACSTR ")", MAC2STR(src_addr));
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, src_addr))
 			break;
 	}
 	if (peer == NULL) {
@@ -2460,8 +2460,8 @@
 		    kde.lnkid, kde.lnkid_len);
 	lnkid = (struct wpa_tdls_lnkid *) kde.lnkid;
 
-	if (os_memcmp(sm->bssid, wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
-		      ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sm->bssid,
+			      wpa_tdls_get_link_bssid(sm, peer->mld_link_id))) {
 		wpa_printf(MSG_INFO, "TDLS: TPK M2 from different BSS");
 		status = WLAN_STATUS_NOT_IN_SAME_BSS;
 		goto error;
@@ -2668,7 +2668,7 @@
 	wpa_printf(MSG_DEBUG, "TDLS: Received TDLS Setup Confirm / TPK M3 "
 		   "(Peer " MACSTR ")", MAC2STR(src_addr));
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, src_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, src_addr))
 			break;
 	}
 	if (peer == NULL) {
@@ -2713,8 +2713,8 @@
 		    (u8 *) kde.lnkid, kde.lnkid_len);
 	lnkid = (struct wpa_tdls_lnkid *) kde.lnkid;
 
-	if (os_memcmp(wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
-		      lnkid->bssid, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(wpa_tdls_get_link_bssid(sm, peer->mld_link_id),
+			      lnkid->bssid)) {
 		wpa_printf(MSG_INFO, "TDLS: TPK M3 from diff BSS");
 		goto error;
 	}
@@ -2899,7 +2899,7 @@
 		return;
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -2937,7 +2937,7 @@
 		return;
 	}
 
-	if (os_memcmp(src_addr, sm->own_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(src_addr, sm->own_addr)) {
 		wpa_printf(MSG_DEBUG, "TDLS: Discard copy of own message");
 		return;
 	}
@@ -3187,7 +3187,7 @@
 					const u8 *buf, size_t len)
 {
 	struct ieee802_11_elems elems;
-	struct wpa_tdls_lnkid lnkid;
+	const struct wpa_tdls_lnkid *lnkid;
 	struct wpa_tdls_peer *peer;
 	size_t min_req_len = 1 /* Dialog Token */ + 2 /* Capability */ +
 		sizeof(struct wpa_tdls_lnkid);
@@ -3217,12 +3217,12 @@
 		return -1;
 	}
 
-	os_memcpy(&lnkid.bssid[0], elems.link_id, sizeof(lnkid) - 2);
+	lnkid = (const struct wpa_tdls_lnkid *) (elems.link_id - 2);
 
-	if (!wpa_tdls_is_lnkid_bss_valid(sm, &lnkid, &link_id)) {
+	if (!wpa_tdls_is_lnkid_bss_valid(sm, lnkid, &link_id)) {
 		wpa_printf(MSG_DEBUG,
 			   "TDLS: Discovery Response from different BSS "
-			   MACSTR, MAC2STR(lnkid.bssid));
+			   MACSTR, MAC2STR(lnkid->bssid));
 		return -1;
 	}
 
@@ -3234,7 +3234,7 @@
 
 	peer->mld_link_id = link_id;
 	wpa_printf(MSG_DEBUG, "TDLS: Link identifier BSS: " MACSTR
-		   " , link id: %u", MAC2STR(lnkid.bssid), link_id);
+		   " , link id: %u", MAC2STR(lnkid->bssid), link_id);
 
 	return 0;
 }
@@ -3263,7 +3263,7 @@
 	}
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
@@ -3297,7 +3297,7 @@
 		return -1;
 
 	for (peer = sm->tdls; peer; peer = peer->next) {
-		if (os_memcmp(peer->addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(peer->addr, addr))
 			break;
 	}
 
diff --git a/src/rsn_supp/wpa.c b/src/rsn_supp/wpa.c
index 9f49cf9..3eaa015 100644
--- a/src/rsn_supp/wpa.c
+++ b/src/rsn_supp/wpa.c
@@ -233,6 +233,12 @@
 		return;
 	}
 
+	if (!sm->ptk_set) {
+		wpa_printf(MSG_INFO,
+			   "WPA: No PTK derived yet - cannot send EAPOL-Key Request");
+		return;
+	}
+
 	if (wpa_use_akm_defined(sm->key_mgmt))
 		ver = WPA_KEY_INFO_TYPE_AKM_DEFINED;
 	else if (wpa_key_mgmt_ft(sm->key_mgmt) ||
@@ -254,10 +260,11 @@
 		       sm->proto == WPA_PROTO_OSEN) ?
 		EAPOL_KEY_TYPE_RSN : EAPOL_KEY_TYPE_WPA;
 	key_info = WPA_KEY_INFO_REQUEST | ver;
-	if (sm->ptk_set)
-		key_info |= WPA_KEY_INFO_SECURE;
-	if (sm->ptk_set && mic_len)
+	key_info |= WPA_KEY_INFO_SECURE;
+	if (mic_len)
 		key_info |= WPA_KEY_INFO_MIC;
+	else
+		key_info |= WPA_KEY_INFO_ENCR_KEY_DATA;
 	if (error)
 		key_info |= WPA_KEY_INFO_ERROR;
 	if (pairwise)
@@ -522,11 +529,14 @@
 			       const u8 *wpa_ie, size_t wpa_ie_len,
 			       struct wpa_ptk *ptk)
 {
-	size_t mic_len, hdrlen, rlen;
+	size_t mic_len, hdrlen, rlen, extra_len = 0;
 	struct wpa_eapol_key *reply;
 	u8 *rbuf, *key_mic;
 	u8 *rsn_ie_buf = NULL;
 	u16 key_info;
+#ifdef CONFIG_TESTING_OPTIONS
+	size_t pad_len = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
 
 	if (wpa_ie == NULL) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING, "WPA: No wpa_ie set - "
@@ -550,7 +560,7 @@
 			return -1;
 		os_memcpy(rsn_ie_buf, wpa_ie, wpa_ie_len);
 		res = wpa_insert_pmkid(rsn_ie_buf, &wpa_ie_len,
-				       sm->pmk_r1_name);
+				       sm->pmk_r1_name, !sm->ft_prepend_pmkid);
 		if (res < 0) {
 			os_free(rsn_ie_buf);
 			return -1;
@@ -574,10 +584,21 @@
 
 	wpa_hexdump(MSG_DEBUG, "WPA: WPA IE for msg 2/4", wpa_ie, wpa_ie_len);
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->test_eapol_m2_elems)
+		extra_len = wpabuf_len(sm->test_eapol_m2_elems);
+	if (sm->encrypt_eapol_m2) {
+		pad_len = (wpa_ie_len + extra_len) % 8;
+		if (pad_len)
+			pad_len = 8 - pad_len;
+		extra_len += pad_len + 8;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	mic_len = wpa_mic_len(sm->key_mgmt, sm->pmk_len);
 	hdrlen = sizeof(*reply) + mic_len + 2;
 	rbuf = wpa_sm_alloc_eapol(sm, IEEE802_1X_TYPE_EAPOL_KEY,
-				  NULL, hdrlen + wpa_ie_len,
+				  NULL, hdrlen + wpa_ie_len + extra_len,
 				  &rlen, (void *) &reply);
 	if (rbuf == NULL) {
 		os_free(rsn_ie_buf);
@@ -594,6 +615,10 @@
 		key_info |= WPA_KEY_INFO_MIC;
 	else
 		key_info |= WPA_KEY_INFO_ENCR_KEY_DATA;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->encrypt_eapol_m2)
+		key_info |= WPA_KEY_INFO_ENCR_KEY_DATA;
+#endif /* CONFIG_TESTING_OPTIONS */
 	WPA_PUT_BE16(reply->key_info, key_info);
 	if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN)
 		WPA_PUT_BE16(reply->key_length, 0);
@@ -605,9 +630,48 @@
 		    WPA_REPLAY_COUNTER_LEN);
 
 	key_mic = (u8 *) (reply + 1);
-	WPA_PUT_BE16(key_mic + mic_len, wpa_ie_len); /* Key Data Length */
+	/* Key Data Length */
+	WPA_PUT_BE16(key_mic + mic_len, wpa_ie_len + extra_len);
 	os_memcpy(key_mic + mic_len + 2, wpa_ie, wpa_ie_len); /* Key Data */
 	os_free(rsn_ie_buf);
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->test_eapol_m2_elems) {
+		os_memcpy(key_mic + mic_len + 2 + wpa_ie_len,
+			  wpabuf_head(sm->test_eapol_m2_elems),
+			  wpabuf_len(sm->test_eapol_m2_elems));
+	}
+
+	if (sm->encrypt_eapol_m2) {
+		u8 *plain;
+		size_t plain_len;
+
+		if (sm->test_eapol_m2_elems)
+			extra_len = wpabuf_len(sm->test_eapol_m2_elems);
+		else
+			extra_len = 0;
+		plain_len = wpa_ie_len + extra_len + pad_len;
+		plain = os_memdup(key_mic + mic_len + 2, plain_len);
+		if (!plain) {
+			os_free(rbuf);
+			return -1;
+		}
+		if (pad_len)
+			plain[plain_len - pad_len] = 0xdd;
+
+		wpa_hexdump_key(MSG_DEBUG, "RSN: AES-WRAP using KEK",
+				ptk->kek, ptk->kek_len);
+		if (aes_wrap(ptk->kek, ptk->kek_len, plain_len / 8, plain,
+			     key_mic + mic_len + 2)) {
+			os_free(plain);
+			os_free(rbuf);
+			return -1;
+		}
+		wpa_hexdump(MSG_DEBUG,
+			    "RSN: Encrypted Key Data from AES-WRAP",
+			    key_mic + mic_len + 2, plain_len + 8);
+		os_free(plain);
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
 
 	os_memcpy(reply->key_nonce, nonce, WPA_NONCE_LEN);
 
@@ -769,7 +833,7 @@
 static bool is_valid_ap_mld_mac_kde(struct wpa_sm *sm, const u8 *mac_kde)
 {
 	return mac_kde &&
-		os_memcmp(mac_kde, sm->mlo.ap_mld_addr, ETH_ALEN) == 0;
+		ether_addr_equal(mac_kde, sm->mlo.ap_mld_addr);
 }
 
 
@@ -2155,7 +2219,10 @@
 	struct wpa_eapol_key *reply;
 	u8 *rbuf, *key_mic;
 	u8 *kde = NULL;
-	size_t kde_len = 0;
+	size_t kde_len = 0, extra_len = 0;
+#ifdef CONFIG_TESTING_OPTIONS
+	size_t pad_len = 0;
+#endif /* CONFIG_TESTING_OPTIONS */
 
 	if (sm->mlo.valid_links) {
 		u8 *pos;
@@ -2172,10 +2239,22 @@
 		kde_len = pos - kde;
 	}
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->test_eapol_m4_elems)
+		extra_len = wpabuf_len(sm->test_eapol_m4_elems);
+	if (sm->encrypt_eapol_m4) {
+		pad_len = (kde_len + extra_len) % 8;
+		if (pad_len)
+			pad_len = 8 - pad_len;
+		extra_len += pad_len + 8;
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	mic_len = wpa_mic_len(sm->key_mgmt, sm->pmk_len);
 	hdrlen = sizeof(*reply) + mic_len + 2;
 	rbuf = wpa_sm_alloc_eapol(sm, IEEE802_1X_TYPE_EAPOL_KEY, NULL,
-				  hdrlen + kde_len, &rlen, (void *) &reply);
+				  hdrlen + kde_len + extra_len, &rlen,
+				  (void *) &reply);
 	if (!rbuf) {
 		os_free(kde);
 		return -1;
@@ -2190,6 +2269,10 @@
 		key_info |= WPA_KEY_INFO_MIC;
 	else
 		key_info |= WPA_KEY_INFO_ENCR_KEY_DATA;
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->encrypt_eapol_m4)
+		key_info |= WPA_KEY_INFO_ENCR_KEY_DATA;
+#endif /* CONFIG_TESTING_OPTIONS */
 	WPA_PUT_BE16(reply->key_info, key_info);
 	if (sm->proto == WPA_PROTO_RSN || sm->proto == WPA_PROTO_OSEN)
 		WPA_PUT_BE16(reply->key_length, 0);
@@ -2199,12 +2282,52 @@
 		  WPA_REPLAY_COUNTER_LEN);
 
 	key_mic = (u8 *) (reply + 1);
-	WPA_PUT_BE16(key_mic + mic_len, kde_len); /* Key Data length */
+	/* Key Data length */
+	WPA_PUT_BE16(key_mic + mic_len, kde_len + extra_len);
 	if (kde) {
 		os_memcpy(key_mic + mic_len + 2, kde, kde_len); /* Key Data */
 		os_free(kde);
 	}
 
+#ifdef CONFIG_TESTING_OPTIONS
+	if (sm->test_eapol_m4_elems) {
+		os_memcpy(key_mic + mic_len + 2 + kde_len,
+			  wpabuf_head(sm->test_eapol_m4_elems),
+			  wpabuf_len(sm->test_eapol_m4_elems));
+	}
+
+	if (sm->encrypt_eapol_m4) {
+		u8 *plain;
+		size_t plain_len;
+
+		if (sm->test_eapol_m4_elems)
+			extra_len = wpabuf_len(sm->test_eapol_m4_elems);
+		else
+			extra_len = 0;
+		plain_len = kde_len + extra_len + pad_len;
+		plain = os_memdup(key_mic + mic_len + 2, plain_len);
+		if (!plain) {
+			os_free(rbuf);
+			return -1;
+		}
+		if (pad_len)
+			plain[plain_len - pad_len] = 0xdd;
+
+		wpa_hexdump_key(MSG_DEBUG, "RSN: AES-WRAP using KEK",
+				ptk->kek, ptk->kek_len);
+		if (aes_wrap(ptk->kek, ptk->kek_len, plain_len / 8, plain,
+			     key_mic + mic_len + 2)) {
+			os_free(plain);
+			os_free(rbuf);
+			return -1;
+		}
+		wpa_hexdump(MSG_DEBUG,
+			    "RSN: Encrypted Key Data from AES-WRAP",
+			    key_mic + mic_len + 2, plain_len + 8);
+		os_free(plain);
+	}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	wpa_dbg(sm->ctx->msg_ctx, MSG_INFO, "WPA: Sending EAPOL-Key 4/4");
 	return wpa_eapol_key_send(sm, ptk, ver, dst, ETH_P_EAPOL, rbuf, rlen,
 				  key_mic);
@@ -2226,9 +2349,8 @@
 		return -1;
 	}
 
-	if (os_memcmp(sm->mlo.links[link_id].bssid,
-		      &link_kde[RSN_MLO_LINK_KDE_LINK_MAC_INDEX],
-		      ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sm->mlo.links[link_id].bssid,
+			      &link_kde[RSN_MLO_LINK_KDE_LINK_MAC_INDEX])) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"RSN: MLO Link %u MAC address (" MACSTR
 			") not matching association response (" MACSTR ")",
@@ -2319,7 +2441,7 @@
 	    (unsigned int) wpa_cipher_key_len(sm->mgmt_group_cipher)) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_INFO,
 			"RSN MLO: Invalid IGTK KDE length %lu for link ID %u",
-			(unsigned long) ie->mlo_igtk_len, link_id);
+			(unsigned long) ie->mlo_igtk_len[link_id], link_id);
 		return -1;
 	}
 
@@ -2331,7 +2453,7 @@
 	    (unsigned int) wpa_cipher_key_len(sm->mgmt_group_cipher)) {
 		wpa_msg(sm->ctx->msg_ctx, MSG_WARNING,
 			"RSN MLO: Invalid BIGTK KDE length %lu for link ID %u",
-			(unsigned long) ie->mlo_bigtk_len, link_id);
+			(unsigned long) ie->mlo_bigtk_len[link_id], link_id);
 		return -1;
 	}
 
@@ -4041,6 +4163,8 @@
 #endif /* CONFIG_IEEE80211R */
 #ifdef CONFIG_TESTING_OPTIONS
 	wpabuf_free(sm->test_assoc_ie);
+	wpabuf_free(sm->test_eapol_m2_elems);
+	wpabuf_free(sm->test_eapol_m4_elems);
 #endif /* CONFIG_TESTING_OPTIONS */
 #ifdef CONFIG_FILS_SK_PFS
 	crypto_ecdh_deinit(sm->fils_ecdh);
@@ -4111,7 +4235,7 @@
 	os_memset(sm->rx_replay_counter, 0, WPA_REPLAY_COUNTER_LEN);
 	sm->rx_replay_counter_set = 0;
 	sm->renew_snonce = 1;
-	if (os_memcmp(sm->preauth_bssid, bssid, ETH_ALEN) == 0)
+	if (ether_addr_equal(sm->preauth_bssid, bssid))
 		rsn_preauth_deinit(sm);
 
 #ifdef CONFIG_IEEE80211R
@@ -4557,6 +4681,12 @@
 	case WPA_PARAM_DISABLE_EAPOL_G2_TX:
 		sm->disable_eapol_g2_tx = value;
 		break;
+	case WPA_PARAM_ENCRYPT_EAPOL_M2:
+		sm->encrypt_eapol_m2 = value;
+		break;
+	case WPA_PARAM_ENCRYPT_EAPOL_M4:
+		sm->encrypt_eapol_m4 = value;
+		break;
 #endif /* CONFIG_TESTING_OPTIONS */
 #ifdef CONFIG_DPP2
 	case WPA_PARAM_DPP_PFS:
@@ -4566,6 +4696,9 @@
 	case WPA_PARAM_WMM_ENABLED:
 		sm->wmm_enabled = value;
 		break;
+	case WPA_PARAM_FT_PREPEND_PMKID:
+		sm->ft_prepend_pmkid = value;
+		break;
 	default:
 		break;
 	}
@@ -5247,6 +5380,20 @@
 }
 
 
+void wpa_sm_set_test_eapol_m2_elems(struct wpa_sm *sm, struct wpabuf *buf)
+{
+	wpabuf_free(sm->test_eapol_m2_elems);
+	sm->test_eapol_m2_elems = buf;
+}
+
+
+void wpa_sm_set_test_eapol_m4_elems(struct wpa_sm *sm, struct wpabuf *buf)
+{
+	wpabuf_free(sm->test_eapol_m4_elems);
+	sm->test_eapol_m4_elems = buf;
+}
+
+
 const u8 * wpa_sm_get_anonce(struct wpa_sm *sm)
 {
 	return sm->anonce;
diff --git a/src/rsn_supp/wpa.h b/src/rsn_supp/wpa.h
index d8d0a15..7d790a6 100644
--- a/src/rsn_supp/wpa.h
+++ b/src/rsn_supp/wpa.h
@@ -132,6 +132,9 @@
 	WPA_PARAM_OCI_FREQ_FT_ASSOC,
 	WPA_PARAM_OCI_FREQ_FILS_ASSOC,
 	WPA_PARAM_DISABLE_EAPOL_G2_TX,
+	WPA_PARAM_ENCRYPT_EAPOL_M2,
+	WPA_PARAM_ENCRYPT_EAPOL_M4,
+	WPA_PARAM_FT_PREPEND_PMKID,
 };
 
 struct rsn_supp_config {
@@ -605,6 +608,8 @@
 
 int wpa_wnmsleep_install_key(struct wpa_sm *sm, u8 subelem_id, u8 *buf);
 void wpa_sm_set_test_assoc_ie(struct wpa_sm *sm, struct wpabuf *buf);
+void wpa_sm_set_test_eapol_m2_elems(struct wpa_sm *sm, struct wpabuf *buf);
+void wpa_sm_set_test_eapol_m4_elems(struct wpa_sm *sm, struct wpabuf *buf);
 const u8 * wpa_sm_get_anonce(struct wpa_sm *sm);
 unsigned int wpa_sm_get_key_mgmt(struct wpa_sm *sm);
 
diff --git a/src/rsn_supp/wpa_ft.c b/src/rsn_supp/wpa_ft.c
index 3d1dbc6..9a39749 100644
--- a/src/rsn_supp/wpa_ft.c
+++ b/src/rsn_supp/wpa_ft.c
@@ -607,7 +607,7 @@
 			goto fail;
 		}
 
-		if (os_memcmp(target_ap, sm->target_ap, ETH_ALEN) != 0) {
+		if (!ether_addr_equal(target_ap, sm->target_ap)) {
 			wpa_printf(MSG_DEBUG, "FT: No over-the-DS in progress "
 				   "with this Target AP - drop FT Action "
 				   "Response");
@@ -1348,7 +1348,7 @@
 	size_t i;
 
 	for (i = 0; i < sm->n_pasn_r1kh; i++)
-		if (os_memcmp(sm->pasn_r1kh[i].bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(sm->pasn_r1kh[i].bssid, bssid))
 			return &sm->pasn_r1kh[i];
 
 	return NULL;
diff --git a/src/rsn_supp/wpa_i.h b/src/rsn_supp/wpa_i.h
index 5fe6182..ca97c12 100644
--- a/src/rsn_supp/wpa_i.h
+++ b/src/rsn_supp/wpa_i.h
@@ -182,12 +182,16 @@
 
 #ifdef CONFIG_TESTING_OPTIONS
 	struct wpabuf *test_assoc_ie;
+	struct wpabuf *test_eapol_m2_elems;
+	struct wpabuf *test_eapol_m4_elems;
 	int ft_rsnxe_used;
 	unsigned int oci_freq_override_eapol;
 	unsigned int oci_freq_override_eapol_g2;
 	unsigned int oci_freq_override_ft_assoc;
 	unsigned int oci_freq_override_fils_assoc;
 	unsigned int disable_eapol_g2_tx;
+	bool encrypt_eapol_m2;
+	bool encrypt_eapol_m4;
 #endif /* CONFIG_TESTING_OPTIONS */
 
 #ifdef CONFIG_FILS
@@ -223,6 +227,7 @@
 
 	bool wmm_enabled;
 	bool driver_bss_selection;
+	bool ft_prepend_pmkid;
 };
 
 
diff --git a/src/tls/libtommath.c b/src/tls/libtommath.c
index 7156744..ed595bd 100644
--- a/src/tls/libtommath.c
+++ b/src/tls/libtommath.c
@@ -59,14 +59,6 @@
 
 /* from tommath.h */
 
-#ifndef MIN
-   #define MIN(x,y) ((x)<(y)?(x):(y))
-#endif
-
-#ifndef MAX
-   #define MAX(x,y) ((x)>(y)?(x):(y))
-#endif
-
 #define  OPT_CAST(x)
 
 #ifdef __x86_64__
diff --git a/src/tls/pkcs1.c b/src/tls/pkcs1.c
index 49e439d..7ea9cc7 100644
--- a/src/tls/pkcs1.c
+++ b/src/tls/pkcs1.c
@@ -322,8 +322,6 @@
 		return -1;
 	}
 
-	os_free(decrypted);
-
 	if (hdr.payload + hdr.length != decrypted + decrypted_len) {
 		wpa_printf(MSG_INFO,
 			   "PKCS #1: Extra data after signature - reject");
@@ -332,8 +330,12 @@
 			    hdr.payload + hdr.length,
 			    decrypted + decrypted_len - hdr.payload -
 			    hdr.length);
+
+		os_free(decrypted);
 		return -1;
 	}
 
+	os_free(decrypted);
+
 	return 0;
 }
diff --git a/src/utils/common.h b/src/utils/common.h
index bede21e..079f90e 100644
--- a/src/utils/common.h
+++ b/src/utils/common.h
@@ -9,6 +9,7 @@
 #ifndef COMMON_H
 #define COMMON_H
 
+#include "includes.h"
 #include "os.h"
 
 #if defined(__linux__) || defined(__GLIBC__)
@@ -441,6 +442,13 @@
 #define BIT(x) (1U << (x))
 #endif
 
+#ifndef MIN
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#endif
+#ifndef MAX
+#define MAX(a, b) (((a) > (b)) ? (a) : (b))
+#endif
+
 /*
  * Definitions for sparse validation
  * (http://kernel.org/pub/linux/kernel/people/josh/sparse/)
@@ -543,6 +551,11 @@
 	return a[0] & 0x01;
 }
 
+static inline bool ether_addr_equal(const u8 *a, const u8 *b)
+{
+	return os_memcmp(a, b, ETH_ALEN) == 0;
+}
+
 #define broadcast_ether_addr (const u8 *) "\xff\xff\xff\xff\xff\xff"
 
 #include "wpa_debug.h"
diff --git a/src/utils/os.h b/src/utils/os.h
index 21ba5c3..1bbaea3 100644
--- a/src/utils/os.h
+++ b/src/utils/os.h
@@ -108,6 +108,26 @@
 }
 
 
+static inline void os_reltime_add_ms(struct os_reltime *ts, int ms)
+{
+	ts->usec += ms * 1000;
+	while (ts->usec >= 1000000) {
+		ts->sec++;
+		ts->usec -= 1000000;
+	}
+	while (ts->usec < 0) {
+		ts->sec--;
+		ts->usec += 1000000;
+	}
+}
+
+
+static inline int os_reltime_in_ms(struct os_reltime *ts)
+{
+	return ts->sec * 1000 + ts->usec / 1000;
+}
+
+
 static inline int os_reltime_initialized(struct os_reltime *t)
 {
 	return t->sec != 0 || t->usec != 0;
@@ -667,14 +687,24 @@
 
 
 #if defined(WPA_TRACE_BFD) && defined(CONFIG_TESTING_OPTIONS)
-#define TEST_FAIL() testing_test_fail()
-int testing_test_fail(void);
-extern char wpa_trace_fail_func[256];
-extern unsigned int wpa_trace_fail_after;
-extern char wpa_trace_test_fail_func[256];
-extern unsigned int wpa_trace_test_fail_after;
+#define TEST_FAIL() testing_test_fail(NULL, false)
+#define TEST_FAIL_TAG(tag) testing_test_fail(tag, false)
+int testing_test_fail(const char *tag, bool is_alloc);
+int testing_set_fail_pattern(bool is_alloc, char *patterns);
+int testing_get_fail_pattern(bool is_alloc, char *buf, size_t buflen);
 #else
 #define TEST_FAIL() 0
+#define TEST_FAIL_TAG(tag) 0
+static inline int testing_set_fail_pattern(bool is_alloc, char *patterns)
+{
+	return -1;
+}
+
+static inline int testing_get_fail_pattern(bool is_alloc, char *buf,
+					   size_t buflen)
+{
+	return -1;
+}
 #endif
 
 #endif /* OS_H */
diff --git a/src/utils/os_unix.c b/src/utils/os_unix.c
index e721df2..e5b656a 100644
--- a/src/utils/os_unix.c
+++ b/src/utils/os_unix.c
@@ -566,39 +566,59 @@
 #ifdef WPA_TRACE
 
 #if defined(WPA_TRACE_BFD) && defined(CONFIG_TESTING_OPTIONS)
-char wpa_trace_fail_func[256] = { 0 };
-unsigned int wpa_trace_fail_after;
+struct wpa_trace_test_fail {
+	unsigned int fail_after;
+	char pattern[256];
+} wpa_trace_test_fail[5][2];
 
-static int testing_fail_alloc(void)
+int testing_test_fail(const char *tag, bool is_alloc)
 {
+	const char *ignore_list[] = {
+		"os_malloc", "os_zalloc", "os_calloc", "os_realloc",
+		"os_realloc_array", "os_strdup", "os_memdup"
+	};
 	const char *func[WPA_TRACE_LEN];
-	size_t i, res, len;
+	size_t i, j, res, len, idx;
 	char *pos, *next;
 	int match;
 
-	if (!wpa_trace_fail_after)
+	is_alloc = !!is_alloc;
+
+	for (idx = 0; idx < ARRAY_SIZE(wpa_trace_test_fail[is_alloc]); idx++) {
+		if (wpa_trace_test_fail[is_alloc][idx].fail_after != 0)
+			break;
+	}
+	if (idx >= ARRAY_SIZE(wpa_trace_test_fail[is_alloc]))
 		return 0;
 
 	res = wpa_trace_calling_func(func, WPA_TRACE_LEN);
 	i = 0;
-	if (i < res && os_strcmp(func[i], __func__) == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_malloc") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_zalloc") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_calloc") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_realloc") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_realloc_array") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_strdup") == 0)
-		i++;
-	if (i < res && os_strcmp(func[i], "os_memdup") == 0)
+
+	if (is_alloc) {
+		/* Skip our own stack frame */
 		i++;
 
-	pos = wpa_trace_fail_func;
+		/* Skip allocation helpers */
+		for (j = 0; j < ARRAY_SIZE(ignore_list) && i < res; j++) {
+			if (os_strcmp(func[i], ignore_list[j]) == 0)
+				i++;
+		}
+	} else {
+		/* Not allocation, we might have a tag, if so, replace our
+		 * own stack frame with the tag, otherwise skip it.
+		 */
+		if (tag)
+			func[0] = tag;
+		else
+			i++;
+	}
+
+	pos = wpa_trace_test_fail[is_alloc][idx].pattern;
+
+	/* The prefixes mean:
+	 * - '=': The function needs to be next in the backtrace
+	 * - '?': The function is optionally present in the backtrace
+	 */
 
 	match = 0;
 	while (i < res) {
@@ -638,82 +658,10 @@
 	if (!match)
 		return 0;
 
-	wpa_trace_fail_after--;
-	if (wpa_trace_fail_after == 0) {
-		wpa_printf(MSG_INFO, "TESTING: fail allocation at %s",
-			   wpa_trace_fail_func);
-		for (i = 0; i < res; i++)
-			wpa_printf(MSG_INFO, "backtrace[%d] = %s",
-				   (int) i, func[i]);
-		return 1;
-	}
-
-	return 0;
-}
-
-
-char wpa_trace_test_fail_func[256] = { 0 };
-unsigned int wpa_trace_test_fail_after;
-
-int testing_test_fail(void)
-{
-	const char *func[WPA_TRACE_LEN];
-	size_t i, res, len;
-	char *pos, *next;
-	int match;
-
-	if (!wpa_trace_test_fail_after)
-		return 0;
-
-	res = wpa_trace_calling_func(func, WPA_TRACE_LEN);
-	i = 0;
-	if (i < res && os_strcmp(func[i], __func__) == 0)
-		i++;
-
-	pos = wpa_trace_test_fail_func;
-
-	match = 0;
-	while (i < res) {
-		int allow_skip = 1;
-		int maybe = 0;
-
-		if (*pos == '=') {
-			allow_skip = 0;
-			pos++;
-		} else if (*pos == '?') {
-			maybe = 1;
-			pos++;
-		}
-		next = os_strchr(pos, ';');
-		if (next)
-			len = next - pos;
-		else
-			len = os_strlen(pos);
-		if (os_memcmp(pos, func[i], len) != 0) {
-			if (maybe && next) {
-				pos = next + 1;
-				continue;
-			}
-			if (allow_skip) {
-				i++;
-				continue;
-			}
-			return 0;
-		}
-		if (!next) {
-			match = 1;
-			break;
-		}
-		pos = next + 1;
-		i++;
-	}
-	if (!match)
-		return 0;
-
-	wpa_trace_test_fail_after--;
-	if (wpa_trace_test_fail_after == 0) {
+	wpa_trace_test_fail[is_alloc][idx].fail_after--;
+	if (wpa_trace_test_fail[is_alloc][idx].fail_after == 0) {
 		wpa_printf(MSG_INFO, "TESTING: fail at %s",
-			   wpa_trace_test_fail_func);
+			   wpa_trace_test_fail[is_alloc][idx].pattern);
 		for (i = 0; i < res; i++)
 			wpa_printf(MSG_INFO, "backtrace[%d] = %s",
 				   (int) i, func[i]);
@@ -723,19 +671,84 @@
 	return 0;
 }
 
-#else
 
-static inline int testing_fail_alloc(void)
+int testing_set_fail_pattern(bool is_alloc, char *patterns)
+{
+#ifdef WPA_TRACE_BFD
+	char *token, *context = NULL;
+	size_t idx;
+
+	is_alloc = !!is_alloc;
+
+	os_memset(wpa_trace_test_fail[is_alloc], 0,
+		  sizeof(wpa_trace_test_fail[is_alloc]));
+
+	idx = 0;
+	while ((token = str_token(patterns, " \n\r\t", &context)) &&
+	       idx < ARRAY_SIZE(wpa_trace_test_fail[is_alloc])) {
+		wpa_trace_test_fail[is_alloc][idx].fail_after = atoi(token);
+		token = os_strchr(token, ':');
+		if (!token) {
+			os_memset(wpa_trace_test_fail[is_alloc], 0,
+				  sizeof(wpa_trace_test_fail[is_alloc]));
+			return -1;
+		}
+
+		os_strlcpy(wpa_trace_test_fail[is_alloc][idx].pattern,
+			   token + 1,
+			   sizeof(wpa_trace_test_fail[is_alloc][0].pattern));
+		idx++;
+	}
+
+	return 0;
+#else /* WPA_TRACE_BFD */
+	return -1;
+#endif /* WPA_TRACE_BFD */
+}
+
+
+int testing_get_fail_pattern(bool is_alloc, char *buf, size_t buflen)
+{
+#ifdef WPA_TRACE_BFD
+	size_t idx, ret;
+	char *pos = buf;
+	char *end = buf + buflen;
+
+	is_alloc = !!is_alloc;
+
+	for (idx = 0; idx < ARRAY_SIZE(wpa_trace_test_fail[is_alloc]); idx++) {
+		if (wpa_trace_test_fail[is_alloc][idx].pattern[0] == '\0')
+			break;
+
+		ret = os_snprintf(pos, end - pos, "%s%u:%s",
+				  pos == buf ? "" : " ",
+				  wpa_trace_test_fail[is_alloc][idx].fail_after,
+				  wpa_trace_test_fail[is_alloc][idx].pattern);
+		if (os_snprintf_error(end - pos, ret))
+			break;
+		pos += ret;
+	}
+
+	return pos - buf;
+#else /* WPA_TRACE_BFD */
+	return -1;
+#endif /* WPA_TRACE_BFD */
+}
+
+#else /* defined(WPA_TRACE_BFD) && defined(CONFIG_TESTING_OPTIONS) */
+
+static inline int testing_test_fail(const char *tag, bool is_alloc)
 {
 	return 0;
 }
+
 #endif
 
 void * os_malloc(size_t size)
 {
 	struct os_alloc_trace *a;
 
-	if (testing_fail_alloc())
+	if (testing_test_fail(NULL, true))
 		return NULL;
 
 	a = malloc(sizeof(*a) + size);
diff --git a/src/utils/trace.c b/src/utils/trace.c
index 8f12da8..7c9a17f 100644
--- a/src/utils/trace.c
+++ b/src/utils/trace.c
@@ -197,7 +197,7 @@
 	if (abfd == NULL)
 		return;
 
-	data.pc = (bfd_hostptr_t) ((u8 *) pc - start_offset);
+	data.pc = (uintptr_t) ((u8 *) pc - start_offset);
 	data.found = FALSE;
 	bfd_map_over_sections(abfd, find_addr_sect, &data);
 
@@ -238,7 +238,7 @@
 	if (abfd == NULL)
 		return NULL;
 
-	data.pc = (bfd_hostptr_t) ((u8 *) pc - start_offset);
+	data.pc = (uintptr_t) ((u8 *) pc - start_offset);
 	data.found = FALSE;
 	bfd_map_over_sections(abfd, find_addr_sect, &data);
 
@@ -310,7 +310,7 @@
 	for (i = 0; i < btrace_num; i++) {
 		struct bfd_data data;
 
-		data.pc = (bfd_hostptr_t) ((u8 *) btrace_res[i] - start_offset);
+		data.pc = (uintptr_t) ((u8 *) btrace_res[i] - start_offset);
 		data.found = FALSE;
 		bfd_map_over_sections(abfd, find_addr_sect, &data);
 
diff --git a/src/wps/wps.c b/src/wps/wps.c
index 1fe3806..7cfebfa 100644
--- a/src/wps/wps.c
+++ b/src/wps/wps.c
@@ -336,9 +336,9 @@
 
 	pos = attr.authorized_macs;
 	for (i = 0; i < attr.authorized_macs_len / ETH_ALEN; i++) {
-		if (os_memcmp(pos, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(pos, addr))
 			return 2;
-		if (os_memcmp(pos, bcast, ETH_ALEN) == 0)
+		if (ether_addr_equal(pos, bcast))
 			return 1;
 		pos += ETH_ALEN;
 	}
diff --git a/src/wps/wps_enrollee.c b/src/wps/wps_enrollee.c
index 819cd43..af828e5 100644
--- a/src/wps/wps_enrollee.c
+++ b/src/wps/wps_enrollee.c
@@ -715,8 +715,7 @@
 	    wps_process_cred(&attr, &wps->cred))
 		return -1;
 
-	if (os_memcmp(wps->cred.mac_addr, wps->wps->dev.mac_addr, ETH_ALEN) !=
-	    0) {
+	if (!ether_addr_equal(wps->cred.mac_addr, wps->wps->dev.mac_addr)) {
 		wpa_printf(MSG_DEBUG, "WPS: MAC Address in the Credential ("
 			   MACSTR ") does not match with own address (" MACSTR
 			   ")", MAC2STR(wps->cred.mac_addr),
@@ -815,8 +814,7 @@
 	wpa_printf(MSG_INFO, "WPS: Received new AP configuration from "
 		   "Registrar");
 
-	if (os_memcmp(cred.mac_addr, wps->wps->dev.mac_addr, ETH_ALEN) !=
-	    0) {
+	if (!ether_addr_equal(cred.mac_addr, wps->wps->dev.mac_addr)) {
 		wpa_printf(MSG_DEBUG, "WPS: MAC Address in the AP Settings ("
 			   MACSTR ") does not match with own address (" MACSTR
 			   ")", MAC2STR(cred.mac_addr),
diff --git a/src/wps/wps_er.c b/src/wps/wps_er.c
index 31d2e50..dede64b 100644
--- a/src/wps/wps_er.c
+++ b/src/wps/wps_er.c
@@ -62,7 +62,7 @@
 	struct wps_er_sta *sta;
 	dl_list_for_each(sta, &ap->sta, struct wps_er_sta, list) {
 		if ((addr == NULL ||
-		     os_memcmp(sta->addr, addr, ETH_ALEN) == 0) &&
+		     ether_addr_equal(sta->addr, addr)) &&
 		    (uuid == NULL ||
 		     os_memcmp(uuid, sta->uuid, WPS_UUID_LEN) == 0))
 			return sta;
@@ -106,7 +106,7 @@
 		    (uuid == NULL ||
 		     os_memcmp(uuid, ap->uuid, WPS_UUID_LEN) == 0) &&
 		    (mac_addr == NULL ||
-		     os_memcmp(mac_addr, ap->mac_addr, ETH_ALEN) == 0))
+		     ether_addr_equal(mac_addr, ap->mac_addr)))
 			return ap;
 	}
 	return NULL;
diff --git a/src/wps/wps_registrar.c b/src/wps/wps_registrar.c
index 9587293..f49784f 100644
--- a/src/wps/wps_registrar.c
+++ b/src/wps/wps_registrar.c
@@ -238,7 +238,7 @@
 	wpa_printf(MSG_DEBUG, "WPS: Add authorized MAC " MACSTR,
 		   MAC2STR(addr));
 	for (i = 0; i < WPS_MAX_AUTHORIZED_MACS; i++)
-		if (os_memcmp(reg->authorized_macs[i], addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(reg->authorized_macs[i], addr)) {
 			wpa_printf(MSG_DEBUG, "WPS: Authorized MAC was "
 				   "already in the list");
 			return; /* already in list */
@@ -259,7 +259,7 @@
 	wpa_printf(MSG_DEBUG, "WPS: Remove authorized MAC " MACSTR,
 		   MAC2STR(addr));
 	for (i = 0; i < WPS_MAX_AUTHORIZED_MACS; i++) {
-		if (os_memcmp(reg->authorized_macs, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(reg->authorized_macs[i], addr))
 			break;
 	}
 	if (i == WPS_MAX_AUTHORIZED_MACS) {
@@ -296,7 +296,7 @@
 	struct wps_registrar_device *dev;
 
 	for (dev = reg->devices; dev; dev = dev->next) {
-		if (os_memcmp(dev->dev.mac_addr, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(dev->dev.mac_addr, addr))
 			return dev;
 	}
 	return NULL;
@@ -353,7 +353,7 @@
 
 	pbc = reg->pbc_sessions;
 	while (pbc) {
-		if (os_memcmp(pbc->addr, addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(pbc->addr, addr) &&
 		    os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0) {
 			if (prev)
 				prev->next = pbc->next;
@@ -405,8 +405,7 @@
 	while (pbc) {
 		if (os_memcmp(pbc->uuid_e, uuid_e, WPS_UUID_LEN) == 0 ||
 		    (p2p_dev_addr && !is_zero_ether_addr(reg->p2p_dev_addr) &&
-		     os_memcmp(reg->p2p_dev_addr, p2p_dev_addr, ETH_ALEN) ==
-		     0)) {
+		     ether_addr_equal(reg->p2p_dev_addr, p2p_dev_addr))) {
 			if (prev)
 				prev->next = pbc->next;
 			else
@@ -2611,7 +2610,7 @@
 	if (is_zero_ether_addr(reg->p2p_dev_addr))
 		return 1; /* no filtering in use */
 
-	if (os_memcmp(reg->p2p_dev_addr, wps->p2p_dev_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(reg->p2p_dev_addr, wps->p2p_dev_addr)) {
 		wpa_printf(MSG_DEBUG, "WPS: No match on P2P Device Address "
 			   "filtering for PBC: expected " MACSTR " was "
 			   MACSTR " - indicate PBC session overlap",
@@ -2632,7 +2631,7 @@
 	if (is_zero_ether_addr(reg->p2p_dev_addr))
 		return 0; /* no specific Enrollee selected */
 
-	if (os_memcmp(reg->p2p_dev_addr, wps->p2p_dev_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(reg->p2p_dev_addr, wps->p2p_dev_addr)) {
 		wpa_printf(MSG_DEBUG, "WPS: Skip PBC overlap due to selected "
 			   "Enrollee match");
 		return 1;
diff --git a/wpa_supplicant/Android.mk b/wpa_supplicant/Android.mk
index 98152be..bdb19f9 100644
--- a/wpa_supplicant/Android.mk
+++ b/wpa_supplicant/Android.mk
@@ -139,11 +139,7 @@
 OBJS += src/utils/bitfield.c
 OBJS += src/utils/ip_addr.c
 OBJS += src/utils/crc32.c
-OBJS += wmm_ac.c
-OBJS += op_classes.c
-OBJS += rrm.c
 OBJS += twt.c
-OBJS += robust_av.c
 OBJS_p = wpa_passphrase.c
 OBJS_p += src/utils/common.c
 OBJS_p += src/utils/wpa_debug.c
@@ -324,6 +320,12 @@
 endif
 endif
 
+ifdef CONFIG_NAN_USD
+OBJS += src/common/nan_de.c
+OBJS += nan_usd.c
+L_CFLAGS += -DCONFIG_NAN_USD
+endif
+
 ifdef CONFIG_OWE
 L_CFLAGS += -DCONFIG_OWE
 NEED_ECC=y
@@ -352,6 +354,10 @@
 CONFIG_WNM=y
 endif
 
+ifdef CONFIG_BGSCAN_SIMPLE
+CONFIG_WNM=y
+endif
+
 ifdef CONFIG_WNM
 L_CFLAGS += -DCONFIG_WNM
 OBJS += wnm_sta.c
@@ -466,6 +472,28 @@
 L_CFLAGS += -DCONFIG_NO_TKIP
 endif
 
+ifdef CONFIG_NO_RRM
+L_CFLAGS += -DCONFIG_NO_RRM
+else
+OBJS += rrm.c
+ifdef CONFIG_AP
+OBJS += src/ap/rrm.c
+endif
+OBJS += op_classes.c
+endif
+
+ifdef CONFIG_NO_WMM_AC
+L_CFLAGS += -DCONFIG_NO_WMM_AC
+else
+OBJS += wmm_ac.c
+endif
+
+ifdef CONFIG_NO_ROBUST_AV
+L_CFLAGS += -DCONFIG_NO_ROBUST_AV
+else
+OBJS += robust_av.c
+endif
+
 
 include $(LOCAL_PATH)/src/drivers/drivers.mk
 
@@ -949,7 +977,6 @@
 OBJS += src/ap/bss_load.c
 OBJS += src/ap/eap_user_db.c
 OBJS += src/ap/neighbor_db.c
-OBJS += src/ap/rrm.c
 OBJS += src/ap/ieee802_11_ht.c
 ifdef CONFIG_IEEE80211AC
 OBJS += src/ap/ieee802_11_vht.c
@@ -1010,6 +1037,9 @@
 OBJS += src/ap/gas_query_ap.c
 NEED_AP_GAS_SERV=y
 endif
+ifdef CONFIG_NAN_USD
+OBJS += src/ap/nan_usd_ap.c
+endif
 ifdef CONFIG_INTERWORKING
 NEED_AP_GAS_SERV=y
 endif
@@ -1028,6 +1058,7 @@
 
 ifdef CONFIG_TESTING_OPTIONS
 L_CFLAGS += -DCONFIG_TESTING_OPTIONS
+NEED_AES_WRAP=y
 endif
 
 ifdef NEED_RSN_AUTHENTICATOR
diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile
index 8c417b8..93fc338 100644
--- a/wpa_supplicant/Makefile
+++ b/wpa_supplicant/Makefile
@@ -64,6 +64,7 @@
 CFLAGS += -DCONFIG_TESTING_OPTIONS
 CONFIG_WPS_TESTING=y
 CONFIG_TDLS_TESTING=y
+NEED_AES_WRAP=y
 endif
 
 mkconfig:
@@ -111,10 +112,7 @@
 OBJS += ../src/utils/bitfield.o
 OBJS += ../src/utils/ip_addr.o
 OBJS += ../src/utils/crc32.o
-OBJS += op_classes.o
-OBJS += rrm.o
 OBJS += twt.o
-OBJS += robust_av.o
 OBJS_p = wpa_passphrase.o
 OBJS_p += ../src/utils/common.o
 OBJS_p += ../src/utils/wpa_debug.o
@@ -123,7 +121,6 @@
 OBJS_c += ../src/utils/wpa_debug.o
 OBJS_c += ../src/utils/common.o
 OBJS_c += ../src/common/cli.o
-OBJS += wmm_ac.o
 
 ifndef CONFIG_OS
 ifdef CONFIG_NATIVE_WINDOWS
@@ -192,7 +189,7 @@
 endif
 
 ifdef CONFIG_CODE_COVERAGE
-CFLAGS += -O0 -fprofile-arcs -ftest-coverage
+CFLAGS += -O0 -fprofile-arcs -ftest-coverage -U_FORTIFY_SOURCE
 LIBS += -lgcov
 LIBS_c += -lgcov
 LIBS_p += -lgcov
@@ -318,6 +315,12 @@
 endif
 endif
 
+ifdef CONFIG_NAN_USD
+OBJS += ../src/common/nan_de.o
+OBJS += nan_usd.o
+CFLAGS += -DCONFIG_NAN_USD
+endif
+
 ifdef CONFIG_OWE
 CFLAGS += -DCONFIG_OWE
 NEED_ECC=y
@@ -346,6 +349,10 @@
 CONFIG_WNM=y
 endif
 
+ifdef CONFIG_BGSCAN_SIMPLE
+CONFIG_WNM=y
+endif
+
 ifdef CONFIG_WNM
 CFLAGS += -DCONFIG_WNM
 OBJS += wnm_sta.o
@@ -477,6 +484,28 @@
 CFLAGS += -DCONFIG_NO_LOAD_DYNAMIC_EAP
 endif
 
+ifdef CONFIG_NO_RRM
+CFLAGS += -DCONFIG_NO_RRM
+else
+OBJS += rrm.o
+ifdef CONFIG_AP
+OBJS += ../src/ap/rrm.o
+endif
+OBJS += op_classes.o
+endif
+
+ifdef CONFIG_NO_WMM_AC
+CFLAGS += -DCONFIG_NO_WMM_AC
+else
+OBJS += wmm_ac.o
+endif
+
+ifdef CONFIG_NO_ROBUST_AV
+CFLAGS += -DCONFIG_NO_ROBUST_AV
+else
+OBJS += robust_av.o
+endif
+
 include ../src/drivers/drivers.mak
 ifdef CONFIG_AP
 OBJS_d += $(DRV_BOTH_OBJS)
@@ -983,7 +1012,6 @@
 OBJS += ../src/ap/bss_load.o
 OBJS += ../src/ap/eap_user_db.o
 OBJS += ../src/ap/neighbor_db.o
-OBJS += ../src/ap/rrm.o
 OBJS += ../src/ap/ieee802_11_ht.o
 ifdef CONFIG_IEEE80211AC
 OBJS += ../src/ap/ieee802_11_vht.o
@@ -1044,6 +1072,9 @@
 OBJS += ../src/ap/gas_query_ap.o
 NEED_AP_GAS_SERV=y
 endif
+ifdef CONFIG_NAN_USD
+OBJS += ../src/ap/nan_usd_ap.o
+endif
 ifdef CONFIG_INTERWORKING
 NEED_AP_GAS_SERV=y
 endif
@@ -1058,6 +1089,7 @@
 ifdef CONFIG_MBO
 OBJS += mbo.o
 CFLAGS += -DCONFIG_MBO
+NEED_GAS=y
 endif
 
 ifdef NEED_RSN_AUTHENTICATOR
@@ -1076,17 +1108,20 @@
 
 ifdef CONFIG_PCSC
 # PC/SC interface for smartcards (USIM, GSM SIM)
-CFLAGS += -DPCSC_FUNCS -I/usr/include/PCSC
+CFLAGS += -DPCSC_FUNCS
 OBJS += ../src/utils/pcsc_funcs.o
 ifdef CONFIG_NATIVE_WINDOWS
 #Once MinGW gets support for WinScard, -lwinscard could be used instead of the
 #dynamic symbol loading that is now used in pcsc_funcs.c
 #LIBS += -lwinscard
+CFLAGS += -I/usr/include/PCSC
 else
 ifdef CONFIG_OSX
 LIBS += -framework PCSC
+CFLAGS += -I/usr/include/PCSC
 else
 LIBS += $(shell $(PKG_CONFIG) --libs libpcsclite)
+CFLAGS += $(shell $(PKG_CONFIG) --cflags libpcsclite)
 endif
 endif
 endif
diff --git a/wpa_supplicant/README-NAN-USD b/wpa_supplicant/README-NAN-USD
new file mode 100644
index 0000000..5dfe6ee
--- /dev/null
+++ b/wpa_supplicant/README-NAN-USD
@@ -0,0 +1,147 @@
+Wi-Fi Aware unsynchronized service discovery (NAN USD)
+======================================================
+
+This document descibes how the unsynchronized service discovery defined
+in the Wi-Fi Aware specification v4.0 can be used with wpa_spplicant.
+
+More information about Wi-Fi Aware is available from this Wi-Fi
+Alliance web page:
+https://www.wi-fi.org/discover-wi-fi/wi-fi-aware
+
+Build config setup
+------------------
+
+The following parameters must be included in the config file used to
+compile hostapd and wpa_supplicant.
+
+wpa_supplicant build config
+---------------------------
+
+Enable NAN USD in wpa_supplicant build config file
+
+CONFIG_NAN_USD=y
+
+Control interface commands and events
+-------------------------------------
+
+Following control interface commands can be used:
+
+NAN_PUBLISH service_name=<name> [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [freq_list=<comma separate list of MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>] [solicited=0] [unsolicited=0] [fsd=0]
+
+If ttl=0 or the parameter is not included, only one Publish message is
+transmitted.
+
+If freq is not included, the default frequency 2437 MHz (channel 6 on
+the 2.4 GHz band) is used.
+
+If freq_list is included, publisher iterates over all the listed
+channels. A special freq_list=all value can be used to generate the
+channel list automatically based on the list of allowed 2.4 and 5 GHz
+channels.
+
+srv_proto_type values are defined in the Service Protocol Types table in
+the Wi-Fi Aware specification.
+
+This command returns the assigned publish_id value or FAIL on failure.
+
+This command maps to the Publish() method in the NAN Discovery Engine.
+
+NAN_CANCEL_PUBLISH publish_id=<id from NAN_PUBLISH>
+
+This command maps to the CancelPublish() method in the NAN Discovery
+Engine.
+
+NAN_UPDATE_PUBLISH publish_id=<id from NAN_PUBLISH> [ssi=<service specific information (hexdump)>]
+
+This command maps to the UpdatePublish() method in the NAN Discovery
+Engine.
+
+NAN_SUBSCRIBE service_name=<name> [active=1] [ttl=<time-to-live-in-sec>] [freq=<in MHz>] [srv_proto_type=<type>] [ssi=<service specific information (hexdump)>]
+
+If ttl=0 or the parameter is not included, operation is terminated once
+the first matching publisher is found.
+
+If freq is not included, the default frequency 2437 MHz (channel 6 on
+the 2.4 GHz band) is used.
+
+srv_proto_type values are defined in the Service Protocol Types table in
+the Wi-Fi Aware specification.
+
+This command returns the assigned subscribe_id value or FAIL on failure.
+
+This command maps to the Subscribe() method in the NAN Discovery Engine.
+
+NAN_CANCEL_SUBSCRIBE subscribe_id=<id from NAN_SUBSCRIBE>
+
+This command maps to the CancelSubscribe() method in the NAN Discovery Engine.
+
+NAN_TRANSMIT handle=<id from NAN_PUBLISH or NAN_SUBSCRIBE> req_instance=<peer's id> address=<peer's MAC address> [ssi=<service specific information (hexdump)>]
+
+This command maps to the Transmit() method in the NAN Discovery Engine.
+
+Following control interface events are used:
+
+NAN-DISCOVERY-RESULT subscribe_id=<own id> publish_id=<peer's id> address=<peer MAC address> fsd=<0/1> fsd_gas=<0/1> srv_proto_type=<type> ssi=<service specific information (hexdump)>
+
+This event maps to the DiscoveryResult() event in the NAN Discovery
+Engine.
+
+NAN-REPLIED publish_id=<own id> address=<peer MAC address> subscribe_id=<peer id> srv_proto_type=<ype> ssi=<service specific information (hexdump)>
+
+This event maps to the Replied() event in the NAN Discovery Engine.
+
+NAN-PUBLISH-TERMINATED publish_id=<own id> reason=<timeout/user-request/failure>
+
+This event maps to the PublishTerminated() event in the NAN Discovery
+Engine.
+
+NAN-SUBSCRIBE-TERMINATED subscribe_id=<own id> reason=<timeout/user-request/failure>
+
+This event maps to the SubscribeTerminate() event in the NAN Discovery
+Engine.
+
+NAN-RECEIVE id=<own id> peer_instance_id=<peer id> address=<peer MAC adress> ssi=<service specific information (hexdump)>
+
+This event maps to the Receive() event in the NAN Discovery Engine.
+
+
+Example operation
+-----------------
+
+Start Subscribe and Publish functions:
+
+dev0: NAN_SUBSCRIBE service_name=_test srv_proto_type=3 ssi=1122334455
+--> returns 7
+
+dev1: NAN_PUBLISH service_name=_test srv_proto_type=3 ssi=6677
+--> returns 5
+
+Subscriber notification of a discovery:
+
+event on dev0: <3>NAN-DISCOVERY-RESULT subscribe_id=7 publish_id=5 address=02:00:00:00:01:00 fsd=1 fsd_gas=0 srv_proto_type=3 ssi=6677
+
+Publisher notification of a Follow-up message with no ssi (to enter
+paused state to continue exchange with the subscriber):
+
+event on dev1: <3>NAN-RECEIVE id=5 peer_instance_id=7 address=02:00:00:00:00:00 ssi=
+
+Subscriber sending a Follow-up message:
+
+dev0: NAN_TRANSMIT handle=7 req_instance_id=5 address=02:00:00:00:01:00 ssi=8899
+
+Publisher receiving the Follow-up message:
+
+event on dev1: <3>NAN-RECEIVE id=5 peer_instance_id=7 address=02:00:00:00:00:00 ssi=8899
+
+Publisher sending a Follow-up message:
+
+dev1: NAN_TRANSMIT handle=5 req_instance_id=7 address=02:00:00:00:00:00 ssi=aabbccdd
+
+Subscriber receiving the Follow-up message:
+
+event on dev0: <3>NAN-RECEIVE id=7 peer_instance_id=5 address=02:00:00:00:01:00 ssi=aabbccdd
+
+Stop Subscribe and Publish functions:
+
+dev0: NAN_CANCEL_SUBSCRIBE subscribe_id=7
+dev1: NAN_CANCEL_PUBLIST publish_id=5
diff --git a/wpa_supplicant/android.config b/wpa_supplicant/android.config
index 4cc3808..42e2237 100644
--- a/wpa_supplicant/android.config
+++ b/wpa_supplicant/android.config
@@ -564,4 +564,19 @@
 # WPA3-Personal (SAE) PK (Public Key) mode
 CONFIG_SAE_PK=y
 
+# Disable support for Radio Measurement (IEEE 802.11k) and supported operating
+# class indication. Removing these is not recommended since they can help the
+# AP manage the network and STA steering.
+#CONFIG_NO_RRM=y
+
+# Disable support for Robust AV streaming for consumer and enterprise Wi-Fi
+# applications; IEEE Std 802.11-2020, 4.3.24; SCS, MSCS, QoS Management
+#CONFIG_NO_ROBUST_AV=y
+
+# Disable support for WMM admission control
+#CONFIG_NO_WMM_AC=y
+
+# Wi-Fi Aware unsynchronized service discovery (NAN USD)
+#CONFIG_NAN_USD=y
+
 include $(wildcard $(LOCAL_PATH)/android_config_*.inc)
diff --git a/wpa_supplicant/ap.c b/wpa_supplicant/ap.c
index 1b94fe5..000914a 100644
--- a/wpa_supplicant/ap.c
+++ b/wpa_supplicant/ap.c
@@ -1848,6 +1848,8 @@
 	if (ret)
 		return ret;
 
+	settings.link_id = -1;
+
 	return ap_switch_channel(wpa_s, &settings);
 }
 #endif /* CONFIG_CTRL_IFACE */
diff --git a/wpa_supplicant/bgscan.h b/wpa_supplicant/bgscan.h
index 3df1550..4388b4e 100644
--- a/wpa_supplicant/bgscan.h
+++ b/wpa_supplicant/bgscan.h
@@ -51,7 +51,7 @@
 #else /* CONFIG_BGSCAN */
 
 static inline int bgscan_init(struct wpa_supplicant *wpa_s,
-			      struct wpa_ssid *ssid, const char name)
+			      struct wpa_ssid *ssid, const char *name)
 {
 	return 0;
 }
diff --git a/wpa_supplicant/bgscan_learn.c b/wpa_supplicant/bgscan_learn.c
index 9830c4a..cab4ae2 100644
--- a/wpa_supplicant/bgscan_learn.c
+++ b/wpa_supplicant/bgscan_learn.c
@@ -57,7 +57,7 @@
 		return 0;
 
 	for (i = 0; i < array_len; i++) {
-		if (os_memcmp(array + i * ETH_ALEN, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(array + i * ETH_ALEN, bssid))
 			return 1;
 	}
 
@@ -70,7 +70,7 @@
 {
 	u8 *n;
 
-	if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
+	if (ether_addr_equal(bss->bssid, bssid))
 		return;
 	if (bssid_in_array(bss->neigh, bss->num_neigh, bssid))
 		return;
@@ -91,7 +91,7 @@
 	struct bgscan_learn_bss *bss;
 
 	dl_list_for_each(bss, &data->bss, struct bgscan_learn_bss, list) {
-		if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(bss->bssid, bssid))
 			return bss;
 	}
 	return NULL;
diff --git a/wpa_supplicant/bgscan_simple.c b/wpa_supplicant/bgscan_simple.c
index f398b85..a90cf86 100644
--- a/wpa_supplicant/bgscan_simple.c
+++ b/wpa_supplicant/bgscan_simple.c
@@ -15,11 +15,16 @@
 #include "wpa_supplicant_i.h"
 #include "driver_i.h"
 #include "scan.h"
+#include "config.h"
+#include "wnm_sta.h"
+#include "bss.h"
 #include "bgscan.h"
 
 struct bgscan_simple_data {
 	struct wpa_supplicant *wpa_s;
 	const struct wpa_ssid *ssid;
+	unsigned int use_btm_query;
+	unsigned int scan_action_count;
 	int scan_interval;
 	int signal_threshold;
 	int short_scan_count; /* counter for scans using short scan interval */
@@ -30,12 +35,54 @@
 };
 
 
+static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx);
+
+
+static bool bgscan_simple_btm_query(struct wpa_supplicant *wpa_s,
+				    struct bgscan_simple_data *data)
+{
+	unsigned int mod;
+
+	if (!data->use_btm_query || wpa_s->conf->disable_btm ||
+	    !wpa_s->current_bss ||
+	    !wpa_bss_ext_capab(wpa_s->current_bss,
+			       WLAN_EXT_CAPAB_BSS_TRANSITION))
+		return false;
+
+	/* Try BTM x times, scan on x + 1 */
+	data->scan_action_count++;
+	mod = data->scan_action_count % (data->use_btm_query + 1);
+	if (mod >= data->use_btm_query)
+		return false;
+
+	wpa_printf(MSG_DEBUG,
+		   "bgscan simple: Send BSS transition management query %d/%d",
+		   mod, data->use_btm_query);
+	if (wnm_send_bss_transition_mgmt_query(
+		    wpa_s, WNM_TRANSITION_REASON_BETTER_AP_FOUND, NULL, 0)) {
+		wpa_printf(MSG_DEBUG,
+			   "bgscan simple: Failed to send BSS transition management query");
+		/* Fall through and do regular scan */
+		return false;
+	}
+
+	/* Start a new timeout for the next one. We don't have scan callback to
+	 * otherwise trigger future progress when using BTM path. */
+	eloop_register_timeout(data->scan_interval, 0,
+			       bgscan_simple_timeout, data, NULL);
+	return true;
+}
+
+
 static void bgscan_simple_timeout(void *eloop_ctx, void *timeout_ctx)
 {
 	struct bgscan_simple_data *data = eloop_ctx;
 	struct wpa_supplicant *wpa_s = data->wpa_s;
 	struct wpa_driver_scan_params params;
 
+	if (bgscan_simple_btm_query(wpa_s, data))
+		goto scan_ok;
+
 	os_memset(&params, 0, sizeof(params));
 	params.num_ssids = 1;
 	params.ssids[0].ssid = data->ssid->ssid;
@@ -54,6 +101,7 @@
 		eloop_register_timeout(data->scan_interval, 0,
 				       bgscan_simple_timeout, data, NULL);
 	} else {
+	scan_ok:
 		if (data->scan_interval == data->short_interval) {
 			data->short_scan_count++;
 			if (data->short_scan_count >= data->max_short_scans) {
@@ -80,6 +128,8 @@
 {
 	const char *pos;
 
+	data->use_btm_query = 0;
+
 	data->short_interval = atoi(params);
 
 	pos = os_strchr(params, ':');
@@ -95,6 +145,11 @@
 	}
 	pos++;
 	data->long_interval = atoi(pos);
+	pos = os_strchr(pos, ':');
+	if (pos) {
+		pos++;
+		data->use_btm_query = atoi(pos);
+	}
 
 	return 0;
 }
@@ -134,6 +189,7 @@
 	data->scan_interval = data->short_interval;
 	data->max_short_scans = data->long_interval / data->short_interval + 1;
 	if (data->signal_threshold) {
+		wpa_s->signal_threshold = data->signal_threshold;
 		/* Poll for signal info to set initial scan interval */
 		struct wpa_signal_info siginfo;
 		if (wpa_drv_signal_poll(wpa_s, &siginfo) == 0 &&
@@ -161,8 +217,10 @@
 {
 	struct bgscan_simple_data *data = priv;
 	eloop_cancel_timeout(bgscan_simple_timeout, data, NULL);
-	if (data->signal_threshold)
+	if (data->signal_threshold) {
+		data->wpa_s->signal_threshold = 0;
 		wpa_drv_signal_monitor(data->wpa_s, 0, 0);
+	}
 	os_free(data);
 }
 
diff --git a/wpa_supplicant/bss.c b/wpa_supplicant/bss.c
index c1be660..22b694c 100644
--- a/wpa_supplicant/bss.c
+++ b/wpa_supplicant/bss.c
@@ -13,10 +13,12 @@
 #include "common/ieee802_11_defs.h"
 #include "drivers/driver.h"
 #include "eap_peer/eap.h"
+#include "rsn_supp/wpa.h"
 #include "wpa_supplicant_i.h"
 #include "config.h"
 #include "notify.h"
 #include "scan.h"
+#include "bssid_ignore.h"
 #include "bss.h"
 
 static void wpa_bss_set_hessid(struct wpa_bss *bss)
@@ -263,7 +265,7 @@
 	if (bssid && !wpa_supplicant_filter_bssid_match(wpa_s, bssid))
 		return NULL;
 	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
-		if ((!bssid || os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0) &&
+		if ((!bssid || ether_addr_equal(bss->bssid, bssid)) &&
 		    bss->ssid_len == ssid_len &&
 		    os_memcmp(bss->ssid, ssid, ssid_len) == 0)
 			return bss;
@@ -358,12 +360,11 @@
 #ifdef CONFIG_P2P
 	u8 addr[ETH_ALEN];
 
-	if (os_memcmp(bss->bssid, wpa_s->pending_join_iface_addr,
-		      ETH_ALEN) == 0)
+	if (ether_addr_equal(bss->bssid, wpa_s->pending_join_iface_addr))
 		return true;
 	if (!is_zero_ether_addr(wpa_s->pending_join_dev_addr) &&
 	    p2p_parse_dev_addr(wpa_bss_ie_ptr(bss), bss->ie_len, addr) == 0 &&
-	    os_memcmp(addr, wpa_s->pending_join_dev_addr, ETH_ALEN) == 0)
+	    ether_addr_equal(addr, wpa_s->pending_join_dev_addr))
 		return true;
 #endif /* CONFIG_P2P */
 	return false;
@@ -406,8 +407,8 @@
 		return 0; /* SSID has changed */
 
 	if (!is_zero_ether_addr(bss->bssid) &&
-	    (os_memcmp(bss->bssid, wpa_s->bssid, ETH_ALEN) == 0 ||
-	     os_memcmp(bss->bssid, wpa_s->pending_bssid, ETH_ALEN) == 0))
+	    (ether_addr_equal(bss->bssid, wpa_s->bssid) ||
+	     ether_addr_equal(bss->bssid, wpa_s->pending_bssid)))
 		return 1;
 
 	if (!wpa_s->valid_links)
@@ -417,7 +418,7 @@
 		if (!(wpa_s->valid_links & BIT(i)))
 			continue;
 
-		if (os_memcmp(bss->bssid, wpa_s->links[i].bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(bss->bssid, wpa_s->links[i].bssid))
 			return 1;
 	}
 
@@ -717,7 +718,8 @@
 	dl_list_del(&bss->list);
 #ifdef CONFIG_P2P
 	if (wpa_bss_get_vendor_ie(bss, P2P_IE_VENDOR_TYPE) &&
-	    !wpa_scan_get_vendor_ie(res, P2P_IE_VENDOR_TYPE)) {
+	    !wpa_scan_get_vendor_ie(res, P2P_IE_VENDOR_TYPE) &&
+	    !(changes & WPA_BSS_FREQ_CHANGED_FLAG)) {
 		/*
 		 * This can happen when non-P2P station interface runs a scan
 		 * without P2P IE in the Probe Request frame. P2P GO would reply
@@ -1101,7 +1103,7 @@
 	if (!wpa_supplicant_filter_bssid_match(wpa_s, bssid))
 		return NULL;
 	dl_list_for_each_reverse(bss, &wpa_s->bss, struct wpa_bss, list) {
-		if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(bss->bssid, bssid))
 			return bss;
 	}
 	return NULL;
@@ -1126,7 +1128,7 @@
 	if (!wpa_supplicant_filter_bssid_match(wpa_s, bssid))
 		return NULL;
 	dl_list_for_each_reverse(bss, &wpa_s->bss, struct wpa_bss, list) {
-		if (os_memcmp(bss->bssid, bssid, ETH_ALEN) != 0)
+		if (!ether_addr_equal(bss->bssid, bssid))
 			continue;
 		if (found == NULL ||
 		    os_reltime_before(&found->last_update, &bss->last_update))
@@ -1155,7 +1157,7 @@
 		u8 addr[ETH_ALEN];
 		if (p2p_parse_dev_addr(wpa_bss_ie_ptr(bss), bss->ie_len,
 				       addr) != 0 ||
-		    os_memcmp(addr, dev_addr, ETH_ALEN) != 0)
+		    !ether_addr_equal(addr, dev_addr))
 			continue;
 		if (!found ||
 		    os_reltime_before(&found->last_update, &bss->last_update))
@@ -1222,22 +1224,6 @@
 
 
 /**
- * wpa_bss_get_ie_nth - Fetch a specified information element from a BSS entry
- * @bss: BSS table entry
- * @ie: Information element identitifier (WLAN_EID_*)
- * @nth: Return the nth element of the requested type (2 returns the second)
- * Returns: Pointer to the information element (id field) or %NULL if not found
- *
- * This function returns the nth matching information element in the BSS
- * entry.
- */
-const u8 * wpa_bss_get_ie_nth(const struct wpa_bss *bss, u8 ie, int nth)
-{
-	return get_ie_nth(wpa_bss_ie_ptr(bss), bss->ie_len, ie, nth);
-}
-
-
-/**
  * wpa_bss_get_ie_ext - Fetch a specified extended IE from a BSS entry
  * @bss: BSS table entry
  * @ext: Information element extension identifier (WLAN_EID_EXT_*)
@@ -1496,29 +1482,12 @@
 }
 
 
-/**
- * wpa_bss_defrag_mle - Get a buffer holding a de-fragmented ML element
- * @bss: BSS table entry
- * @type: ML control type
- */
-struct wpabuf * wpa_bss_defrag_mle(const struct wpa_bss *bss, u8 type)
-{
-	struct ieee802_11_elems elems;
-	const u8 *pos = wpa_bss_ie_ptr(bss);
-	size_t len = bss->ie_len;
-
-	if (ieee802_11_parse_elems(pos, len, &elems, 1) == ParseFailed)
-		return NULL;
-
-	return ieee802_11_defrag_mle(&elems, type);
-}
-
-
 static void
 wpa_bss_parse_ml_rnr_ap_info(struct wpa_supplicant *wpa_s,
 			     struct wpa_bss *bss, u8 mbssid_idx,
 			     const struct ieee80211_neighbor_ap_info *ap_info,
-			     size_t len, u16 *seen, u16 *missing)
+			     size_t len, u16 *seen, u16 *missing,
+			     struct wpa_ssid *ssid)
 {
 	const u8 *pos, *end;
 	const u8 *mld_params;
@@ -1542,12 +1511,15 @@
 	pos += sizeof(*ap_info);
 
 	for (i = 0; i < count; i++) {
+		u8 bss_params;
+
 		if (bss->n_mld_links >= MAX_NUM_MLD_LINKS)
 			return;
 
 		if (end - pos < ap_info->tbtt_info_len)
 			break;
 
+		bss_params = pos[1 + ETH_ALEN + 4];
 		mld_params = pos + mld_params_offset;
 
 		link_id = *(mld_params + 1) & EHT_ML_LINK_ID_MSK;
@@ -1557,23 +1529,30 @@
 				   "MLD: Reported link not part of MLD");
 		} else if (!(BIT(link_id) & *seen)) {
 			struct wpa_bss *neigh_bss =
-				wpa_bss_get_bssid(wpa_s, ap_info->data + 1);
+				wpa_bss_get_bssid(wpa_s, pos + 1);
 
 			*seen |= BIT(link_id);
 			wpa_printf(MSG_DEBUG, "MLD: mld ID=%u, link ID=%u",
 				   *mld_params, link_id);
 
-			if (neigh_bss) {
+			if (!neigh_bss) {
+				*missing |= BIT(link_id);
+			} else if ((!ssid ||
+				    (bss_params & (RNR_BSS_PARAM_SAME_SSID |
+						   RNR_BSS_PARAM_CO_LOCATED)) ||
+				    wpa_scan_res_match(wpa_s, 0, neigh_bss,
+						       ssid, 1, 0)) &&
+				   !wpa_bssid_ignore_is_listed(
+					   wpa_s, neigh_bss->bssid)) {
 				struct mld_link *l;
 
 				l = &bss->mld_links[bss->n_mld_links];
 				l->link_id = link_id;
-				os_memcpy(l->bssid, ap_info->data + 1,
-					  ETH_ALEN);
+				os_memcpy(l->bssid, pos + 1, ETH_ALEN);
 				l->freq = neigh_bss->freq;
+				l->disabled = mld_params[2] &
+					RNR_TBTT_INFO_MLD_PARAM2_LINK_DISABLED;
 				bss->n_mld_links++;
-			} else {
-				*missing |= BIT(link_id);
 			}
 		}
 
@@ -1590,6 +1569,8 @@
  * @link_info: Array to store link information (or %NULL),
  *   should be initialized and #MAX_NUM_MLD_LINKS elements long
  * @missing_links: Result bitmask of links that were not discovered (or %NULL)
+ * @ssid: Target SSID (or %NULL)
+ * @ap_mld_id: On return would hold the corresponding AP MLD ID (or %NULL)
  * Returns: 0 on success or -1 for non-MLD or parsing failures
  *
  * Parses the Basic Multi-Link element of the BSS into @link_info using the scan
@@ -1600,13 +1581,15 @@
 int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
 				   u8 *ap_mld_addr,
-				   u16 *missing_links)
+				   u16 *missing_links,
+				   struct wpa_ssid *ssid,
+				   u8 *ap_mld_id)
 {
 	struct ieee802_11_elems elems;
 	struct wpabuf *mlbuf;
 	const struct element *elem;
 	u8 mbssid_idx = 0;
-	u8 ml_ie_len;
+	size_t ml_ie_len;
 	const struct ieee80211_eht_ml *eht_ml;
 	const struct eht_ml_basic_common_info *ml_basic_common_info;
 	u8 i, link_id;
@@ -1633,7 +1616,7 @@
 		return ret;
 	}
 
-	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_BASIC);
+	mlbuf = ieee802_11_defrag(elems.basic_mle, elems.basic_mle_len, true);
 	if (!mlbuf) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No Multi-Link element");
 		return ret;
@@ -1641,6 +1624,32 @@
 
 	ml_ie_len = wpabuf_len(mlbuf);
 
+	if (ssid) {
+		struct wpa_ie_data ie;
+
+		if (!elems.rsn_ie ||
+		    wpa_parse_wpa_ie(elems.rsn_ie - 2, 2 + elems.rsn_ie_len,
+				     &ie)) {
+			wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No RSN element");
+			goto out;
+		}
+
+		if (!(ie.capabilities & WPA_CAPABILITY_MFPC) ||
+		    wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: No management frame protection");
+			goto out;
+		}
+
+		ie.key_mgmt &= ~(WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_FT_PSK |
+				 WPA_KEY_MGMT_PSK_SHA256);
+		if (!(ie.key_mgmt & ssid->key_mgmt)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"MLD: No valid key management");
+			goto out;
+		}
+	}
+
 	/*
 	 * for ext ID + 2 control + common info len + MLD address +
 	 * link info
@@ -1720,7 +1729,7 @@
 
 			wpa_bss_parse_ml_rnr_ap_info(wpa_s, bss, mbssid_idx,
 						     ap_info, len, &seen,
-						     &missing);
+						     &missing, ssid);
 
 			pos += ap_info_len;
 			len -= ap_info_len;
@@ -1739,6 +1748,9 @@
 	if (missing_links)
 		*missing_links = missing;
 
+	if (ap_mld_id)
+		*ap_mld_id = mbssid_idx;
+
 	ret = 0;
 out:
 	wpabuf_free(mlbuf);
@@ -1769,7 +1781,7 @@
 	if (!elems.reconf_mle || !elems.reconf_mle_len)
 		return 0;
 
-	mlbuf = ieee802_11_defrag_mle(&elems, MULTI_LINK_CONTROL_TYPE_RECONF);
+	mlbuf = ieee802_11_defrag(elems.reconf_mle, elems.reconf_mle_len, true);
 	if (!mlbuf)
 		return 0;
 
diff --git a/wpa_supplicant/bss.h b/wpa_supplicant/bss.h
index 042b5d2..c06c20a 100644
--- a/wpa_supplicant/bss.h
+++ b/wpa_supplicant/bss.h
@@ -133,6 +133,9 @@
 		u8 link_id;
 		u8 bssid[ETH_ALEN];
 		int freq;
+
+		/* Whether the link is valid but currently disabled */
+		bool disabled;
 	} mld_links[MAX_NUM_MLD_LINKS];
 
 	/* followed by ie_len octets of IEs */
@@ -171,7 +174,6 @@
 struct wpa_bss * wpa_bss_get_id_range(struct wpa_supplicant *wpa_s,
 				      unsigned int idf, unsigned int idl);
 const u8 * wpa_bss_get_ie(const struct wpa_bss *bss, u8 ie);
-const u8 * wpa_bss_get_ie_nth(const struct wpa_bss *bss, u8 ie, int nth);
 const u8 * wpa_bss_get_ie_ext(const struct wpa_bss *bss, u8 ext);
 const u8 * wpa_bss_get_vendor_ie(const struct wpa_bss *bss, u32 vendor_type);
 const u8 * wpa_bss_get_vendor_ie_beacon(const struct wpa_bss *bss,
@@ -213,11 +215,12 @@
 			   unsigned int age_ms,
 			   struct os_reltime *update_time);
 
-struct wpabuf * wpa_bss_defrag_mle(const struct wpa_bss *bss, u8 type);
 int wpa_bss_parse_basic_ml_element(struct wpa_supplicant *wpa_s,
 				   struct wpa_bss *bss,
 				   u8 *ap_mld_addr,
-				   u16 *missing_links);
+				   u16 *missing_links,
+				   struct wpa_ssid *ssid,
+				   u8 *ap_mld_id);
 u16 wpa_bss_parse_reconf_ml_element(struct wpa_supplicant *wpa_s,
 				    struct wpa_bss *bss);
 
diff --git a/wpa_supplicant/bssid_ignore.c b/wpa_supplicant/bssid_ignore.c
index e378577..ab16fc5 100644
--- a/wpa_supplicant/bssid_ignore.c
+++ b/wpa_supplicant/bssid_ignore.c
@@ -37,7 +37,7 @@
 
 	e = wpa_s->bssid_ignore;
 	while (e) {
-		if (os_memcmp(e->bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(e->bssid, bssid))
 			return e;
 		e = e->next;
 	}
@@ -85,9 +85,9 @@
 			e->timeout_secs = 60;
 		else
 			e->timeout_secs = 10;
-		wpa_printf(MSG_INFO, "BSSID " MACSTR
-			   " ignore list count incremented to %d, ignoring for %d seconds",
-			   MAC2STR(bssid), e->count, e->timeout_secs);
+		wpa_msg(wpa_s, MSG_INFO, "BSSID " MACSTR
+			" ignore list count incremented to %d, ignoring for %d seconds",
+			MAC2STR(bssid), e->count, e->timeout_secs);
 		return e->count;
 	}
 
@@ -100,9 +100,9 @@
 	e->start = now;
 	e->next = wpa_s->bssid_ignore;
 	wpa_s->bssid_ignore = e;
-	wpa_printf(MSG_DEBUG, "Added BSSID " MACSTR
-		   " into ignore list, ignoring for %d seconds",
-		   MAC2STR(bssid), e->timeout_secs);
+	wpa_msg(wpa_s, MSG_INFO, "Added BSSID " MACSTR
+		" into ignore list, ignoring for %d seconds",
+		MAC2STR(bssid), e->timeout_secs);
 
 	return e->count;
 }
@@ -123,14 +123,14 @@
 
 	e = wpa_s->bssid_ignore;
 	while (e) {
-		if (os_memcmp(e->bssid, bssid, ETH_ALEN) == 0) {
+		if (ether_addr_equal(e->bssid, bssid)) {
 			if (prev == NULL) {
 				wpa_s->bssid_ignore = e->next;
 			} else {
 				prev->next = e->next;
 			}
-			wpa_printf(MSG_DEBUG, "Removed BSSID " MACSTR
-				   " from ignore list", MAC2STR(bssid));
+			wpa_msg(wpa_s, MSG_INFO, "Removed BSSID " MACSTR
+				" from ignore list", MAC2STR(bssid));
 			os_free(e);
 			return 0;
 		}
@@ -175,8 +175,8 @@
 	while (e) {
 		prev = e;
 		e = e->next;
-		wpa_printf(MSG_DEBUG, "Removed BSSID " MACSTR
-			   " from ignore list (clear)", MAC2STR(prev->bssid));
+		wpa_msg(wpa_s, MSG_INFO, "Removed BSSID " MACSTR
+			" from ignore list (clear)", MAC2STR(prev->bssid));
 		os_free(prev);
 	}
 }
@@ -209,9 +209,9 @@
 				wpa_s->bssid_ignore = e->next;
 				e = wpa_s->bssid_ignore;
 			}
-			wpa_printf(MSG_INFO, "Removed BSSID " MACSTR
-				   " from ignore list (expired)",
-				   MAC2STR(to_delete->bssid));
+			wpa_msg(wpa_s, MSG_INFO, "Removed BSSID " MACSTR
+				" from ignore list (expired)",
+				MAC2STR(to_delete->bssid));
 			os_free(to_delete);
 		} else {
 			prev = e;
diff --git a/wpa_supplicant/config.c b/wpa_supplicant/config.c
index d1d8ca3..8c0db68 100644
--- a/wpa_supplicant/config.c
+++ b/wpa_supplicant/config.c
@@ -2368,7 +2368,7 @@
 	u8 mac_value[ETH_ALEN];
 
 	if (hwaddr_aton(value, mac_value) == 0) {
-		if (os_memcmp(mac_value, ssid->mac_value, ETH_ALEN) == 0)
+		if (ether_addr_equal(mac_value, ssid->mac_value))
 			return 1;
 		os_memcpy(ssid->mac_value, mac_value, ETH_ALEN);
 		return 0;
@@ -4681,6 +4681,10 @@
 		config->driver_param = os_strdup(driver_param);
 	config->gas_rand_addr_lifetime = DEFAULT_RAND_ADDR_LIFETIME;
 
+#ifdef CONFIG_TESTING_OPTIONS
+	config->mld_connect_band_pref = DEFAULT_MLD_CONNECT_BAND_PREF;
+#endif /* CONFIG_TESTING_OPTIONS */
+
 	return config;
 }
 
@@ -5315,6 +5319,23 @@
 #endif /* CONFIG_P2P */
 
 
+#ifdef CONFIG_TESTING_OPTIONS
+static int wpa_config_process_mld_connect_bssid_pref(
+	const struct global_parse_data *data,
+	struct wpa_config *config, int line, const char *pos)
+{
+	if (hwaddr_aton2(pos, config->mld_connect_bssid_pref) < 0) {
+		wpa_printf(MSG_ERROR,
+			   "Line %d: Invalid mld_connect_bssid_pref '%s'",
+			   line, pos);
+		return -1;
+	}
+
+	return 0;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+
 #ifdef OFFSET
 #undef OFFSET
 #endif /* OFFSET */
@@ -5531,6 +5552,15 @@
 	{ INT_RANGE(pasn_corrupt_mic, 0, 1), 0 },
 #endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_PASN */
+#ifdef CONFIG_TESTING_OPTIONS
+	{ INT_RANGE(mld_force_single_link, 0, 1), 0 },
+	{ INT_RANGE(mld_connect_band_pref, 0, MLD_CONNECT_BAND_PREF_MAX), 0 },
+	{ FUNC(mld_connect_bssid_pref), 0 },
+#endif /* CONFIG_TESTING_OPTIONS */
+	{ INT_RANGE(ft_prepend_pmkid, 0, 1), CFG_CHANGED_FT_PREPEND_PMKID },
+	/* NOTE: When adding new parameters here, add_interface() in
+	 * wpa_supplicant/dbus_new_introspect.c may need to be modified to
+	 * increase the size of the iface->xml buffer. */
 };
 
 #undef FUNC
diff --git a/wpa_supplicant/config.h b/wpa_supplicant/config.h
index 09bcbc8..56d5c61 100644
--- a/wpa_supplicant/config.h
+++ b/wpa_supplicant/config.h
@@ -48,6 +48,7 @@
 #define DEFAULT_EXTENDED_KEY_ID 0
 #define DEFAULT_BTM_OFFLOAD 0
 #define DEFAULT_SCAN_RES_VALID_FOR_CONNECT 5
+#define DEFAULT_MLD_CONNECT_BAND_PREF MLD_CONNECT_BAND_PREF_AUTO
 
 #include "config_ssid.h"
 #include "wps/wps.h"
@@ -451,7 +452,8 @@
 #define CFG_CHANGED_WOWLAN_TRIGGERS BIT(18)
 #define CFG_CHANGED_DISABLE_BTM BIT(19)
 #define CFG_CHANGED_BGSCAN BIT(20)
-#define CFG_CHANGED_DISABLE_BTM_NOTIFY BIT(21)
+#define CFG_CHANGED_FT_PREPEND_PMKID BIT(21)
+#define CFG_CHANGED_DISABLE_BTM_NOTIFY BIT(22)
 
 /**
  * struct wpa_config - wpa_supplicant configuration data
@@ -726,6 +728,14 @@
 	unsigned int dot11RSNAConfigSATimeout;
 
 	/**
+	 * ft_prepend_pmkid - Whether to prepend PMKR1Name with PMKIDs
+	 *
+	 * This control whether PMKR1Name is prepended to the PMKID list
+	 * insread of replacing the full list when constructing RSNE for
+	 * EAPOL-Key msg 2/4 for FT cases. */
+	bool ft_prepend_pmkid;
+
+	/**
 	 * update_config - Is wpa_supplicant allowed to update configuration
 	 *
 	 * This variable control whether wpa_supplicant is allow to re-write
@@ -1806,6 +1816,20 @@
 
 #endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_PASN*/
+
+#ifdef CONFIG_TESTING_OPTIONS
+	enum {
+		MLD_CONNECT_BAND_PREF_AUTO = 0,
+		MLD_CONNECT_BAND_PREF_2GHZ = 1,
+		MLD_CONNECT_BAND_PREF_5GHZ = 2,
+		MLD_CONNECT_BAND_PREF_6GHZ = 3,
+		MLD_CONNECT_BAND_PREF_MAX = 4,
+	}  mld_connect_band_pref;
+
+	u8 mld_connect_bssid_pref[ETH_ALEN];
+
+	int mld_force_single_link;
+#endif /* CONFIG_TESTING_OPTIONS */
 };
 
 
diff --git a/wpa_supplicant/config_file.c b/wpa_supplicant/config_file.c
index ebad5f1..71a64b1 100644
--- a/wpa_supplicant/config_file.c
+++ b/wpa_supplicant/config_file.c
@@ -1618,6 +1618,18 @@
 	if (config->wowlan_disconnect_on_deinit)
 		fprintf(f, "wowlan_disconnect_on_deinit=%d\n",
 			config->wowlan_disconnect_on_deinit);
+#ifdef CONFIG_TESTING_OPTIONS
+	if (config->mld_force_single_link)
+		fprintf(f, "mld_force_single_link=1\n");
+	if (config->mld_connect_band_pref != MLD_CONNECT_BAND_PREF_AUTO)
+		fprintf(f, "mld_connect_band_pref=%d\n",
+			config->mld_connect_band_pref);
+	if (!is_zero_ether_addr(config->mld_connect_bssid_pref))
+		fprintf(f, "mld_connect_bssid_pref=" MACSTR "\n",
+			MAC2STR(config->mld_connect_bssid_pref));
+#endif /* CONFIG_TESTING_OPTIONS */
+	if (config->ft_prepend_pmkid)
+		fprintf(f, "ft_prepend_pmkid=%d", config->ft_prepend_pmkid);
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */
diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c
index a68802e..500f4d1 100644
--- a/wpa_supplicant/ctrl_iface.c
+++ b/wpa_supplicant/ctrl_iface.c
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant / Control interface (shared code for all backends)
- * Copyright (c) 2004-2020, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2004-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -22,6 +22,7 @@
 #ifdef CONFIG_DPP
 #include "common/dpp.h"
 #endif /* CONFIG_DPP */
+#include "common/nan_de.h"
 #include "common/ptksa_cache.h"
 #include "crypto/tls.h"
 #include "ap/hostapd.h"
@@ -58,6 +59,7 @@
 #include "mesh.h"
 #include "dpp_supplicant.h"
 #include "sme.h"
+#include "nan_usd.h"
 
 #ifdef __NetBSD__
 #include <net/if_ether.h>
@@ -445,7 +447,7 @@
 
 	dl_list_for_each(tmp, &wpa_s->drv_signal_override,
 			 struct driver_signal_override, list) {
-		if (os_memcmp(bssid, tmp->bssid, ETH_ALEN) == 0) {
+		if (ether_addr_equal(bssid, tmp->bssid)) {
 			dso = tmp;
 			break;
 		}
@@ -739,6 +741,12 @@
 				wpa_s->ext_eapol_frame_io;
 		}
 #endif /* CONFIG_AP */
+	} else if (os_strcasecmp(cmd, "encrypt_eapol_m2") == 0) {
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_ENCRYPT_EAPOL_M2,
+				 !!atoi(value));
+	} else if (os_strcasecmp(cmd, "encrypt_eapol_m4") == 0) {
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_ENCRYPT_EAPOL_M4,
+				 !!atoi(value));
 	} else if (os_strcasecmp(cmd, "extra_roc_dur") == 0) {
 		wpa_s->extra_roc_dur = atoi(value);
 	} else if (os_strcasecmp(cmd, "test_failure") == 0) {
@@ -833,15 +841,19 @@
 			wpa_s->sae_commit_override = wpabuf_parse_bin(value);
 	} else if (os_strcasecmp(cmd, "driver_signal_override") == 0) {
 		ret = wpas_ctrl_iface_set_dso(wpa_s, value);
+#ifndef CONFIG_NO_ROBUST_AV
 	} else if (os_strcasecmp(cmd, "disable_scs_support") == 0) {
 		wpa_s->disable_scs_support = !!atoi(value);
 	} else if (os_strcasecmp(cmd, "disable_mscs_support") == 0) {
 		wpa_s->disable_mscs_support = !!atoi(value);
+#endif /* CONFIG_NO_ROBUST_AV */
 	} else if (os_strcasecmp(cmd, "disable_eapol_g2_tx") == 0) {
 		wpa_s->disable_eapol_g2_tx = !!atoi(value);
 		/* Populate value to wpa_sm if already associated. */
 		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_DISABLE_EAPOL_G2_TX,
 				 wpa_s->disable_eapol_g2_tx);
+	} else if (os_strcasecmp(cmd, "test_assoc_comeback_type") == 0) {
+		wpa_s->test_assoc_comeback_type = atoi(value);
 #ifdef CONFIG_DPP
 	} else if (os_strcasecmp(cmd, "dpp_config_obj_override") == 0) {
 		os_free(wpa_s->dpp_config_obj_override);
@@ -932,8 +944,10 @@
 			return -1;
 		wnm_set_coloc_intf_elems(wpa_s, elems);
 #endif /* CONFIG_WNM */
+#ifndef CONFIG_NO_ROBUST_AV
 	} else if (os_strcasecmp(cmd, "enable_dscp_policy_capa") == 0) {
 		wpa_s->enable_dscp_policy_capa = !!atoi(value);
+#endif /* CONFIG_NO_ROBUST_AV */
 	} else {
 		value[-1] = '=';
 		ret = wpa_config_process_global(wpa_s->conf, cmd, -1);
@@ -1260,6 +1274,8 @@
 #endif /* CONFIG_TDLS */
 
 
+#ifndef CONFIG_NO_WMM_AC
+
 static int wmm_ac_ctrl_addts(struct wpa_supplicant *wpa_s, char *cmd)
 {
 	char *token, *context = NULL;
@@ -1309,6 +1325,8 @@
 	return wpas_wmm_ac_delts(wpa_s, tsid);
 }
 
+#endif /* CONFIG_NO_WMM_AC */
+
 
 #ifdef CONFIG_IEEE80211R
 static int wpa_supplicant_ctrl_iface_ft_ds(
@@ -3618,7 +3636,7 @@
 #endif /* CONFIG_BGSCAN */
 
 	if (os_strcmp(name, "bssid") != 0 &&
-	    os_strcmp(name, "bssid_hint") != 0 &&
+	    os_strncmp(name, "bssid_", 6) != 0 &&
 	    os_strcmp(name, "scan_freq") != 0 &&
 	    os_strcmp(name, "priority") != 0) {
 		wpa_sm_pmksa_cache_flush(wpa_s->wpa, ssid);
@@ -3683,7 +3701,7 @@
 						       value);
 	if (ret == 0 &&
 	    (ssid->bssid_set != prev_bssid_set ||
-	     os_memcmp(ssid->bssid, prev_bssid, ETH_ALEN) != 0))
+	     !ether_addr_equal(ssid->bssid, prev_bssid)))
 		wpas_notify_network_bssid_set_changed(wpa_s, ssid);
 
 	if (prev_disabled != ssid->disabled &&
@@ -4859,6 +4877,15 @@
 	}
 #endif /* CONFIG_DPP */
 
+#ifdef CONFIG_NAN_USD
+	if (os_strcmp(field, "nan") == 0) {
+		res = os_snprintf(buf, buflen, "USD");
+		if (os_snprintf_error(buflen, res))
+			return -1;
+		return res;
+	}
+#endif /* CONFIG_NAN_USD */
+
 #ifdef CONFIG_SAE
 	if (os_strcmp(field, "sae") == 0 &&
 	    (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SAE)) {
@@ -4954,7 +4981,7 @@
 	ie_end = ie + 2 + ie[1];
 	ie += 2;
 	if (ie_end - ie < 2)
-		return -1;
+		return 0;
 
 	info = WPA_GET_LE16(ie);
 	ie += 2;
@@ -4966,7 +4993,7 @@
 	if (info & BIT(7)) {
 		/* Cache Identifier Included */
 		if (ie_end - ie < 2)
-			return -1;
+			return 0;
 		ret = os_snprintf(pos, end - pos, "fils_cache_id=%02x%02x\n",
 				  ie[0], ie[1]);
 		if (os_snprintf_error(end - pos, ret))
@@ -4978,7 +5005,7 @@
 	if (info & BIT(8)) {
 		/* HESSID Included */
 		if (ie_end - ie < ETH_ALEN)
-			return -1;
+			return 0;
 		ret = os_snprintf(pos, end - pos, "fils_hessid=" MACSTR "\n",
 				  MAC2STR(ie));
 		if (os_snprintf_error(end - pos, ret))
@@ -4990,7 +5017,7 @@
 	realms = (info & (BIT(3) | BIT(4) | BIT(5))) >> 3;
 	if (realms) {
 		if (ie_end - ie < realms * 2)
-			return -1;
+			return 0;
 		ret = os_snprintf(pos, end - pos, "fils_realms=");
 		if (os_snprintf_error(end - pos, ret))
 			return 0;
@@ -5244,7 +5271,7 @@
 		if (common_info_length < 1)
 			return 0;
 
-		ret = os_snprintf(pos, end - pos, ", MLD ID=0x%x\n", *ie);
+		ret = os_snprintf(pos, end - pos, ", MLD ID=0x%x", *ie);
 		if (os_snprintf_error(end - pos, ret))
 			return 0;
 		pos += ret;
@@ -5252,6 +5279,11 @@
 		common_info_length--;
 	}
 
+	ret = os_snprintf(pos, end - pos, "\n");
+	if (os_snprintf_error(end - pos, ret))
+		return 0;
+	pos += ret;
+
 	return pos - start;
 }
 
@@ -5386,13 +5418,13 @@
 		if (ieee802_11_rsnx_capab(rsnxe, WLAN_RSNX_CAPAB_SAE_H2E)) {
 			ret = os_snprintf(pos, end - pos, "[SAE-H2E]");
 			if (os_snprintf_error(end - pos, ret))
-				return -1;
+				return 0;
 			pos += ret;
 		}
 		if (ieee802_11_rsnx_capab(rsnxe, WLAN_RSNX_CAPAB_SAE_PK)) {
 			ret = os_snprintf(pos, end - pos, "[SAE-PK]");
 			if (os_snprintf_error(end - pos, ret))
-				return -1;
+				return 0;
 			pos += ret;
 		}
 		osen_ie = wpa_bss_get_vendor_ie(bss, OSEN_IE_VENDOR_TYPE);
@@ -5691,8 +5723,6 @@
 #ifdef CONFIG_FILS
 	if (mask & WPA_BSS_MASK_FILS_INDICATION) {
 		ret = print_fils_indication(bss, pos, end);
-		if (ret < 0)
-			return 0;
 		pos += ret;
 	}
 #endif /* CONFIG_FILS */
@@ -6289,32 +6319,6 @@
 }
 
 
-static int parse_freq(int chwidth, int freq2)
-{
-	if (freq2 < 0)
-		return -1;
-	if (freq2)
-		return CONF_OPER_CHWIDTH_80P80MHZ;
-
-	switch (chwidth) {
-	case 0:
-	case 20:
-	case 40:
-		return CONF_OPER_CHWIDTH_USE_HT;
-	case 80:
-		return CONF_OPER_CHWIDTH_80MHZ;
-	case 160:
-		return CONF_OPER_CHWIDTH_160MHZ;
-	case 320:
-		return CONF_OPER_CHWIDTH_320MHZ;
-	default:
-		wpa_printf(MSG_DEBUG, "Unknown max oper bandwidth: %d",
-			   chwidth);
-		return -1;
-	}
-}
-
-
 static int p2p_ctrl_connect(struct wpa_supplicant *wpa_s, char *cmd,
 			    char *buf, size_t buflen)
 {
@@ -6408,7 +6412,7 @@
 	if (pos2)
 		chwidth = atoi(pos2 + 18);
 
-	max_oper_chwidth = parse_freq(chwidth, freq2);
+	max_oper_chwidth = chwidth_freq2_to_ch_width(chwidth, freq2);
 	if (max_oper_chwidth < 0)
 		return -1;
 
@@ -7062,7 +7066,7 @@
 	if (pos)
 		chwidth = atoi(pos + 18);
 
-	max_oper_chwidth = parse_freq(chwidth, freq2);
+	max_oper_chwidth = chwidth_freq2_to_ch_width(chwidth, freq2);
 	if (max_oper_chwidth < 0)
 		return -1;
 
@@ -7138,8 +7142,8 @@
 		return -1;
 	}
 
-	return wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq,
-					     vht_center_freq2, 0, ht40, vht,
+	return wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0,
+					     vht_center_freq2, ht40, vht,
 					     vht_chwidth, he, edmg,
 					     NULL, 0, 0, allow_6ghz, 0,
 					     go_bssid);
@@ -7217,7 +7221,7 @@
 	}
 #endif /* CONFIG_ACS */
 
-	max_oper_chwidth = parse_freq(chwidth, freq2);
+	max_oper_chwidth = chwidth_freq2_to_ch_width(chwidth, freq2);
 	if (max_oper_chwidth < 0)
 		return -1;
 
@@ -7837,7 +7841,7 @@
 
 		dl_list_for_each_reverse(bss, &wpa_s->bss, struct wpa_bss,
 					 list) {
-			if (os_memcmp(bss->bssid, bssid, ETH_ALEN) == 0 &&
+			if (ether_addr_equal(bss->bssid, bssid) &&
 			    bss->ssid_len > 0) {
 				found = 1;
 				break;
@@ -8004,11 +8008,11 @@
 	dialog_token = atoi(pos);
 
 	if (wpa_s->last_gas_resp &&
-	    os_memcmp(addr, wpa_s->last_gas_addr, ETH_ALEN) == 0 &&
+	    ether_addr_equal(addr, wpa_s->last_gas_addr) &&
 	    dialog_token == wpa_s->last_gas_dialog_token)
 		resp = wpa_s->last_gas_resp;
 	else if (wpa_s->prev_gas_resp &&
-		 os_memcmp(addr, wpa_s->prev_gas_addr, ETH_ALEN) == 0 &&
+		 ether_addr_equal(addr, wpa_s->prev_gas_addr) &&
 		 dialog_token == wpa_s->prev_gas_dialog_token)
 		resp = wpa_s->prev_gas_resp;
 	else
@@ -8867,6 +8871,10 @@
 	wpa_s->ft_rsnxe_used = 0;
 	wpa_s->reject_btm_req_reason = 0;
 	wpa_sm_set_test_assoc_ie(wpa_s->wpa, NULL);
+	wpa_sm_set_test_eapol_m2_elems(wpa_s->wpa, NULL);
+	wpa_sm_set_test_eapol_m4_elems(wpa_s->wpa, NULL);
+	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_ENCRYPT_EAPOL_M2, 0);
+	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_ENCRYPT_EAPOL_M4, 0);
 	os_free(wpa_s->get_pref_freq_list_override);
 	wpa_s->get_pref_freq_list_override = NULL;
 	wpabuf_free(wpa_s->sae_commit_override);
@@ -8880,9 +8888,11 @@
 	wpabuf_free(wpa_s->rsnxe_override_eapol);
 	wpa_s->rsnxe_override_eapol = NULL;
 	wpas_clear_driver_signal_override(wpa_s);
+#ifndef CONFIG_NO_ROBUST_AV
 	wpa_s->disable_scs_support = 0;
 	wpa_s->disable_mscs_support = 0;
 	wpa_s->enable_dscp_policy_capa = 0;
+#endif /* CONFIG_NO_ROBUST_AV */
 	wpa_s->oci_freq_override_eapol = 0;
 	wpa_s->oci_freq_override_saquery_req = 0;
 	wpa_s->oci_freq_override_saquery_resp = 0;
@@ -8891,6 +8901,7 @@
 	wpa_s->oci_freq_override_fils_assoc = 0;
 	wpa_s->oci_freq_override_wnm_sleep = 0;
 	wpa_s->disable_eapol_g2_tx = 0;
+	wpa_s->test_assoc_comeback_type = -1;
 #ifdef CONFIG_DPP
 	os_free(wpa_s->dpp_config_obj_override);
 	wpa_s->dpp_config_obj_override = NULL;
@@ -8911,7 +8922,9 @@
 	wpa_s->next_scan_bssid_wildcard_ssid = 0;
 	os_free(wpa_s->select_network_scan_freqs);
 	wpa_s->select_network_scan_freqs = NULL;
+#ifndef CONFIG_NO_ROBUST_AV
 	os_memset(&wpa_s->robust_av, 0, sizeof(struct robust_av_data));
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	wpa_bss_flush(wpa_s);
 	if (!dl_list_empty(&wpa_s->bss)) {
@@ -8938,7 +8951,9 @@
 
 	free_bss_tmp_disallowed(wpa_s);
 
+#ifndef CONFIG_NO_ROBUST_AV
 	os_memset(&wpa_s->robust_av, 0, sizeof(struct robust_av_data));
+#endif /* CONFIG_NO_ROBUST_AV */
 
 #ifdef CONFIG_PASN
 	wpas_pasn_auth_stop(wpa_s);
@@ -8948,6 +8963,10 @@
 		wpas_restore_permanent_mac_addr(wpa_s);
 
 	wpa_s->conf->ignore_old_scan_res = 0;
+
+#ifdef CONFIG_NAN_USD
+	wpas_nan_usd_flush(wpa_s);
+#endif /* CONFIG_NAN_USD */
 }
 
 
@@ -10134,72 +10153,6 @@
 }
 
 
-static int wpas_ctrl_test_alloc_fail(struct wpa_supplicant *wpa_s, char *cmd)
-{
-#ifdef WPA_TRACE_BFD
-	char *pos;
-
-	wpa_trace_fail_after = atoi(cmd);
-	pos = os_strchr(cmd, ':');
-	if (pos) {
-		pos++;
-		os_strlcpy(wpa_trace_fail_func, pos,
-			   sizeof(wpa_trace_fail_func));
-	} else {
-		wpa_trace_fail_after = 0;
-	}
-	return 0;
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int wpas_ctrl_get_alloc_fail(struct wpa_supplicant *wpa_s,
-				    char *buf, size_t buflen)
-{
-#ifdef WPA_TRACE_BFD
-	return os_snprintf(buf, buflen, "%u:%s", wpa_trace_fail_after,
-			   wpa_trace_fail_func);
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int wpas_ctrl_test_fail(struct wpa_supplicant *wpa_s, char *cmd)
-{
-#ifdef WPA_TRACE_BFD
-	char *pos;
-
-	wpa_trace_test_fail_after = atoi(cmd);
-	pos = os_strchr(cmd, ':');
-	if (pos) {
-		pos++;
-		os_strlcpy(wpa_trace_test_fail_func, pos,
-			   sizeof(wpa_trace_test_fail_func));
-	} else {
-		wpa_trace_test_fail_after = 0;
-	}
-	return 0;
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
-static int wpas_ctrl_get_fail(struct wpa_supplicant *wpa_s,
-				    char *buf, size_t buflen)
-{
-#ifdef WPA_TRACE_BFD
-	return os_snprintf(buf, buflen, "%u:%s", wpa_trace_test_fail_after,
-			   wpa_trace_test_fail_func);
-#else /* WPA_TRACE_BFD */
-	return -1;
-#endif /* WPA_TRACE_BFD */
-}
-
-
 static void wpas_ctrl_event_test_cb(void *eloop_ctx, void *timeout_ctx)
 {
 	struct wpa_supplicant *wpa_s = eloop_ctx;
@@ -10227,13 +10180,12 @@
 }
 
 
-static int wpas_ctrl_test_assoc_ie(struct wpa_supplicant *wpa_s,
-				   const char *cmd)
+static int wpas_get_hex_buf(const char *val, struct wpabuf **ret)
 {
 	struct wpabuf *buf;
 	size_t len;
 
-	len = os_strlen(cmd);
+	len = os_strlen(val);
 	if (len & 1)
 		return -1;
 	len /= 2;
@@ -10242,20 +10194,56 @@
 		buf = NULL;
 	} else {
 		buf = wpabuf_alloc(len);
-		if (buf == NULL)
+		if (!buf)
 			return -1;
 
-		if (hexstr2bin(cmd, wpabuf_put(buf, len), len) < 0) {
+		if (hexstr2bin(val, wpabuf_put(buf, len), len) < 0) {
 			wpabuf_free(buf);
 			return -1;
 		}
 	}
 
+	*ret = buf;
+	return 0;
+}
+
+
+static int wpas_ctrl_test_assoc_ie(struct wpa_supplicant *wpa_s,
+				   const char *cmd)
+{
+	struct wpabuf *buf;
+
+	if (wpas_get_hex_buf(cmd, &buf) < 0)
+		return -1;
 	wpa_sm_set_test_assoc_ie(wpa_s->wpa, buf);
 	return 0;
 }
 
 
+static int wpas_ctrl_test_eapol_m2_elems(struct wpa_supplicant *wpa_s,
+					 const char *cmd)
+{
+	struct wpabuf *buf;
+
+	if (wpas_get_hex_buf(cmd, &buf) < 0)
+		return -1;
+	wpa_sm_set_test_eapol_m2_elems(wpa_s->wpa, buf);
+	return 0;
+}
+
+
+static int wpas_ctrl_test_eapol_m4_elems(struct wpa_supplicant *wpa_s,
+					 const char *cmd)
+{
+	struct wpabuf *buf;
+
+	if (wpas_get_hex_buf(cmd, &buf) < 0)
+		return -1;
+	wpa_sm_set_test_eapol_m4_elems(wpa_s->wpa, buf);
+	return 0;
+}
+
+
 static int wpas_ctrl_reset_pn(struct wpa_supplicant *wpa_s)
 {
 	u8 zero[WPA_TK_MAX_LEN];
@@ -10571,6 +10559,8 @@
 }
 
 
+#ifndef CONFIG_NO_RRM
+
 static void wpas_ctrl_neighbor_rep_cb(void *ctx, struct wpabuf *neighbor_rep)
 {
 	struct wpa_supplicant *wpa_s = ctx;
@@ -10714,6 +10704,8 @@
 	return ret;
 }
 
+#endif /* CONFIG_NO_RRM */
+
 
 static int wpas_ctrl_iface_erp_flush(struct wpa_supplicant *wpa_s)
 {
@@ -11067,6 +11059,7 @@
 }
 
 
+#ifndef CONFIG_NO_ROBUST_AV
 static int wpas_ctrl_iface_configure_mscs(struct wpa_supplicant *wpa_s,
 					  const char *cmd)
 {
@@ -11135,6 +11128,7 @@
 
 	return wpas_send_mscs_req(wpa_s);
 }
+#endif /* CONFIG_NO_ROBUST_AV */
 
 
 #ifdef CONFIG_PASN
@@ -11236,9 +11230,55 @@
 	return wpas_pasn_deauthenticate(wpa_s, wpa_s->own_addr, bssid);
 }
 
+
+#ifdef CONFIG_TESTING_OPTIONS
+static int wpas_ctrl_iface_pasn_driver(struct wpa_supplicant *wpa_s,
+				       const char *cmd)
+{
+	union wpa_event_data event;
+	const char *pos = cmd;
+	u8 addr[ETH_ALEN];
+
+	os_memset(&event, 0, sizeof(event));
+
+	if (os_strncmp(pos, "auth ", 5) == 0)
+		event.pasn_auth.action = PASN_ACTION_AUTH;
+	else if (os_strncmp(pos, "del ", 4) == 0)
+		event.pasn_auth.action =
+			PASN_ACTION_DELETE_SECURE_RANGING_CONTEXT;
+	else
+		return -1;
+
+	pos = os_strchr(pos, ' ');
+	if (!pos)
+		return -1;
+	pos++;
+	while (hwaddr_aton(pos, addr) == 0) {
+		struct pasn_peer *peer;
+
+		if (event.pasn_auth.num_peers == WPAS_MAX_PASN_PEERS)
+			return -1;
+		peer = &event.pasn_auth.peer[event.pasn_auth.num_peers];
+		os_memcpy(peer->own_addr, wpa_s->own_addr, ETH_ALEN);
+		os_memcpy(peer->peer_addr, addr, ETH_ALEN);
+		event.pasn_auth.num_peers++;
+
+		pos = os_strchr(pos, ' ');
+		if (!pos)
+			break;
+		pos++;
+	}
+
+	wpa_supplicant_event(wpa_s, EVENT_PASN_AUTH, &event);
+	return 0;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+
 #endif /* CONFIG_PASN */
 
 
+#ifndef CONFIG_NO_ROBUST_AV
+
 static int set_type4_frame_classifier(const char *cmd,
 				      struct type4_params *param)
 {
@@ -11935,6 +11975,8 @@
 	return wpas_send_dscp_query(wpa_s, pos + 12, os_strlen(pos + 12));
 }
 
+#endif /* CONFIG_NO_ROBUST_AV */
+
 
 static int wpas_ctrl_iface_mlo_signal_poll(struct wpa_supplicant *wpa_s,
 					   char *buf, size_t buflen)
@@ -12121,6 +12163,327 @@
 #endif /* CONFIG_TESTING_OPTIONS */
 
 
+#ifdef CONFIG_NAN_USD
+
+static int wpas_ctrl_nan_publish(struct wpa_supplicant *wpa_s, char *cmd,
+				 char *buf, size_t buflen)
+{
+	char *token, *context = NULL;
+	int publish_id;
+	struct nan_publish_params params;
+	const char *service_name = NULL;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+	enum nan_service_protocol_type srv_proto_type = 0;
+	int *freq_list = NULL;
+
+	os_memset(&params, 0, sizeof(params));
+	/* USD shall use both solicited and unsolicited transmissions */
+	params.unsolicited = true;
+	params.solicited = true;
+	/* USD shall require FSD without GAS */
+	params.fsd = true;
+	params.freq = NAN_USD_DEFAULT_FREQ;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "service_name=", 13) == 0) {
+			service_name = token + 13;
+			continue;
+		}
+
+		if (os_strncmp(token, "ttl=", 4) == 0) {
+			params.ttl = atoi(token + 4);
+			continue;
+		}
+
+		if (os_strncmp(token, "freq=", 5) == 0) {
+			params.freq = atoi(token + 5);
+			continue;
+		}
+
+		if (os_strncmp(token, "freq_list=", 10) == 0) {
+			char *pos = token + 10;
+
+			if (os_strcmp(pos, "all") == 0) {
+				os_free(freq_list);
+				freq_list = wpas_nan_usd_all_freqs(wpa_s);
+				params.freq_list = freq_list;
+				continue;
+			}
+
+			while (pos && pos[0]) {
+				int_array_add_unique(&freq_list, atoi(pos));
+				pos = os_strchr(pos, ',');
+				if (pos)
+					pos++;
+			}
+
+			params.freq_list = freq_list;
+			continue;
+		}
+
+		if (os_strncmp(token, "srv_proto_type=", 15) == 0) {
+			srv_proto_type = atoi(token + 15);
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		if (os_strcmp(token, "solicited=0") == 0) {
+			params.solicited = false;
+			continue;
+		}
+
+		if (os_strcmp(token, "unsolicited=0") == 0) {
+			params.unsolicited = false;
+			continue;
+		}
+
+		if (os_strcmp(token, "fsd=0") == 0) {
+			params.fsd = false;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO, "CTRL: Invalid NAN_PUBLISH parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	publish_id = wpas_nan_usd_publish(wpa_s, service_name, srv_proto_type,
+					  ssi, &params);
+	if (publish_id > 0)
+		ret = os_snprintf(buf, buflen, "%d", publish_id);
+fail:
+	wpabuf_free(ssi);
+	os_free(freq_list);
+	return ret;
+}
+
+
+static int wpas_ctrl_nan_cancel_publish(struct wpa_supplicant *wpa_s,
+					char *cmd)
+{
+	char *token, *context = NULL;
+	int publish_id = 0;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "publish_id=%i", &publish_id) == 1)
+			continue;
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_CANCEL_PUBLISH parameter: %s",
+			   token);
+		return -1;
+	}
+
+	if (publish_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_CANCEL_PUBLISH publish_id");
+		return -1;
+	}
+
+	wpas_nan_usd_cancel_publish(wpa_s, publish_id);
+	return 0;
+}
+
+
+static int wpas_ctrl_nan_update_publish(struct wpa_supplicant *wpa_s,
+					char *cmd)
+{
+	char *token, *context = NULL;
+	int publish_id = 0;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "publish_id=%i", &publish_id) == 1)
+			continue;
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_UPDATE_PUBLISH parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	if (publish_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_UPDATE_PUBLISH publish_id");
+		goto fail;
+	}
+
+	ret = wpas_nan_usd_update_publish(wpa_s, publish_id, ssi);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+
+static int wpas_ctrl_nan_subscribe(struct wpa_supplicant *wpa_s, char *cmd,
+				   char *buf, size_t buflen)
+{
+	char *token, *context = NULL;
+	int subscribe_id;
+	struct nan_subscribe_params params;
+	const char *service_name = NULL;
+	struct wpabuf *ssi = NULL;
+	int ret = -1;
+	enum nan_service_protocol_type srv_proto_type = 0;
+
+	os_memset(&params, 0, sizeof(params));
+	params.freq = NAN_USD_DEFAULT_FREQ;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (os_strncmp(token, "service_name=", 13) == 0) {
+			service_name = token + 13;
+			continue;
+		}
+
+		if (os_strcmp(token, "active=1") == 0) {
+			params.active = true;
+			continue;
+		}
+
+		if (os_strncmp(token, "ttl=", 4) == 0) {
+			params.ttl = atoi(token + 4);
+			continue;
+		}
+
+		if (os_strncmp(token, "freq=", 5) == 0) {
+			params.freq = atoi(token + 5);
+			continue;
+		}
+
+		if (os_strncmp(token, "srv_proto_type=", 15) == 0) {
+			srv_proto_type = atoi(token + 15);
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_SUBSCRIBE parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	subscribe_id = wpas_nan_usd_subscribe(wpa_s, service_name,
+					      srv_proto_type, ssi,
+					      &params);
+	if (subscribe_id > 0)
+		ret = os_snprintf(buf, buflen, "%d", subscribe_id);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+
+static int wpas_ctrl_nan_cancel_subscribe(struct wpa_supplicant *wpa_s,
+					  char *cmd)
+{
+	char *token, *context = NULL;
+	int subscribe_id = 0;
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "subscribe_id=%i", &subscribe_id) == 1)
+			continue;
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_CANCEL_SUBSCRIBE parameter: %s",
+			   token);
+		return -1;
+	}
+
+	if (subscribe_id <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_CANCEL_SUBSCRIBE subscribe_id");
+		return -1;
+	}
+
+	wpas_nan_usd_cancel_subscribe(wpa_s, subscribe_id);
+	return 0;
+}
+
+
+static int wpas_ctrl_nan_transmit(struct wpa_supplicant *wpa_s, char *cmd)
+{
+	char *token, *context = NULL;
+	int handle = 0;
+	int req_instance_id = 0;
+	struct wpabuf *ssi = NULL;
+	u8 peer_addr[ETH_ALEN];
+	int ret = -1;
+
+	os_memset(peer_addr, 0, ETH_ALEN);
+
+	while ((token = str_token(cmd, " ", &context))) {
+		if (sscanf(token, "handle=%i", &handle) == 1)
+			continue;
+
+		if (sscanf(token, "req_instance_id=%i", &req_instance_id) == 1)
+			continue;
+
+		if (os_strncmp(token, "address=", 8) == 0) {
+			if (hwaddr_aton(token + 8, peer_addr) < 0)
+				return -1;
+			continue;
+		}
+
+		if (os_strncmp(token, "ssi=", 4) == 0) {
+			if (ssi)
+				goto fail;
+			ssi = wpabuf_parse_bin(token + 4);
+			if (!ssi)
+				goto fail;
+			continue;
+		}
+
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid NAN_TRANSMIT parameter: %s",
+			   token);
+		goto fail;
+	}
+
+	if (handle <= 0) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_TRANSMIT handle");
+		goto fail;
+	}
+
+	if (is_zero_ether_addr(peer_addr)) {
+		wpa_printf(MSG_INFO,
+			   "CTRL: Invalid or missing NAN_TRANSMIT address");
+		goto fail;
+	}
+
+	ret = wpas_nan_usd_transmit(wpa_s, handle, ssi, NULL, peer_addr,
+				    req_instance_id);
+fail:
+	wpabuf_free(ssi);
+	return ret;
+}
+
+#endif /* CONFIG_NAN_USD */
+
+
 char * wpa_supplicant_ctrl_iface_process(struct wpa_supplicant *wpa_s,
 					 char *buf, size_t *resp_len)
 {
@@ -12792,6 +13155,7 @@
 		reply_len = wpa_supplicant_ctrl_iface_tdls_link_status(
 			wpa_s, buf + 17, reply, reply_size);
 #endif /* CONFIG_TDLS */
+#ifndef CONFIG_NO_WMM_AC
 	} else if (os_strcmp(buf, "WMM_AC_STATUS") == 0) {
 		reply_len = wpas_wmm_ac_status(wpa_s, reply, reply_size);
 	} else if (os_strncmp(buf, "WMM_AC_ADDTS ", 13) == 0) {
@@ -12800,6 +13164,7 @@
 	} else if (os_strncmp(buf, "WMM_AC_DELTS ", 13) == 0) {
 		if (wmm_ac_ctrl_delts(wpa_s, buf + 13))
 			reply_len = -1;
+#endif /* CONFIG_NO_WMM_AC */
 	} else if (os_strncmp(buf, "SIGNAL_POLL", 11) == 0) {
 		reply_len = wpa_supplicant_signal_poll(wpa_s, reply,
 						       reply_size);
@@ -12886,21 +13251,27 @@
 		if (wpas_ctrl_iface_data_test_frame(wpa_s, buf + 16) < 0)
 			reply_len = -1;
 	} else if (os_strncmp(buf, "TEST_ALLOC_FAIL ", 16) == 0) {
-		if (wpas_ctrl_test_alloc_fail(wpa_s, buf + 16) < 0)
+		if (testing_set_fail_pattern(true, buf + 16) < 0)
 			reply_len = -1;
 	} else if (os_strcmp(buf, "GET_ALLOC_FAIL") == 0) {
-		reply_len = wpas_ctrl_get_alloc_fail(wpa_s, reply, reply_size);
+		reply_len = testing_get_fail_pattern(true, reply, reply_size);
 	} else if (os_strncmp(buf, "TEST_FAIL ", 10) == 0) {
-		if (wpas_ctrl_test_fail(wpa_s, buf + 10) < 0)
+		if (testing_set_fail_pattern(false, buf + 10) < 0)
 			reply_len = -1;
 	} else if (os_strcmp(buf, "GET_FAIL") == 0) {
-		reply_len = wpas_ctrl_get_fail(wpa_s, reply, reply_size);
+		reply_len = testing_get_fail_pattern(false, reply, reply_size);
 	} else if (os_strncmp(buf, "EVENT_TEST ", 11) == 0) {
 		if (wpas_ctrl_event_test(wpa_s, buf + 11) < 0)
 			reply_len = -1;
 	} else if (os_strncmp(buf, "TEST_ASSOC_IE ", 14) == 0) {
 		if (wpas_ctrl_test_assoc_ie(wpa_s, buf + 14) < 0)
 			reply_len = -1;
+	} else if (os_strncmp(buf, "TEST_EAPOL_M2_ELEMS ", 20) == 0) {
+		if (wpas_ctrl_test_eapol_m2_elems(wpa_s, buf + 20) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "TEST_EAPOL_M4_ELEMS ", 20) == 0) {
+		if (wpas_ctrl_test_eapol_m4_elems(wpa_s, buf + 20) < 0)
+			reply_len = -1;
 	} else if (os_strcmp(buf, "RESET_PN") == 0) {
 		if (wpas_ctrl_reset_pn(wpa_s) < 0)
 			reply_len = -1;
@@ -12939,9 +13310,11 @@
 	} else if (os_strncmp(buf, "VENDOR_ELEM_REMOVE ", 19) == 0) {
 		if (wpas_ctrl_vendor_elem_remove(wpa_s, buf + 19) < 0)
 			reply_len = -1;
+#ifndef CONFIG_NO_RRM
 	} else if (os_strncmp(buf, "NEIGHBOR_REP_REQUEST", 20) == 0) {
 		if (wpas_ctrl_iface_send_neighbor_rep(wpa_s, buf + 20))
 			reply_len = -1;
+#endif /* CONFIG_NO_RRM */
 	} else if (os_strcmp(buf, "ERP_FLUSH") == 0) {
 		wpas_ctrl_iface_erp_flush(wpa_s);
 	} else if (os_strncmp(buf, "MAC_RAND_SCAN ", 14) == 0) {
@@ -13114,9 +13487,26 @@
 			reply_len = -1;
 #endif /* CONFIG_DPP3 */
 #endif /* CONFIG_DPP */
-	} else if (os_strncmp(buf, "MSCS ", 5) == 0) {
-		if (wpas_ctrl_iface_configure_mscs(wpa_s, buf + 5))
+#ifdef CONFIG_NAN_USD
+	} else if (os_strncmp(buf, "NAN_PUBLISH ", 12) == 0) {
+		reply_len = wpas_ctrl_nan_publish(wpa_s, buf + 12, reply,
+						  reply_size);
+	} else if (os_strncmp(buf, "NAN_CANCEL_PUBLISH ", 19) == 0) {
+		if (wpas_ctrl_nan_cancel_publish(wpa_s, buf + 19) < 0)
 			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_UPDATE_PUBLISH ", 19) == 0) {
+		if (wpas_ctrl_nan_update_publish(wpa_s, buf + 19) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_SUBSCRIBE ", 14) == 0) {
+		reply_len = wpas_ctrl_nan_subscribe(wpa_s, buf + 14, reply,
+						    reply_size);
+	} else if (os_strncmp(buf, "NAN_CANCEL_SUBSCRIBE ", 21) == 0) {
+		if (wpas_ctrl_nan_cancel_subscribe(wpa_s, buf + 21) < 0)
+			reply_len = -1;
+	} else if (os_strncmp(buf, "NAN_TRANSMIT ", 13) == 0) {
+		if (wpas_ctrl_nan_transmit(wpa_s, buf + 13) < 0)
+			reply_len = -1;
+#endif /* CONFIG_NAN_USD */
 #ifdef CONFIG_PASN
 	} else if (os_strncmp(buf, "PASN_START ", 11) == 0) {
 		if (wpas_ctrl_iface_pasn_start(wpa_s, buf + 11) < 0)
@@ -13128,7 +13518,16 @@
 	} else if (os_strncmp(buf, "PASN_DEAUTH ", 12) == 0) {
 		if (wpas_ctrl_iface_pasn_deauthenticate(wpa_s, buf + 12) < 0)
 			reply_len = -1;
+#ifdef CONFIG_TESTING_OPTIONS
+	} else if (os_strncmp(buf, "PASN_DRIVER ", 12) == 0) {
+		if (wpas_ctrl_iface_pasn_driver(wpa_s, buf + 12) < 0)
+			reply_len = -1;
+#endif /* CONFIG_TESTING_OPTIONS */
 #endif /* CONFIG_PASN */
+#ifndef CONFIG_NO_ROBUST_AV
+	} else if (os_strncmp(buf, "MSCS ", 5) == 0) {
+		if (wpas_ctrl_iface_configure_mscs(wpa_s, buf + 5))
+			reply_len = -1;
 	} else if (os_strncmp(buf, "SCS ", 4) == 0) {
 		if (wpas_ctrl_iface_configure_scs(wpa_s, buf + 4))
 			reply_len = -1;
@@ -13138,6 +13537,7 @@
 	} else if (os_strncmp(buf, "DSCP_QUERY ", 11) == 0) {
 		if (wpas_ctrl_iface_send_dscp_query(wpa_s, buf + 11))
 			reply_len = -1;
+#endif /* CONFIG_NO_ROBUST_AV */
 	} else if (os_strcmp(buf, "MLO_STATUS") == 0) {
 		reply_len = wpas_ctrl_iface_mlo_status(wpa_s, reply,
 						       reply_size);
diff --git a/wpa_supplicant/dbus/dbus_new.c b/wpa_supplicant/dbus/dbus_new.c
index 8fc29b3..00b38ed 100644
--- a/wpa_supplicant/dbus/dbus_new.c
+++ b/wpa_supplicant/dbus/dbus_new.c
@@ -4304,6 +4304,14 @@
 	  }
 	},
 #endif /* CONFIG_INTERWORKING */
+#ifdef CONFIG_HS20
+	{ "HS20TermsAndConditions", WPAS_DBUS_NEW_IFACE_INTERFACE,
+	  {
+		  { "url", "s", ARG_OUT },
+		  END_ARGS
+	  }
+	},
+#endif /* CONFIG_HS20 */
 	{ NULL, NULL, { END_ARGS } }
 };
 
@@ -5142,3 +5150,39 @@
 }
 
 #endif /* CONFIG_P2P */
+
+
+#ifdef CONFIG_HS20
+/**
+ * wpas_dbus_signal_hs20_t_c_acceptance - Signals a terms and conditions was
+ * received.
+ *
+ * @wpa_s: %wpa_supplicant network interface data
+ * @url: URL of the terms and conditions acceptance page.
+ */
+void wpas_dbus_signal_hs20_t_c_acceptance(struct wpa_supplicant *wpa_s,
+					  const char *url)
+{
+	struct wpas_dbus_priv *iface;
+	DBusMessage *msg;
+
+	iface = wpa_s->global->dbus;
+
+	/* Do nothing if the control interface is not turned on */
+	if (!iface || !wpa_s->dbus_new_path)
+		return;
+
+	msg = dbus_message_new_signal(wpa_s->dbus_new_path,
+				      WPAS_DBUS_NEW_IFACE_INTERFACE,
+				      "HS20TermsAndConditions");
+	if (!msg)
+		return;
+
+	if (dbus_message_append_args(msg, DBUS_TYPE_STRING, &url,
+				     DBUS_TYPE_INVALID))
+		dbus_connection_send(iface->con, msg, NULL);
+	else
+		wpa_printf(MSG_ERROR, "dbus: Failed to construct signal");
+	dbus_message_unref(msg);
+}
+#endif /* CONFIG_HS20 */
diff --git a/wpa_supplicant/dbus/dbus_new.h b/wpa_supplicant/dbus/dbus_new.h
index 5c5d855..b653f10 100644
--- a/wpa_supplicant/dbus/dbus_new.h
+++ b/wpa_supplicant/dbus/dbus_new.h
@@ -279,6 +279,8 @@
 					    int bh, int bss_load,
 					    int conn_capab);
 void wpas_dbus_signal_interworking_select_done(struct wpa_supplicant *wpa_s);
+void wpas_dbus_signal_hs20_t_c_acceptance(struct wpa_supplicant *wpa_s,
+					  const char *url);
 
 #else /* CONFIG_CTRL_IFACE_DBUS_NEW */
 
@@ -650,6 +652,12 @@
 {
 }
 
+static inline
+void wpas_dbus_signal_hs20_t_c_acceptance(struct wpa_supplicant *wpa_s,
+					  const char *url)
+{
+}
+
 #endif /* CONFIG_CTRL_IFACE_DBUS_NEW */
 
 #endif /* CTRL_IFACE_DBUS_H_NEW */
diff --git a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c
index a178d87..16b2caa 100644
--- a/wpa_supplicant/dbus/dbus_new_handlers_p2p.c
+++ b/wpa_supplicant/dbus/dbus_new_handlers_p2p.c
@@ -14,6 +14,7 @@
 #include "../wpa_supplicant_i.h"
 #include "../wps_supplicant.h"
 #include "../notify.h"
+#include "../bss.h"
 #include "dbus_new_helpers.h"
 #include "dbus_new.h"
 #include "dbus_new_handlers.h"
@@ -360,6 +361,12 @@
 	unsigned int group_id = 0;
 	struct wpa_ssid *ssid;
 	u8 go_bssid_buf[ETH_ALEN], *go_bssid = NULL;
+	bool allow_6ghz = false;
+	int vht = wpa_s->conf->p2p_go_vht;
+	int ht40 = wpa_s->conf->p2p_go_ht40 || vht;
+	int he = wpa_s->conf->p2p_go_he;
+	int edmg = wpa_s->conf->p2p_go_edmg;
+	int max_oper_chwidth, chwidth = 0, freq2 = 0;
 
 	dbus_message_iter_init(message, &iter);
 
@@ -392,6 +399,28 @@
 			if (hwaddr_aton(entry.str_value, go_bssid_buf))
 				goto inv_args_clear;
 			go_bssid = go_bssid_buf;
+		} else if (os_strcmp(entry.key, "ht40") == 0 &&
+			   entry.type == DBUS_TYPE_BOOLEAN) {
+			ht40 = entry.bool_value;
+		} else if (os_strcmp(entry.key, "vht") == 0 &&
+			   entry.type == DBUS_TYPE_BOOLEAN) {
+			vht = entry.bool_value;
+			ht40 |= vht;
+		} else if (os_strcmp(entry.key, "he") == 0 &&
+			   entry.type == DBUS_TYPE_BOOLEAN) {
+			he = entry.bool_value;
+		} else if (os_strcmp(entry.key, "edmg") == 0 &&
+			   entry.type == DBUS_TYPE_BOOLEAN) {
+			edmg = entry.bool_value;
+		} else if (os_strcmp(entry.key, "allow_6ghz") == 0 &&
+			   entry.type == DBUS_TYPE_BOOLEAN) {
+			allow_6ghz = entry.bool_value;
+		} else if (os_strcmp(entry.key, "freq2") == 0 &&
+			   entry.type == DBUS_TYPE_INT32) {
+			freq2 = entry.int32_value;
+		} else if (os_strcmp(entry.key, "max_oper_chwidth") == 0 &&
+			   entry.type == DBUS_TYPE_INT32) {
+			chwidth = entry.int32_value;
 		} else {
 			goto inv_args_clear;
 		}
@@ -399,6 +428,13 @@
 		wpa_dbus_dict_entry_clear(&entry);
 	}
 
+	max_oper_chwidth = chwidth_freq2_to_ch_width(chwidth, freq2);
+	if (max_oper_chwidth < 0)
+		goto inv_args;
+
+	if (allow_6ghz && chwidth == 40)
+		max_oper_chwidth = CONF_OPER_CHWIDTH_40MHZ_6GHZ;
+
 	wpa_s = wpa_s->global->p2p_init_wpa_s;
 	if (!wpa_s) {
 		reply = wpas_dbus_error_no_p2p_mgmt_iface(message);
@@ -437,17 +473,19 @@
 		if (ssid == NULL || ssid->disabled != 2)
 			goto inv_args;
 
-		if (wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0, 0, 0,
-						  0, 0, 0, 0, NULL, 0, 0,
-						  false, retry_limit,
-						  go_bssid)) {
+		if (wpas_p2p_group_add_persistent(wpa_s, ssid, 0, freq, 0,
+						  freq2, ht40, vht,
+						  max_oper_chwidth, he, edmg,
+						  NULL, 0, 0, allow_6ghz,
+						  retry_limit, go_bssid)) {
 			reply = wpas_dbus_error_unknown_error(
 				message,
 				"Failed to reinvoke a persistent group");
 			goto out;
 		}
-	} else if (wpas_p2p_group_add(wpa_s, persistent_group, freq, 0, 0, 0,
-				      0, 0, 0, false))
+	} else if (wpas_p2p_group_add(wpa_s, persistent_group, freq, freq2,
+				      ht40, vht, max_oper_chwidth, he, edmg,
+				      allow_6ghz))
 		goto inv_args;
 
 out:
@@ -2427,9 +2465,9 @@
 	u8 *p_bssid;
 
 	if (role == WPAS_P2P_ROLE_CLIENT) {
-		if (wpa_s->current_ssid == NULL)
+		if (!wpa_s->current_bss)
 			return FALSE;
-		p_bssid = wpa_s->current_ssid->bssid;
+		p_bssid = wpa_s->current_bss->bssid;
 	} else {
 		if (wpa_s->ap_iface == NULL)
 			return FALSE;
@@ -2451,9 +2489,9 @@
 	u8 role = wpas_get_p2p_role(wpa_s);
 
 	if (role == WPAS_P2P_ROLE_CLIENT) {
-		if (wpa_s->go_params == NULL)
+		if (!wpa_s->current_bss)
 			return FALSE;
-		op_freq = wpa_s->go_params->freq;
+		op_freq = wpa_s->current_bss->freq;
 	} else {
 		if (wpa_s->ap_iface == NULL)
 			return FALSE;
diff --git a/wpa_supplicant/dbus/dbus_new_helpers.c b/wpa_supplicant/dbus/dbus_new_helpers.c
index 7fb0669..28f1aa4 100644
--- a/wpa_supplicant/dbus/dbus_new_helpers.c
+++ b/wpa_supplicant/dbus/dbus_new_helpers.c
@@ -671,20 +671,27 @@
 					    &interface) ||
 	    /* Changed properties dict */
 	    !dbus_message_iter_open_container(&signal_iter, DBUS_TYPE_ARRAY,
-					      "{sv}", &dict_iter) ||
-	    !put_changed_properties(obj_dsc, interface, &dict_iter, 0) ||
-	    !dbus_message_iter_close_container(&signal_iter, &dict_iter) ||
+					      "{sv}", &dict_iter))
+		goto fail;
+	if (!put_changed_properties(obj_dsc, interface, &dict_iter, 0)) {
+		dbus_message_iter_close_container(&signal_iter, &dict_iter);
+		goto fail;
+	}
+	if (!dbus_message_iter_close_container(&signal_iter, &dict_iter) ||
 	    /* Invalidated properties array (empty) */
 	    !dbus_message_iter_open_container(&signal_iter, DBUS_TYPE_ARRAY,
 					      "s", &dict_iter) ||
-	    !dbus_message_iter_close_container(&signal_iter, &dict_iter)) {
-		wpa_printf(MSG_DEBUG, "dbus: %s: Failed to construct signal",
-			   __func__);
-	} else {
-		dbus_connection_send(con, msg, NULL);
-	}
+	    !dbus_message_iter_close_container(&signal_iter, &dict_iter))
+		goto fail;
 
+	dbus_connection_send(con, msg, NULL);
+
+out:
 	dbus_message_unref(msg);
+	return;
+fail:
+	wpa_printf(MSG_DEBUG, "dbus: %s: Failed to construct signal", __func__);
+	goto out;
 }
 
 
@@ -702,16 +709,23 @@
 	dbus_message_iter_init_append(msg, &signal_iter);
 
 	if (!dbus_message_iter_open_container(&signal_iter, DBUS_TYPE_ARRAY,
-					      "{sv}", &dict_iter) ||
-	    !put_changed_properties(obj_dsc, interface, &dict_iter, 1) ||
-	    !dbus_message_iter_close_container(&signal_iter, &dict_iter)) {
-		wpa_printf(MSG_DEBUG, "dbus: %s: Failed to construct signal",
-			   __func__);
-	} else {
-		dbus_connection_send(con, msg, NULL);
+					      "{sv}", &dict_iter))
+		goto fail;
+	if (!put_changed_properties(obj_dsc, interface, &dict_iter, 1)) {
+		dbus_message_iter_close_container(&signal_iter, &dict_iter);
+		goto fail;
 	}
+	if (!dbus_message_iter_close_container(&signal_iter, &dict_iter))
+		goto fail;
 
+	dbus_connection_send(con, msg, NULL);
+
+out:
 	dbus_message_unref(msg);
+	return;
+fail:
+	wpa_printf(MSG_DEBUG, "dbus: %s: Failed to construct signal", __func__);
+	goto out;
 }
 
 
diff --git a/wpa_supplicant/dbus/dbus_new_introspect.c b/wpa_supplicant/dbus/dbus_new_introspect.c
index 6c721bf..a8c0d28 100644
--- a/wpa_supplicant/dbus/dbus_new_introspect.c
+++ b/wpa_supplicant/dbus/dbus_new_introspect.c
@@ -38,7 +38,7 @@
 	if (!iface)
 		return NULL;
 	iface->dbus_interface = os_strdup(dbus_interface);
-	iface->xml = wpabuf_alloc(15000);
+	iface->xml = wpabuf_alloc(16000);
 	if (iface->dbus_interface == NULL || iface->xml == NULL) {
 		os_free(iface->dbus_interface);
 		wpabuf_free(iface->xml);
diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig
index 1ae5a9f..c0192e0 100644
--- a/wpa_supplicant/defconfig
+++ b/wpa_supplicant/defconfig
@@ -677,3 +677,17 @@
 # production use.
 #CONFIG_PASN=y
 
+# Disable support for Radio Measurement (IEEE 802.11k) and supported operating
+# class indication. Removing these is not recommended since they can help the
+# AP manage the network and STA steering.
+#CONFIG_NO_RRM=y
+
+# Disable support for Robust AV streaming for consumer and enterprise Wi-Fi
+# applications; IEEE Std 802.11-2020, 4.3.24; SCS, MSCS, QoS Management
+#CONFIG_NO_ROBUST_AV=y
+
+# Disable support for WMM admission control
+#CONFIG_NO_WMM_AC=y
+
+# Wi-Fi Aware unsynchronized service discovery (NAN USD)
+#CONFIG_NAN_USD=y
diff --git a/wpa_supplicant/dpp_supplicant.c b/wpa_supplicant/dpp_supplicant.c
index 4f5be0a..801e698 100644
--- a/wpa_supplicant/dpp_supplicant.c
+++ b/wpa_supplicant/dpp_supplicant.c
@@ -1800,7 +1800,7 @@
 	wpa_s->dpp_gas_dialog_token = -1;
 
 	if (!auth || (!auth->auth_success && !auth->reconfig_success) ||
-	    os_memcmp(addr, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(addr, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress");
 		return;
 	}
@@ -1935,7 +1935,11 @@
 	offchannel_send_action_done(wpa_s);
 	wpas_dpp_listen_stop(wpa_s);
 
+#ifdef CONFIG_NO_RRM
+	supp_op_classes = NULL;
+#else /* CONFIG_NO_RRM */
 	supp_op_classes = wpas_supp_op_classes(wpa_s);
+#endif /* CONFIG_NO_RRM */
 	buf = dpp_build_conf_req_helper(auth, wpa_s->conf->dpp_name,
 					wpa_s->dpp_netrole,
 					wpa_s->conf->dpp_mud_url,
@@ -2016,7 +2020,7 @@
 	}
 
 	if (!is_zero_ether_addr(auth->peer_mac_addr) &&
-	    os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2071,7 +2075,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2175,7 +2179,7 @@
 
 	if (!auth || !auth->waiting_conf_result) {
 		if (auth &&
-		    os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) == 0 &&
+		    ether_addr_equal(src, auth->peer_mac_addr) &&
 		    gas_server_response_sent(wpa_s->gas_server,
 					     auth->gas_server_ctx)) {
 			/* This could happen if the TX status event gets delayed
@@ -2192,7 +2196,7 @@
 		}
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2608,7 +2612,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2652,7 +2656,7 @@
 		return;
 	}
 
-	if (os_memcmp(src, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(src, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: MAC address mismatch (expected "
 			   MACSTR ") - drop", MAC2STR(auth->peer_mac_addr));
 		return;
@@ -2690,7 +2694,7 @@
 	wpa_printf(MSG_DEBUG, "DPP: Peer Discovery Response from " MACSTR,
 		   MAC2STR(src));
 	if (is_zero_ether_addr(wpa_s->dpp_intro_bssid) ||
-	    os_memcmp(src, wpa_s->dpp_intro_bssid, ETH_ALEN) != 0) {
+	    !ether_addr_equal(src, wpa_s->dpp_intro_bssid)) {
 		wpa_printf(MSG_DEBUG, "DPP: Not waiting for response from "
 			   MACSTR " - drop", MAC2STR(src));
 		return;
@@ -3854,7 +3858,7 @@
 	wpa_printf(MSG_DEBUG, "DPP: Private Peer Introduction Notify from "
 		   MACSTR, MAC2STR(src));
 	if (is_zero_ether_addr(wpa_s->dpp_intro_bssid) ||
-	    os_memcmp(src, wpa_s->dpp_intro_bssid, ETH_ALEN) != 0) {
+	    !ether_addr_equal(src, wpa_s->dpp_intro_bssid)) {
 		wpa_printf(MSG_DEBUG, "DPP: Not waiting for response from "
 			   MACSTR " - drop", MAC2STR(src));
 		return;
@@ -4145,7 +4149,7 @@
 	wpa_printf(MSG_DEBUG, "DPP: GAS request from " MACSTR,
 		   MAC2STR(sa));
 	if (!auth || (!auth->auth_success && !auth->reconfig_success) ||
-	    os_memcmp(sa, auth->peer_mac_addr, ETH_ALEN) != 0) {
+	    !ether_addr_equal(sa, auth->peer_mac_addr)) {
 		wpa_printf(MSG_DEBUG, "DPP: No matching exchange in progress");
 		return NULL;
 	}
@@ -4159,6 +4163,13 @@
 		 * exchange. */
 		dpp_notify_auth_success(auth, 1);
 		wpa_s->dpp_auth_ok_on_ack = 0;
+#ifdef CONFIG_TESTING_OPTIONS
+		if (dpp_test == DPP_TEST_STOP_AT_AUTH_CONF) {
+			wpa_printf(MSG_INFO,
+				   "DPP: TESTING - stop at Authentication Confirm");
+			return NULL;
+		}
+#endif /* CONFIG_TESTING_OPTIONS */
 	}
 
 	wpa_hexdump(MSG_DEBUG,
@@ -5692,6 +5703,8 @@
 	if (wpa_s->dpp_pb_bi) {
 		char id[20];
 
+		if (wpa_s->dpp_pb_bi == wpa_s->dpp_pkex_bi)
+			wpa_s->dpp_pkex_bi = NULL;
 		os_snprintf(id, sizeof(id), "%u", wpa_s->dpp_pb_bi->id);
 		dpp_bootstrap_remove(wpa_s->dpp, id);
 		wpa_s->dpp_pb_bi = NULL;
diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h
index dcf5764..9a4c235 100644
--- a/wpa_supplicant/driver_i.h
+++ b/wpa_supplicant/driver_i.h
@@ -1155,8 +1155,10 @@
 {
 	struct secure_ranging_params params;
 
+	/* Configure secure ranging context only to the drivers that support it.
+	 */
 	if (!wpa_s->driver->set_secure_ranging_ctx)
-		return -1;
+		return 0;
 
 	os_memset(&params, 0, sizeof(params));
 	params.action = action;
diff --git a/wpa_supplicant/events.c b/wpa_supplicant/events.c
index f386119..bc45579 100644
--- a/wpa_supplicant/events.c
+++ b/wpa_supplicant/events.c
@@ -50,6 +50,7 @@
 #include "mesh.h"
 #include "mesh_mpm.h"
 #include "wmm_ac.h"
+#include "nan_usd.h"
 #include "dpp_supplicant.h"
 #include "rsn_supp/wpa_i.h"
 
@@ -383,8 +384,13 @@
 	wpa_s->key_mgmt = 0;
 	wpa_s->allowed_key_mgmts = 0;
 
+#ifndef CONFIG_NO_RRM
 	wpas_rrm_reset(wpa_s);
+#endif /* CONFIG_NO_RRM */
 	wpa_s->wnmsleep_used = 0;
+#ifdef CONFIG_WNM
+	wpa_s->wnm_mode = 0;
+#endif /* CONFIG_WNM */
 	wnm_clear_coloc_intf_reporting(wpa_s);
 	wpa_s->disable_mbo_oce = 0;
 
@@ -759,15 +765,33 @@
 		return 0;
 	}
 
+	wpa_ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE);
+
 	if (wpas_get_ssid_pmf(wpa_s, ssid) == MGMT_FRAME_PROTECTION_REQUIRED &&
 	    (!(ssid->key_mgmt & WPA_KEY_MGMT_OWE) || ssid->owe_only)) {
+#ifdef CONFIG_OWE
+		if ((ssid->key_mgmt & WPA_KEY_MGMT_OWE) && ssid->owe_only &&
+		    !wpa_ie && !rsn_ie &&
+		    wpa_s->owe_transition_select &&
+		    wpa_bss_get_vendor_ie(bss, OWE_IE_VENDOR_TYPE) &&
+		    ssid->owe_transition_bss_select_count + 1 <=
+		    MAX_OWE_TRANSITION_BSS_SELECT_COUNT) {
+			ssid->owe_transition_bss_select_count++;
+			if (debug_print)
+				wpa_dbg(wpa_s, MSG_DEBUG,
+					"   skip OWE open BSS (selection count %d does not exceed %d)",
+					ssid->owe_transition_bss_select_count,
+					MAX_OWE_TRANSITION_BSS_SELECT_COUNT);
+			wpa_s->owe_transition_search = 1;
+			return 0;
+		}
+#endif /* CONFIG_OWE */
 		if (debug_print)
 			wpa_dbg(wpa_s, MSG_DEBUG,
 				"   skip - MFP Required but network not MFP Capable");
 		return 0;
 	}
 
-	wpa_ie = wpa_bss_get_vendor_ie(bss, WPA_IE_VENDOR_TYPE);
 	while ((ssid->proto & WPA_PROTO_WPA) && wpa_ie) {
 		proto_match++;
 
@@ -1136,7 +1160,7 @@
 {
 	u16 removed_links;
 
-	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL))
+	if (wpa_bss_parse_basic_ml_element(wpa_s, bss, NULL, NULL, NULL, NULL))
 		return true;
 
 	if (bss->n_mld_links == 0)
@@ -1285,7 +1309,7 @@
 #endif /* CONFIG_WPS */
 
 	if (ssid->bssid_set && ssid->ssid_len == 0 &&
-	    os_memcmp(bss->bssid, ssid->bssid, ETH_ALEN) == 0)
+	    ether_addr_equal(bss->bssid, ssid->bssid))
 		check_ssid = false;
 
 	if (check_ssid &&
@@ -1297,7 +1321,7 @@
 	}
 
 	if (ssid->bssid_set &&
-	    os_memcmp(bss->bssid, ssid->bssid, ETH_ALEN) != 0) {
+	    !ether_addr_equal(bss->bssid, ssid->bssid)) {
 		if (debug_print)
 			wpa_dbg(wpa_s, MSG_DEBUG, "   skip - BSSID mismatch");
 		return false;
@@ -1466,7 +1490,7 @@
 		}
 
 		if (p2p_parse_dev_addr_in_p2p_ie(p2p_ie, dev_addr) < 0 ||
-		    os_memcmp(dev_addr, ssid->go_p2p_dev_addr, ETH_ALEN) != 0) {
+		    !ether_addr_equal(dev_addr, ssid->go_p2p_dev_addr)) {
 			if (debug_print)
 				wpa_dbg(wpa_s, MSG_DEBUG,
 					"   skip - no matching GO P2P Device Address in P2P element");
@@ -1673,6 +1697,14 @@
 		return NULL;
 	}
 
+#ifdef CONFIG_WNM
+	if (wnm_is_bss_excluded(wpa_s, bss)) {
+		if (debug_print)
+			wpa_dbg(wpa_s, MSG_DEBUG, "   skip - BSSID excluded");
+		return NULL;
+	}
+#endif /* CONFIG_WNM */
+
 	for (ssid = group; ssid; ssid = only_first_ssid ? NULL : ssid->pnext) {
 		if (wpa_scan_res_ok(wpa_s, ssid, match_ssid, match_ssid_len,
 				    bss, bssid_ignore_count, debug_print))
@@ -1857,14 +1889,16 @@
 {
 	int *freqs;
 	u16 missing_links = 0, removed_links;
+	u8 ap_mld_id;
 
 	if (!((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
 	      (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)))
 		return 0;
 
-	/* Try to resolve any missing link information */
 	if (wpa_bss_parse_basic_ml_element(wpa_s, selected, NULL,
-					   &missing_links) || !missing_links)
+					   &missing_links, ssid,
+					   &ap_mld_id) ||
+	    !missing_links)
 		return 0;
 
 	removed_links = wpa_bss_parse_reconf_ml_element(wpa_s, selected);
@@ -1895,7 +1929,36 @@
 	wpa_s->manual_scan_freqs = freqs;
 
 	os_memcpy(wpa_s->ml_probe_bssid, selected->bssid, ETH_ALEN);
-	wpa_s->ml_probe_mld_id = -1;
+
+	/*
+	 * In case the ML probe request is intended to retrieve information from
+	 * the transmitted BSS, the AP MLD ID should be included and should be
+	 * set to zero.
+	 * In case the ML probe requested is intended to retrieve information
+	 * from a non-transmitted BSS, the AP MLD ID should not be included.
+	 */
+	if (ap_mld_id)
+		wpa_s->ml_probe_mld_id = -1;
+	else
+		wpa_s->ml_probe_mld_id = 0;
+
+	if (ssid && ssid->ssid_len) {
+		os_free(wpa_s->ssids_from_scan_req);
+		wpa_s->num_ssids_from_scan_req = 0;
+
+		wpa_s->ssids_from_scan_req =
+			os_zalloc(sizeof(struct wpa_ssid_value));
+		if (wpa_s->ssids_from_scan_req) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: ML probe: With direct SSID");
+
+			wpa_s->num_ssids_from_scan_req = 1;
+			wpa_s->ssids_from_scan_req[0].ssid_len = ssid->ssid_len;
+			os_memcpy(wpa_s->ssids_from_scan_req[0].ssid,
+				  ssid->ssid, ssid->ssid_len);
+		}
+	}
+
 	wpa_s->ml_probe_links = missing_links;
 
 	wpa_s->normal_scans = 0;
@@ -1952,12 +2015,11 @@
 	 * the selected BSSID, do not trigger new attempt.
 	 */
 	if (wpa_s->reassociate ||
-	    (os_memcmp(selected->bssid, wpa_s->bssid, ETH_ALEN) != 0 &&
+	    (!ether_addr_equal(selected->bssid, wpa_s->bssid) &&
 	     ((wpa_s->wpa_state != WPA_ASSOCIATING &&
 	       wpa_s->wpa_state != WPA_AUTHENTICATING) ||
 	      (!is_zero_ether_addr(wpa_s->pending_bssid) &&
-	       os_memcmp(selected->bssid, wpa_s->pending_bssid, ETH_ALEN) !=
-	       0) ||
+	       !ether_addr_equal(selected->bssid, wpa_s->pending_bssid)) ||
 	      (is_zero_ether_addr(wpa_s->pending_bssid) &&
 	       ssid != wpa_s->current_ssid)))) {
 		if (wpa_supplicant_scard_init(wpa_s, ssid)) {
@@ -2070,11 +2132,22 @@
 }
 
 
+static int wpas_evaluate_band_score(int frequency)
+{
+	if (is_6ghz_freq(frequency))
+		return 2;
+	if (IS_5GHZ(frequency))
+		return 1;
+	return 0;
+}
+
+
 int wpa_supplicant_need_to_roam_within_ess(struct wpa_supplicant *wpa_s,
 					   struct wpa_bss *current_bss,
 					   struct wpa_bss *selected)
 {
 	int min_diff, diff;
+	int cur_band_score, sel_band_score;
 	int to_5ghz, to_6ghz;
 	int cur_level, sel_level;
 	unsigned int cur_est, sel_est;
@@ -2100,8 +2173,7 @@
 		selected->snr, selected->est_throughput);
 
 	if (wpa_s->current_ssid->bssid_set &&
-	    os_memcmp(selected->bssid, wpa_s->current_ssid->bssid, ETH_ALEN) ==
-	    0) {
+	    ether_addr_equal(selected->bssid, wpa_s->current_ssid->bssid)) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "Allow reassociation - selected BSS "
 			"has preferred BSSID");
 		return 1;
@@ -2221,9 +2293,11 @@
 	else if (sel_est > cur_est)
 		min_diff--;
 
-	if (to_5ghz)
-		min_diff -= 2;
-	if (to_6ghz)
+	cur_band_score = wpas_evaluate_band_score(current_bss->freq);
+	sel_band_score = wpas_evaluate_band_score(selected->freq);
+	min_diff += (cur_band_score - sel_band_score) * 2;
+	if (wpa_s->signal_threshold && cur_level <= wpa_s->signal_threshold &&
+	    sel_level > wpa_s->signal_threshold)
 		min_diff -= 2;
 	diff = sel_level - cur_level;
 	if (diff < min_diff) {
@@ -2256,6 +2330,7 @@
 				       struct wpa_ssid *ssid)
 {
 	struct wpa_bss *current_bss = NULL;
+	const u8 *bssid;
 
 	if (wpa_s->reassociate)
 		return 1; /* explicit request to reassociate */
@@ -2269,12 +2344,17 @@
 	if (wpas_driver_bss_selection(wpa_s))
 		return 0; /* Driver-based roaming */
 
+	if (wpa_s->valid_links)
+		bssid = wpa_s->links[wpa_s->mlo_assoc_link_id].bssid;
+	else
+		bssid = wpa_s->bssid;
+
 	if (wpa_s->current_ssid->ssid)
-		current_bss = wpa_bss_get(wpa_s, wpa_s->bssid,
+		current_bss = wpa_bss_get(wpa_s, bssid,
 					  wpa_s->current_ssid->ssid,
 					  wpa_s->current_ssid->ssid_len);
 	if (!current_bss)
-		current_bss = wpa_bss_get_bssid(wpa_s, wpa_s->bssid);
+		current_bss = wpa_bss_get_bssid(wpa_s, bssid);
 
 	if (!current_bss)
 		return 1; /* current BSS not seen in scan results */
@@ -2424,9 +2504,11 @@
 	if (sme_proc_obss_scan(wpa_s) > 0)
 		goto scan_work_done;
 
+#ifndef CONFIG_NO_RRM
 	if (own_request && data &&
 	    wpas_beacon_rep_scan_process(wpa_s, scan_res, &data->scan_info) > 0)
 		goto scan_work_done;
+#endif /* CONFIG_NO_RRM */
 
 	if (ml_link_probe_scan(wpa_s))
 		goto scan_work_done;
@@ -3586,10 +3668,12 @@
 			     data->assoc_info.resp_ies_len);
 #endif /* CONFIG_IEEE80211R */
 
+#ifndef CONFIG_NO_ROBUST_AV
 	if (bssid_known)
 		wpas_handle_assoc_resp_mscs(wpa_s, bssid,
 					    data->assoc_info.resp_ies,
 					    data->assoc_info.resp_ies_len);
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	/* WPA/RSN IE from Beacon/ProbeResp */
 	p = data->assoc_info.beacon_ies;
@@ -3644,8 +3728,10 @@
 
 	wpa_s->assoc_freq = data->assoc_info.freq;
 
+#ifndef CONFIG_NO_ROBUST_AV
 	wpas_handle_assoc_resp_qos_mgmt(wpa_s, data->assoc_info.resp_ies,
 					data->assoc_info.resp_ies_len);
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	return 0;
 }
@@ -3717,6 +3803,261 @@
 }
 
 
+static unsigned int wpas_ml_parse_assoc(struct wpa_supplicant *wpa_s,
+					struct ieee802_11_elems *elems,
+					struct ml_sta_link_info *ml_info)
+{
+	struct wpabuf *mlbuf;
+	struct ieee80211_eht_ml *ml;
+	size_t ml_len;
+	struct eht_ml_basic_common_info *common_info;
+	const u8 *pos;
+	u16 eml_capa = 0, mld_capa = 0;
+	const u16 control =
+		host_to_le16(MULTI_LINK_CONTROL_TYPE_BASIC |
+			     BASIC_MULTI_LINK_CTRL_PRES_LINK_ID |
+			     BASIC_MULTI_LINK_CTRL_PRES_BSS_PARAM_CH_COUNT);
+	u8 expected_common_info_len = 9;
+	unsigned int i = 0;
+	u16 ml_control;
+
+	if (!wpa_s->valid_links || !elems->basic_mle || !elems->basic_mle_len)
+		return 0;
+
+	mlbuf = ieee802_11_defrag(elems->basic_mle, elems->basic_mle_len, true);
+	if (!mlbuf)
+		return 0;
+
+	ml = (struct ieee80211_eht_ml *) wpabuf_head(mlbuf);
+	ml_len = wpabuf_len(mlbuf);
+	if (ml_len < sizeof(*ml))
+		goto out;
+
+	os_memset(ml_info, 0, sizeof(*ml_info) * MAX_NUM_MLD_LINKS);
+
+	ml_control = le_to_host16(ml->ml_control);
+
+	if ((ml_control & control) != control) {
+		wpa_printf(MSG_DEBUG, "MLD: Invalid presence BM=0x%x",
+			   ml_control);
+		goto out;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		wpa_printf(MSG_DEBUG, "MLD: EML capabilities included");
+		expected_common_info_len += 2;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) {
+		wpa_printf(MSG_DEBUG, "MLD: MLD capabilities included");
+		expected_common_info_len += 2;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Unexpected: medium sync delay info present");
+		expected_common_info_len += 2;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_AP_MLD_ID) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Unexpected: MLD ID present");
+		expected_common_info_len++;
+	}
+
+	if (sizeof(*ml) + expected_common_info_len > ml_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Not enough bytes for common info. ml_len=%zu",
+			   ml_len);
+		goto out;
+	}
+
+	common_info = (struct eht_ml_basic_common_info *) ml->variable;
+	if (common_info->len != expected_common_info_len) {
+		wpa_printf(MSG_DEBUG,
+			   "MLD: Invalid common info len=%u. expected=%u",
+			   common_info->len, expected_common_info_len);
+		goto out;
+	}
+
+	wpa_printf(MSG_DEBUG, "MLD: address: " MACSTR,
+		   MAC2STR(common_info->mld_addr));
+
+	if (!ether_addr_equal(wpa_s->ap_mld_addr, common_info->mld_addr)) {
+		wpa_printf(MSG_DEBUG, "MLD: Mismatching MLD address (expected "
+			   MACSTR ")", MAC2STR(wpa_s->ap_mld_addr));
+		goto out;
+	}
+
+	pos = common_info->variable;
+
+	/* Store the information for the association link */
+	ml_info[i].link_id = *pos;
+	pos++;
+
+	/* Skip the BSS Parameters Change Count */
+	pos++;
+
+	/* Skip the Medium Synchronization Delay Information if present  */
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MSD_INFO)
+		pos += 2;
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_EML_CAPA) {
+		eml_capa = WPA_GET_LE16(pos);
+		pos += 2;
+	}
+
+	if (ml_control & BASIC_MULTI_LINK_CTRL_PRES_MLD_CAPA) {
+		mld_capa = WPA_GET_LE16(pos);
+		pos += 2;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: link_id=%u, eml=0x%x, mld=0x%x",
+		   ml_info[i].link_id, eml_capa, mld_capa);
+
+	i++;
+
+	pos = ((u8 *) common_info) + common_info->len;
+	ml_len -= sizeof(*ml) + common_info->len;
+	while (ml_len > 2 && i < MAX_NUM_MLD_LINKS) {
+		u8 sub_elem_len = pos[1];
+		u8 sta_info_len;
+		u8 nstr_bitmap_len = 0;
+		u16 ctrl;
+		const u8 *end;
+
+		wpa_printf(MSG_DEBUG, "MLD: Subelement len=%u", sub_elem_len);
+
+		if (sub_elem_len > ml_len - 2) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Invalid link info len: %u > %zu",
+				   2 + sub_elem_len, ml_len);
+			goto out;
+		}
+
+		switch (*pos) {
+		case EHT_ML_SUB_ELEM_PER_STA_PROFILE:
+			break;
+		case EHT_ML_SUB_ELEM_FRAGMENT:
+		case EHT_ML_SUB_ELEM_VENDOR:
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Skip subelement id=%u, len=%u",
+				   *pos, sub_elem_len);
+			pos += 2 + sub_elem_len;
+			ml_len -= 2 + sub_elem_len;
+			continue;
+		default:
+			wpa_printf(MSG_DEBUG, "MLD: Unknown subelement ID=%u",
+				   *pos);
+			goto out;
+		}
+
+		end = pos + 2 + sub_elem_len;
+
+		/* Skip the subelement ID and the length */
+		pos += 2;
+		ml_len -= 2;
+
+		if (end - pos < 2)
+			goto out;
+
+		/* Get the station control field */
+		ctrl = WPA_GET_LE16(pos);
+
+		pos += 2;
+		ml_len -= 2;
+
+		if (!(ctrl & EHT_PER_STA_CTRL_COMPLETE_PROFILE_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per STA complete profile expected");
+			goto out;
+		}
+
+		if (!(ctrl & EHT_PER_STA_CTRL_MAC_ADDR_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per STA MAC address not present");
+			goto out;
+		}
+
+		if (!(ctrl & EHT_PER_STA_CTRL_TSF_OFFSET_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Per STA TSF offset not present");
+			goto out;
+		}
+
+		if (!(ctrl & EHT_PER_STA_CTRL_BEACON_INTERVAL_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Beacon interval not present");
+			goto out;
+		}
+
+		if (!(ctrl & EHT_PER_STA_CTRL_DTIM_INFO_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD:  DTIM information not present");
+			goto out;
+		}
+
+		if (ctrl & EHT_PER_STA_CTRL_NSTR_LINK_PAIR_PRESENT_MSK) {
+			if (ctrl & EHT_PER_STA_CTRL_NSTR_BM_SIZE_MSK)
+				nstr_bitmap_len = 2;
+			else
+				nstr_bitmap_len = 1;
+		}
+
+		if (!(ctrl & EHT_PER_STA_CTRL_BSS_PARAM_CNT_PRESENT_MSK)) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD:  BSS params change count not present");
+			goto out;
+		}
+
+		sta_info_len = 1 + ETH_ALEN + 8 + 2 + 2 + 1 + nstr_bitmap_len;
+		if (sta_info_len > ml_len || sta_info_len > end - pos ||
+		    sta_info_len + 2 > sub_elem_len ||
+		    sta_info_len != *pos) {
+			wpa_printf(MSG_DEBUG,
+				   "MLD: Invalid STA info len=%u, len=%u",
+				   sta_info_len, *pos);
+			goto out;
+		}
+
+		/* Get the link address */
+		wpa_printf(MSG_DEBUG,
+			   "MLD: link addr: " MACSTR " nstr BM len=%u",
+			   MAC2STR(pos + 1), nstr_bitmap_len);
+
+		ml_info[i].link_id = ctrl & EHT_PER_STA_CTRL_LINK_ID_MSK;
+		os_memcpy(ml_info[i].bssid, pos + 1, ETH_ALEN);
+
+		pos += sta_info_len;
+		ml_len -= sta_info_len;
+
+		wpa_printf(MSG_DEBUG, "MLD: sub_elem_len=%u, sta_info_len=%u",
+			   sub_elem_len, sta_info_len);
+
+		sub_elem_len -= sta_info_len + 2;
+		if (sub_elem_len < 4) {
+			wpa_printf(MSG_DEBUG, "MLD: Per STA profile too short");
+			goto out;
+		}
+
+		wpa_hexdump(MSG_MSGDUMP, "MLD: STA profile", pos, sub_elem_len);
+		ml_info[i].status = WPA_GET_LE16(pos + 2);
+
+		pos += sub_elem_len;
+		ml_len -= sub_elem_len;
+
+		i++;
+	}
+
+	wpabuf_free(mlbuf);
+	return i;
+out:
+	wpabuf_free(mlbuf);
+	return 0;
+}
+
+
 static int wpa_drv_get_mlo_info(struct wpa_supplicant *wpa_s)
 {
 	struct driver_sta_mlo_info mlo;
@@ -3740,18 +4081,17 @@
 			if (!(mlo.valid_links & BIT(i)))
 				continue;
 
-			if (os_memcmp(wpa_s->links[i].addr, mlo.links[i].addr,
-				      ETH_ALEN) != 0 ||
-			    os_memcmp(wpa_s->links[i].bssid, mlo.links[i].bssid,
-				      ETH_ALEN) != 0) {
+			if (!ether_addr_equal(wpa_s->links[i].addr,
+					      mlo.links[i].addr) ||
+			    !ether_addr_equal(wpa_s->links[i].bssid,
+					      mlo.links[i].bssid)) {
 				match = false;
 				break;
 			}
 		}
 
 		if (match && wpa_s->mlo_assoc_link_id == mlo.assoc_link_id &&
-		    os_memcmp(wpa_s->ap_mld_addr, mlo.ap_mld_addr,
-			      ETH_ALEN) == 0)
+		    ether_addr_equal(wpa_s->ap_mld_addr, mlo.ap_mld_addr))
 			return 0;
 	}
 
@@ -3939,7 +4279,7 @@
 #endif
 
 	wpa_supplicant_set_state(wpa_s, WPA_ASSOCIATED);
-	if (os_memcmp(bssid, wpa_s->bssid, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(bssid, wpa_s->bssid)) {
 		if (os_reltime_initialized(&wpa_s->session_start)) {
 			os_reltime_age(&wpa_s->session_start,
 				       &wpa_s->session_length);
@@ -4138,9 +4478,9 @@
 		os_get_reltime(&now);
 		os_reltime_sub(&now, &wpa_s->pending_eapol_rx_time, &age);
 		if (age.sec == 0 && age.usec < 200000 &&
-		    os_memcmp(wpa_s->pending_eapol_rx_src,
-			      wpa_s->valid_links ? wpa_s->ap_mld_addr : bssid,
-			      ETH_ALEN) == 0) {
+		    ether_addr_equal(wpa_s->pending_eapol_rx_src,
+				     wpa_s->valid_links ? wpa_s->ap_mld_addr :
+				     bssid)) {
 			wpa_dbg(wpa_s, MSG_DEBUG, "Process pending EAPOL "
 				"frame that was received just before "
 				"association notification");
@@ -4184,6 +4524,7 @@
 
 	wpas_wps_notify_assoc(wpa_s, bssid);
 
+#ifndef CONFIG_NO_WMM_AC
 	if (data) {
 		wmm_ac_notify_assoc(wpa_s, data->assoc_info.resp_ies,
 				    data->assoc_info.resp_ies_len,
@@ -4192,6 +4533,7 @@
 		if (wpa_s->reassoc_same_bss)
 			wmm_ac_restore_tspecs(wpa_s);
 	}
+#endif /* CONFIG_NO_WMM_AC */
 
 #if defined(CONFIG_FILS) || defined(CONFIG_MBO)
 	bss = wpa_bss_get_bssid(wpa_s, bssid);
@@ -4284,16 +4626,10 @@
 						 int locally_generated)
 {
 	const u8 *bssid;
-	int authenticating;
-	u8 prev_pending_bssid[ETH_ALEN];
 	struct wpa_bss *fast_reconnect = NULL;
 	struct wpa_ssid *fast_reconnect_ssid = NULL;
-	struct wpa_ssid *last_ssid;
 	struct wpa_bss *curr = NULL;
 
-	authenticating = wpa_s->wpa_state == WPA_AUTHENTICATING;
-	os_memcpy(prev_pending_bssid, wpa_s->pending_bssid, ETH_ALEN);
-
 	if (wpa_s->key_mgmt == WPA_KEY_MGMT_WPA_NONE) {
 		/*
 		 * At least Host AP driver and a Prism3 card seemed to be
@@ -4323,7 +4659,7 @@
 			"pre-shared key may be incorrect");
 		if (wpas_p2p_4way_hs_failed(wpa_s) > 0)
 			return; /* P2P group removed */
-		wpas_auth_failed(wpa_s, "WRONG_KEY", prev_pending_bssid);
+		wpas_auth_failed(wpa_s, "WRONG_KEY", wpa_s->pending_bssid);
 		wpas_notify_psk_mismatch(wpa_s);
 #ifdef CONFIG_DPP2
 		wpas_dpp_send_conn_status_result(wpa_s,
@@ -4374,7 +4710,7 @@
 	if (is_zero_ether_addr(bssid))
 		bssid = wpa_s->pending_bssid;
 	if (wpa_s->wpa_state >= WPA_AUTHENTICATING)
-		wpas_connection_failed(wpa_s, bssid);
+		wpas_connection_failed(wpa_s, bssid, NULL);
 	wpa_sm_notify_disassoc(wpa_s->wpa);
 	ptksa_cache_flush(wpa_s->ptksa, wpa_s->bssid, WPA_CIPHER_NONE);
 
@@ -4387,17 +4723,11 @@
 		wpa_dbg(wpa_s, MSG_DEBUG, "Disconnect event - remove keys");
 		wpa_clear_keys(wpa_s, wpa_s->bssid);
 	}
-	last_ssid = wpa_s->current_ssid;
 	wpa_supplicant_mark_disassoc(wpa_s);
 
 	if (curr)
 		wpa_bss_remove(wpa_s, curr, "Connection to AP lost");
 
-	if (authenticating && (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME)) {
-		sme_disassoc_while_authenticating(wpa_s, prev_pending_bssid);
-		wpa_s->current_ssid = last_ssid;
-	}
-
 	if (fast_reconnect &&
 	    !wpas_network_disabled(wpa_s, fast_reconnect_ssid) &&
 	    !disallowed_bssid(wpa_s, fast_reconnect->bssid) &&
@@ -4747,7 +5077,7 @@
 		MACSTR " TargetAP " MACSTR " status %u",
 		MAC2STR(sta_addr), MAC2STR(target_ap_addr), status);
 
-	if (os_memcmp(sta_addr, wpa_s->own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sta_addr, wpa_s->own_addr)) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "FT: Foreign STA Address " MACSTR
 			" in FT Action Response", MAC2STR(sta_addr));
 		return;
@@ -4974,6 +5304,20 @@
 }
 
 
+static void wpas_beacon_hint(struct wpa_supplicant *wpa_s, const char *title,
+			     struct frequency_attrs *attrs)
+{
+	if (!attrs->freq)
+		return;
+	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_REGDOM_BEACON_HINT
+		"%s freq=%u max_tx_power=%u%s%s%s",
+		title, attrs->freq, attrs->max_tx_power,
+		attrs->disabled ? " disabled=1" : "",
+		attrs->no_ir ? " no_ir=1" : "",
+		attrs->radar ? " radar=1" : "");
+}
+
+
 void wpa_supplicant_update_channel_list(struct wpa_supplicant *wpa_s,
 					struct channel_list_changed *info)
 {
@@ -4995,6 +5339,13 @@
 			reg_init_str(info->initiator), reg_type_str(info->type),
 			info->alpha2[0] ? " alpha2=" : "",
 			info->alpha2[0] ? info->alpha2 : "");
+
+		if (info->initiator == REGDOM_BEACON_HINT) {
+			wpas_beacon_hint(ifs, "before",
+					 &info->beacon_hint_before);
+			wpas_beacon_hint(ifs, "after",
+					 &info->beacon_hint_after);
+		}
 	}
 
 	if (wpa_s->drv_priv == NULL)
@@ -5055,10 +5406,12 @@
 		" Category=%u DataLen=%d freq=%d MHz",
 		MAC2STR(mgmt->sa), category, (int) plen, freq);
 
+#ifndef CONFIG_NO_WMM_AC
 	if (category == WLAN_ACTION_WMM) {
 		wmm_ac_rx_action(wpa_s, mgmt->da, mgmt->sa, payload, plen);
 		return;
 	}
+#endif /* CONFIG_NO_WMM_AC */
 
 #ifdef CONFIG_IEEE80211R
 	if (category == WLAN_ACTION_FT) {
@@ -5122,7 +5475,7 @@
 		size_t qlen = plen - 1;
 		wpa_dbg(wpa_s, MSG_DEBUG, "Interworking: Received QoS Map Configure frame from "
 			MACSTR, MAC2STR(mgmt->sa));
-		if (os_memcmp(mgmt->sa, wpa_s->bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(mgmt->sa, wpa_s->bssid) &&
 		    qlen > 2 && pos[0] == WLAN_EID_QOS_MAP_SET &&
 		    pos[1] <= qlen - 2 && pos[1] >= 16)
 			wpas_qos_map_set(wpa_s, pos + 2, pos[1]);
@@ -5130,6 +5483,7 @@
 	}
 #endif /* CONFIG_INTERWORKING */
 
+#ifndef CONFIG_NO_RRM
 	if (category == WLAN_ACTION_RADIO_MEASUREMENT &&
 	    payload[0] == WLAN_RRM_RADIO_MEASUREMENT_REQUEST) {
 		wpas_rrm_handle_radio_measurement_request(wpa_s, mgmt->sa,
@@ -5152,6 +5506,7 @@
 							 rssi);
 		return;
 	}
+#endif /* CONFIG_NO_RRM */
 
 #ifdef CONFIG_FST
 	if (mgmt->u.action.category == WLAN_ACTION_FST && wpa_s->fst) {
@@ -5160,6 +5515,17 @@
 	}
 #endif /* CONFIG_FST */
 
+#ifdef CONFIG_NAN_USD
+	if (category == WLAN_ACTION_PUBLIC && plen >= 5 &&
+	    payload[0] == WLAN_PA_VENDOR_SPECIFIC &&
+	    WPA_GET_BE32(&payload[1]) == NAN_SDF_VENDOR_TYPE) {
+		payload += 5;
+		plen -= 5;
+		wpas_nan_usd_rx_sdf(wpa_s, mgmt->sa, freq, payload, plen);
+		return;
+	}
+#endif /* CONFIG_NAN_USD */
+
 #ifdef CONFIG_DPP
 	if (category == WLAN_ACTION_PUBLIC && plen >= 5 &&
 	    payload[0] == WLAN_PA_VENDOR_SPECIFIC &&
@@ -5172,6 +5538,7 @@
 	}
 #endif /* CONFIG_DPP */
 
+#ifndef CONFIG_NO_ROBUST_AV
 	if (category == WLAN_ACTION_ROBUST_AV_STREAMING &&
 	    payload[0] == ROBUST_AV_SCS_RESP) {
 		wpas_handle_robust_av_scs_recv_action(wpa_s, mgmt->sa,
@@ -5192,6 +5559,7 @@
 						 payload + 4, plen - 4);
 		return;
 	}
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	wpas_p2p_rx_action(wpa_s, mgmt->da, mgmt->sa, mgmt->bssid,
 			   category, payload, plen, freq);
@@ -5395,6 +5763,8 @@
 				    union wpa_event_data *data)
 {
 	const u8 *bssid = data->assoc_reject.bssid;
+	struct ieee802_11_elems elems;
+	const u8 *link_bssids[MAX_NUM_MLD_LINKS];
 #ifdef CONFIG_MBO
 	struct wpa_bss *reject_bss;
 #endif /* CONFIG_MBO */
@@ -5450,7 +5820,7 @@
 		if (!bss) {
 			bss = wpa_supplicant_get_new_bss(wpa_s, bssid);
 			if (!bss) {
-				wpas_connection_failed(wpa_s, bssid);
+				wpas_connection_failed(wpa_s, bssid, NULL);
 				wpa_supplicant_mark_disassoc(wpa_s);
 				return;
 			}
@@ -5485,7 +5855,7 @@
 		if (!bss || wpa_s->dpp_pfs_fallback) {
 			wpa_printf(MSG_DEBUG,
 				   "DPP: Updated PFS policy for next try");
-			wpas_connection_failed(wpa_s, bssid);
+			wpas_connection_failed(wpa_s, bssid, NULL);
 			wpa_supplicant_mark_disassoc(wpa_s);
 			return;
 		}
@@ -5522,8 +5892,34 @@
 	}
 #endif /* CONFIG_MBO */
 
+	/* Check for other failed links in the response */
+	os_memset(link_bssids, 0, sizeof(link_bssids));
+	if (ieee802_11_parse_elems(data->assoc_reject.resp_ies,
+				   data->assoc_reject.resp_ies_len,
+				   &elems, 1) != ParseFailed) {
+		struct ml_sta_link_info ml_info[MAX_NUM_MLD_LINKS];
+		unsigned int n_links, i, idx;
+
+		idx = 0;
+		n_links = wpas_ml_parse_assoc(wpa_s, &elems, ml_info);
+
+		for (i = 1; i < n_links; i++) {
+			/* The status cannot be success here.
+			 * Add the link to the failed list if it is reporting
+			 * an error. The only valid "non-error" status is
+			 * TX_LINK_NOT_ACCEPTED as that means this link may
+			 * still accept an association from us.
+			 */
+			if (ml_info[i].status !=
+			    WLAN_STATUS_DENIED_TX_LINK_NOT_ACCEPTED) {
+				link_bssids[idx] = ml_info[i].bssid;
+				idx++;
+			}
+		}
+	}
+
 	if (wpa_s->drv_flags & WPA_DRIVER_FLAGS_SME) {
-		sme_event_assoc_reject(wpa_s, data);
+		sme_event_assoc_reject(wpa_s, data, link_bssids);
 		return;
 	}
 
@@ -5560,7 +5956,7 @@
 	}
 #endif /* CONFIG_FILS */
 
-	wpas_connection_failed(wpa_s, bssid);
+	wpas_connection_failed(wpa_s, bssid, link_bssids);
 	wpa_supplicant_mark_disassoc(wpa_s);
 }
 
@@ -5572,7 +5968,7 @@
 	int res;
 
 	if (!data || wpa_s->wpa_state != WPA_COMPLETED ||
-	    os_memcmp(data->sa, wpa_s->bssid, ETH_ALEN) != 0)
+	    !ether_addr_equal(data->sa, wpa_s->bssid))
 		return;
 	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_UNPROT_BEACON MACSTR,
 		MAC2STR(data->sa));
@@ -5662,7 +6058,7 @@
 		return;
 	}
 
-	if (os_memcmp(bssid, wpa_s->bssid, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(bssid, wpa_s->bssid)) {
 		os_memcpy(wpa_s->bssid, bssid, ETH_ALEN);
 		wpa_supplicant_update_current_bss(wpa_s, wpa_s->bssid);
 		wpas_notify_bssid_changed(wpa_s);
@@ -5947,8 +6343,8 @@
 		 */
 		if (data->tx_status.type == WLAN_FC_TYPE_MGMT &&
 		    data->tx_status.stype == WLAN_FC_STYPE_ACTION &&
-		    os_memcmp(wpa_s->p2pdev->pending_action_dst,
-			      data->tx_status.dst, ETH_ALEN) == 0) {
+		    ether_addr_equal(wpa_s->p2pdev->pending_action_dst,
+				     data->tx_status.dst)) {
 			offchannel_send_action_tx_status(
 				wpa_s->p2pdev, data->tx_status.dst,
 				data->tx_status.data,
@@ -6271,6 +6667,11 @@
 			wpa_s, data->remain_on_channel.freq,
 			data->remain_on_channel.duration);
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+		wpas_nan_usd_remain_on_channel_cb(
+			wpa_s, data->remain_on_channel.freq,
+			data->remain_on_channel.duration);
+#endif /* CONFIG_NAN_USD */
 		break;
 	case EVENT_CANCEL_REMAIN_ON_CHANNEL:
 #ifdef CONFIG_OFFCHANNEL
@@ -6283,6 +6684,10 @@
 		wpas_dpp_cancel_remain_on_channel_cb(
 			wpa_s, data->remain_on_channel.freq);
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+		wpas_nan_usd_cancel_remain_on_channel_cb(
+			wpa_s, data->remain_on_channel.freq);
+#endif /* CONFIG_NAN_USD */
 		break;
 	case EVENT_EAPOL_RX:
 		wpa_supplicant_rx_eapol(wpa_s, data->eapol_rx.src,
@@ -6321,7 +6726,7 @@
 					     wpa_s, NULL);
 			os_memcpy(addr, wpa_s->own_addr, ETH_ALEN);
 			wpa_supplicant_update_mac_addr(wpa_s);
-			if (os_memcmp(addr, wpa_s->own_addr, ETH_ALEN) != 0)
+			if (!ether_addr_equal(addr, wpa_s->own_addr))
 				wpa_sm_pmksa_cache_flush(wpa_s->wpa, NULL);
 			else
 				wpa_sm_pmksa_cache_reconfig(wpa_s->wpa);
@@ -6455,8 +6860,8 @@
 #endif /* CONFIG_IBSS_RSN */
 		break;
 	case EVENT_DRIVER_GTK_REKEY:
-		if (os_memcmp(data->driver_gtk_rekey.bssid,
-			      wpa_s->bssid, ETH_ALEN))
+		if (!ether_addr_equal(data->driver_gtk_rekey.bssid,
+				      wpa_s->bssid))
 			break;
 		if (!wpa_s->wpa)
 			break;
@@ -6618,6 +7023,9 @@
 #ifdef CONFIG_DPP
 		wpas_dpp_tx_wait_expire(wpa_s);
 #endif /* CONFIG_DPP */
+#ifdef CONFIG_NAN_USD
+		wpas_nan_usd_tx_wait_expire(wpa_s);
+#endif /* CONFIG_NAN_USD */
 		break;
 	case EVENT_TID_LINK_MAP:
 		if (data)
diff --git a/wpa_supplicant/gas_query.c b/wpa_supplicant/gas_query.c
index c301f74..7d29931 100644
--- a/wpa_supplicant/gas_query.c
+++ b/wpa_supplicant/gas_query.c
@@ -199,10 +199,16 @@
 gas_query_get_pending(struct gas_query *gas, const u8 *addr, u8 dialog_token)
 {
 	struct gas_query_pending *q;
+	struct wpa_supplicant *wpa_s = gas->wpa_s;
+
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(q->addr, addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(q->addr, addr) &&
 		    q->dialog_token == dialog_token)
 			return q;
+		if (wpa_s->valid_links &&
+		    ether_addr_equal(wpa_s->ap_mld_addr, addr) &&
+		    wpas_ap_link_address(wpa_s, q->addr))
+			return q;
 	}
 	return NULL;
 }
@@ -243,7 +249,7 @@
 	wpa_printf(MSG_DEBUG, "GAS: TX status: freq=%u dst=" MACSTR
 		   " result=%d query=%p dialog_token=%u dur=%d ms",
 		   freq, MAC2STR(dst), result, query, query->dialog_token, dur);
-	if (os_memcmp(dst, query->addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(dst, query->addr)) {
 		wpa_printf(MSG_DEBUG, "GAS: TX status for unexpected destination");
 		return;
 	}
@@ -300,7 +306,7 @@
 	    (!gas->wpa_s->conf->gas_address3 ||
 	     (gas->wpa_s->current_ssid &&
 	      gas->wpa_s->wpa_state >= WPA_ASSOCIATED &&
-	      os_memcmp(query->addr, gas->wpa_s->bssid, ETH_ALEN) == 0)))
+	      ether_addr_equal(query->addr, gas->wpa_s->bssid))))
 		bssid = query->addr;
 	else
 		bssid = wildcard_bssid;
@@ -668,7 +674,7 @@
 {
 	struct gas_query_pending *q;
 	dl_list_for_each(q, &gas->pending, struct gas_query_pending, list) {
-		if (os_memcmp(dst, q->addr, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(dst, q->addr) &&
 		    dialog_token == q->dialog_token)
 			return 0;
 	}
diff --git a/wpa_supplicant/hs20_supplicant.c b/wpa_supplicant/hs20_supplicant.c
index eaf0803..c68167f 100644
--- a/wpa_supplicant/hs20_supplicant.c
+++ b/wpa_supplicant/hs20_supplicant.c
@@ -27,6 +27,7 @@
 #include "interworking.h"
 #include "hs20_supplicant.h"
 #include "base64.h"
+#include "notify.h"
 
 
 #define OSU_MAX_ITEMS 10
@@ -68,7 +69,6 @@
 void hs20_configure_frame_filters(struct wpa_supplicant *wpa_s)
 {
 	struct wpa_bss *bss = wpa_s->current_bss;
-	u8 *bssid = wpa_s->bssid;
 	const u8 *ie;
 	const u8 *ext_capa;
 	u32 filter = 0;
@@ -79,9 +79,8 @@
 			|| !is_hs20_config(wpa_s)
 #endif
 			) {
-		wpa_printf(MSG_DEBUG,
-			   "Not configuring frame filtering - BSS " MACSTR
-			   " is not a Hotspot 2.0 network", MAC2STR(bssid));
+		/* Not configuring frame filtering - BSS is not a Hotspot 2.0
+		 * network */
 		return;
 	}
 
@@ -336,7 +335,7 @@
 	struct icon_entry *icon;
 
 	dl_list_for_each(icon, &wpa_s->icon_head, struct icon_entry, list) {
-		if (os_memcmp(icon->bssid, bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(icon->bssid, bssid) &&
 		    os_strcmp(icon->file_name, file_name) == 0 && icon->image)
 			return icon;
 	}
@@ -412,7 +411,7 @@
 
 	dl_list_for_each_safe(icon, tmp, &wpa_s->icon_head, struct icon_entry,
 			      list) {
-		if ((!bssid || os_memcmp(icon->bssid, bssid, ETH_ALEN) == 0) &&
+		if ((!bssid || ether_addr_equal(icon->bssid, bssid)) &&
 		    (!file_name ||
 		     os_strcmp(icon->file_name, file_name) == 0)) {
 			dl_list_del(&icon->list);
@@ -458,7 +457,7 @@
 			      list) {
 		if (icon == new_icon)
 			continue;
-		if (os_memcmp(icon->bssid, new_icon->bssid, ETH_ALEN) == 0 &&
+		if (ether_addr_equal(icon->bssid, new_icon->bssid) &&
 		    os_strcmp(icon->file_name, new_icon->file_name) == 0) {
 			dl_list_del(&icon->list);
 			hs20_free_icon_entry(icon);
@@ -479,7 +478,7 @@
 
 	dl_list_for_each(icon, &wpa_s->icon_head, struct icon_entry, list) {
 		if (icon->dialog_token == dialog_token && !icon->image &&
-		    os_memcmp(icon->bssid, sa, ETH_ALEN) == 0) {
+		    ether_addr_equal(icon->bssid, sa)) {
 			icon->image = os_memdup(pos, slen);
 			if (!icon->image)
 				return -1;
@@ -1354,8 +1353,7 @@
 		return;
 	}
 
-	wpa_msg(wpa_s, MSG_INFO, HS20_T_C_ACCEPTANCE "%s", url);
-	wpas_notify_hs20_rx_terms_and_conditions_acceptance(wpa_s, url);
+	wpas_notify_hs20_t_c_acceptance(wpa_s, url);
 }
 
 
diff --git a/wpa_supplicant/ibss_rsn.c b/wpa_supplicant/ibss_rsn.c
index 5b31f7b..554268a 100644
--- a/wpa_supplicant/ibss_rsn.c
+++ b/wpa_supplicant/ibss_rsn.c
@@ -30,7 +30,7 @@
 	struct ibss_rsn_peer *peer;
 
 	for (peer = ibss_rsn->peers; peer; peer = peer->next)
-		if (os_memcmp(addr, peer->addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(addr, peer->addr))
 			break;
 	return peer;
 }
@@ -672,7 +672,7 @@
 
 		for (prev = NULL, peer = ibss_rsn->peers; peer != NULL;
 		     prev = peer, peer = peer->next) {
-			if (os_memcmp(peermac, peer->addr, ETH_ALEN) == 0) {
+			if (ether_addr_equal(peermac, peer->addr)) {
 				if (prev == NULL)
 					ibss_rsn->peers = peer->next;
 				else
diff --git a/wpa_supplicant/interworking.c b/wpa_supplicant/interworking.c
index cb1165e..498cc98 100644
--- a/wpa_supplicant/interworking.c
+++ b/wpa_supplicant/interworking.c
@@ -2674,7 +2674,7 @@
 			continue;
 		if (!(other->flags & WPA_BSS_ANQP_FETCH_TRIED))
 			continue;
-		if (os_memcmp(bss->hessid, other->hessid, ETH_ALEN) != 0)
+		if (!ether_addr_equal(bss->hessid, other->hessid))
 			continue;
 		if (bss->ssid_len != other->ssid_len ||
 		    os_memcmp(bss->ssid, other->ssid, bss->ssid_len) != 0)
@@ -3169,7 +3169,7 @@
 	 */
 	dl_list_for_each_reverse(tmp, &wpa_s->bss, struct wpa_bss, list) {
 		if (tmp == wpa_s->interworking_gas_bss &&
-		    os_memcmp(tmp->bssid, dst, ETH_ALEN) == 0) {
+		    ether_addr_equal(tmp->bssid, dst)) {
 			bss = tmp;
 			break;
 		}
diff --git a/wpa_supplicant/mesh_mpm.c b/wpa_supplicant/mesh_mpm.c
index 138c013..b2fc127 100644
--- a/wpa_supplicant/mesh_mpm.c
+++ b/wpa_supplicant/mesh_mpm.c
@@ -468,6 +468,7 @@
 	params.plink_state = state;
 	params.peer_aid = sta->peer_aid;
 	params.set = 1;
+	params.mld_link_id = -1;
 
 	ret = wpa_drv_sta_add(wpa_s, &params);
 	if (ret) {
@@ -697,6 +698,7 @@
 	params.addr = sta->addr;
 	params.flags = WPA_STA_AUTHENTICATED | WPA_STA_AUTHORIZED;
 	params.set = 1;
+	params.mld_link_id = -1;
 
 	wpa_msg(wpa_s, MSG_DEBUG, "MPM authenticating " MACSTR,
 		MAC2STR(sta->addr));
@@ -816,6 +818,7 @@
 	params.eht_capab_len = sta->eht_capab_len;
 	params.flags |= WPA_STA_WMM;
 	params.flags_mask |= WPA_STA_AUTHENTICATED;
+	params.mld_link_id = -1;
 	if (conf->security == MESH_CONF_SEC_NONE) {
 		params.flags |= WPA_STA_AUTHORIZED;
 		params.flags |= WPA_STA_AUTHENTICATED;
@@ -851,7 +854,7 @@
 
 	if (ssid && ssid->no_auto_peer &&
 	    (is_zero_ether_addr(data->mesh_required_peer) ||
-	     os_memcmp(data->mesh_required_peer, addr, ETH_ALEN) != 0)) {
+	     !ether_addr_equal(data->mesh_required_peer, addr))) {
 		wpa_msg(wpa_s, MSG_INFO, "will not initiate new peer link with "
 			MACSTR " because of no_auto_peer", MAC2STR(addr));
 		if (data->mesh_pending_auth) {
@@ -862,7 +865,7 @@
 			mgmt = wpabuf_head(data->mesh_pending_auth);
 			os_reltime_age(&data->mesh_pending_auth_time, &age);
 			if (age.sec < 2 &&
-			    os_memcmp(mgmt->sa, addr, ETH_ALEN) == 0) {
+			    ether_addr_equal(mgmt->sa, addr)) {
 				wpa_printf(MSG_DEBUG,
 					   "mesh: Process pending Authentication frame from %u.%06u seconds ago",
 					   (unsigned int) age.sec,
diff --git a/wpa_supplicant/mesh_rsn.c b/wpa_supplicant/mesh_rsn.c
index 12dcc30..ada53c7 100644
--- a/wpa_supplicant/mesh_rsn.c
+++ b/wpa_supplicant/mesh_rsn.c
@@ -142,6 +142,23 @@
 }
 
 
+static int auth_for_each_sta(
+	void *ctx, int (*cb)(struct wpa_state_machine *sm, void *ctx),
+	void *cb_ctx)
+{
+	struct mesh_rsn *rsn = ctx;
+	struct hostapd_data *hapd;
+	struct sta_info *sta;
+
+	hapd = rsn->wpa_s->ifmsh->bss[0];
+	for (sta = hapd->sta_list; sta; sta = sta->next) {
+		if (sta->wpa_sm && cb(sta->wpa_sm, cb_ctx))
+			return 1;
+	}
+	return 0;
+}
+
+
 static int __mesh_rsn_auth_init(struct mesh_rsn *rsn, const u8 *addr,
 				enum mfp_options ieee80211w, int ocv)
 {
@@ -151,6 +168,7 @@
 		.get_psk = auth_get_psk,
 		.set_key = auth_set_key,
 		.start_ampe = auth_start_ampe,
+		.for_each_sta = auth_for_each_sta,
 	};
 	u8 seq[6] = {};
 
@@ -386,7 +404,8 @@
 			   " - try to use PMKSA caching instead of new SAE authentication",
 			   MAC2STR(sta->addr));
 		wpa_auth_pmksa_set_to_sm(pmksa, sta->wpa_sm, hapd->wpa_auth,
-					 sta->sae->pmkid, sta->sae->pmk);
+					 sta->sae->pmkid, sta->sae->pmk,
+					 &sta->sae->pmk_len);
 		sae_accept_sta(hapd, sta);
 		sta->mesh_sae_pmksa_caching = 1;
 		return 0;
diff --git a/wpa_supplicant/nan_usd.c b/wpa_supplicant/nan_usd.c
new file mode 100644
index 0000000..657b302
--- /dev/null
+++ b/wpa_supplicant/nan_usd.c
@@ -0,0 +1,513 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "common/nan_de.h"
+#include "wpa_supplicant_i.h"
+#include "offchannel.h"
+#include "driver_i.h"
+#include "nan_usd.h"
+
+
+static const char *
+tx_status_result_txt(enum offchannel_send_action_result result)
+{
+	switch (result) {
+	case OFFCHANNEL_SEND_ACTION_SUCCESS:
+		return "success";
+	case OFFCHANNEL_SEND_ACTION_NO_ACK:
+		return "no-ack";
+	case OFFCHANNEL_SEND_ACTION_FAILED:
+		return "failed";
+	}
+
+	return "?";
+}
+
+
+static void wpas_nan_de_tx_status(struct wpa_supplicant *wpa_s,
+				  unsigned int freq, const u8 *dst,
+				  const u8 *src, const u8 *bssid,
+				  const u8 *data, size_t data_len,
+				  enum offchannel_send_action_result result)
+{
+	if (!wpa_s->nan_de)
+		return;
+
+	wpa_printf(MSG_DEBUG, "NAN: TX status A1=" MACSTR " A2=" MACSTR
+		   " A3=" MACSTR " freq=%d len=%zu result=%s",
+		   MAC2STR(dst), MAC2STR(src), MAC2STR(bssid), freq,
+		   data_len, tx_status_result_txt(result));
+
+	nan_de_tx_status(wpa_s->nan_de, freq, dst);
+}
+
+
+struct wpas_nan_usd_tx_work {
+	unsigned int freq;
+	unsigned int wait_time;
+	u8 dst[ETH_ALEN];
+	u8 src[ETH_ALEN];
+	u8 bssid[ETH_ALEN];
+	struct wpabuf *buf;
+};
+
+
+static void wpas_nan_usd_tx_work_free(struct wpas_nan_usd_tx_work *twork)
+{
+	if (!twork)
+		return;
+	wpabuf_free(twork->buf);
+	os_free(twork);
+}
+
+
+static void wpas_nan_usd_tx_work_done(struct wpa_supplicant *wpa_s)
+{
+	struct wpas_nan_usd_tx_work *twork;
+
+	if (!wpa_s->nan_usd_tx_work)
+		return;
+
+	twork = wpa_s->nan_usd_tx_work->ctx;
+	wpas_nan_usd_tx_work_free(twork);
+	radio_work_done(wpa_s->nan_usd_tx_work);
+	wpa_s->nan_usd_tx_work = NULL;
+}
+
+
+static int wpas_nan_de_tx_send(struct wpa_supplicant *wpa_s, unsigned int freq,
+			       unsigned int wait_time, const u8 *dst,
+			       const u8 *src, const u8 *bssid,
+			       const struct wpabuf *buf)
+{
+	wpa_printf(MSG_DEBUG, "NAN: TX NAN SDF A1=" MACSTR " A2=" MACSTR
+		   " A3=" MACSTR " freq=%d len=%zu",
+		   MAC2STR(dst), MAC2STR(src), MAC2STR(bssid), freq,
+		   wpabuf_len(buf));
+
+	return offchannel_send_action(wpa_s, freq, dst, src, bssid,
+				      wpabuf_head(buf), wpabuf_len(buf),
+				      wait_time, wpas_nan_de_tx_status, 1);
+}
+
+
+static void wpas_nan_usd_start_tx_cb(struct wpa_radio_work *work, int deinit)
+{
+	struct wpa_supplicant *wpa_s = work->wpa_s;
+	struct wpas_nan_usd_tx_work *twork = work->ctx;
+
+	if (deinit) {
+		if (work->started) {
+			wpa_s->nan_usd_tx_work = NULL;
+			offchannel_send_action_done(wpa_s);
+		}
+		wpas_nan_usd_tx_work_free(twork);
+		return;
+	}
+
+	wpa_s->nan_usd_tx_work = work;
+
+	if (wpas_nan_de_tx_send(wpa_s, twork->freq, twork->wait_time,
+				twork->dst, twork->src, twork->bssid,
+				twork->buf) < 0)
+		wpas_nan_usd_tx_work_done(wpa_s);
+}
+
+
+static int wpas_nan_de_tx(void *ctx, unsigned int freq, unsigned int wait_time,
+			  const u8 *dst, const u8 *src, const u8 *bssid,
+			  const struct wpabuf *buf)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	struct wpas_nan_usd_tx_work *twork;
+
+	if (wpa_s->nan_usd_tx_work || wpa_s->nan_usd_listen_work) {
+		/* Reuse ongoing radio work */
+		return wpas_nan_de_tx_send(wpa_s, freq, wait_time, dst, src,
+					   bssid, buf);
+	}
+
+	twork = os_zalloc(sizeof(*twork));
+	if (!twork)
+		return -1;
+	twork->freq = freq;
+	twork->wait_time = wait_time;
+	os_memcpy(twork->dst, dst, ETH_ALEN);
+	os_memcpy(twork->src, src, ETH_ALEN);
+	os_memcpy(twork->bssid, bssid, ETH_ALEN);
+	twork->buf = wpabuf_dup(buf);
+	if (!twork->buf) {
+		wpas_nan_usd_tx_work_free(twork);
+		return -1;
+	}
+
+	if (radio_add_work(wpa_s, freq, "nan-usd-tx", 0,
+			   wpas_nan_usd_start_tx_cb, twork) < 0) {
+		wpas_nan_usd_tx_work_free(twork);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+struct wpas_nan_usd_listen_work {
+	unsigned int freq;
+	unsigned int duration;
+};
+
+
+static void wpas_nan_usd_listen_work_done(struct wpa_supplicant *wpa_s)
+{
+	struct wpas_nan_usd_listen_work *lwork;
+
+	if (!wpa_s->nan_usd_listen_work)
+		return;
+
+	lwork = wpa_s->nan_usd_listen_work->ctx;
+	os_free(lwork);
+	radio_work_done(wpa_s->nan_usd_listen_work);
+	wpa_s->nan_usd_listen_work = NULL;
+}
+
+
+static void wpas_nan_usd_start_listen_cb(struct wpa_radio_work *work,
+					 int deinit)
+{
+	struct wpa_supplicant *wpa_s = work->wpa_s;
+	struct wpas_nan_usd_listen_work *lwork = work->ctx;
+	unsigned int duration;
+
+	if (deinit) {
+		if (work->started) {
+			wpa_s->nan_usd_listen_work = NULL;
+			wpa_drv_cancel_remain_on_channel(wpa_s);
+		}
+		os_free(lwork);
+		return;
+	}
+
+	wpa_s->nan_usd_listen_work = work;
+
+	duration = lwork->duration;
+	if (duration > wpa_s->max_remain_on_chan)
+		duration = wpa_s->max_remain_on_chan;
+	wpa_printf(MSG_DEBUG, "NAN: Start listen on %u MHz for %u ms",
+		   lwork->freq, duration);
+	if (wpa_drv_remain_on_channel(wpa_s, lwork->freq, duration) < 0) {
+		wpa_printf(MSG_DEBUG,
+			   "NAN: Failed to request the driver to remain on channel (%u MHz) for listen",
+			   lwork->freq);
+		wpas_nan_usd_listen_work_done(wpa_s);
+		return;
+	}
+}
+
+
+static int wpas_nan_de_listen(void *ctx, unsigned int freq,
+			      unsigned int duration)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	struct wpas_nan_usd_listen_work *lwork;
+
+	lwork = os_zalloc(sizeof(*lwork));
+	if (!lwork)
+		return -1;
+	lwork->freq = freq;
+	lwork->duration = duration;
+
+	if (radio_add_work(wpa_s, freq, "nan-usd-listen", 0,
+			   wpas_nan_usd_start_listen_cb, lwork) < 0) {
+		os_free(lwork);
+		return -1;
+	}
+
+	return 0;
+}
+
+
+static void
+wpas_nan_de_discovery_result(void *ctx, int subscribe_id,
+			     enum nan_service_protocol_type srv_proto_type,
+			     const u8 *ssi, size_t ssi_len, int peer_publish_id,
+			     const u8 *peer_addr, bool fsd, bool fsd_gas)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(wpa_s, MSG_INFO, NAN_DISCOVERY_RESULT
+		"subscribe_id=%d publish_id=%d address=" MACSTR
+		" fsd=%d fsd_gas=%d srv_proto_type=%u ssi=%s",
+		subscribe_id, peer_publish_id, MAC2STR(peer_addr),
+		fsd, fsd_gas, srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static void wpas_nan_de_replied(void *ctx, int publish_id, const u8 *peer_addr,
+				int peer_subscribe_id,
+				enum nan_service_protocol_type srv_proto_type,
+				const u8 *ssi, size_t ssi_len)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(wpa_s, MSG_INFO, NAN_REPLIED
+		"publish_id=%d address=" MACSTR
+		" subscribe_id=%d srv_proto_type=%u ssi=%s",
+		publish_id, MAC2STR(peer_addr), peer_subscribe_id,
+		srv_proto_type, ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+static const char * nan_reason_txt(enum nan_de_reason reason)
+{
+	switch (reason) {
+	case NAN_DE_REASON_TIMEOUT:
+		return "timeout";
+	case NAN_DE_REASON_USER_REQUEST:
+		return "user-request";
+	case NAN_DE_REASON_FAILURE:
+		return "failure";
+	}
+
+	return "unknown";
+}
+
+
+static void wpas_nan_de_publish_terminated(void *ctx, int publish_id,
+					   enum nan_de_reason reason)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpa_msg(wpa_s, MSG_INFO, NAN_PUBLISH_TERMINATED
+		"publish_id=%d reason=%s",
+		publish_id, nan_reason_txt(reason));
+}
+
+
+static void wpas_nan_de_subscribe_terminated(void *ctx, int subscribe_id,
+					     enum nan_de_reason reason)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+
+	wpa_msg(wpa_s, MSG_INFO, NAN_SUBSCRIBE_TERMINATED
+		"subscribe_id=%d reason=%s",
+		subscribe_id, nan_reason_txt(reason));
+}
+
+
+static void wpas_nan_de_receive(void *ctx, int id, int peer_instance_id,
+				const u8 *ssi, size_t ssi_len,
+				const u8 *peer_addr)
+{
+	struct wpa_supplicant *wpa_s = ctx;
+	char *ssi_hex;
+
+	ssi_hex = os_zalloc(2 * ssi_len + 1);
+	if (!ssi_hex)
+		return;
+	if (ssi)
+		wpa_snprintf_hex(ssi_hex, 2 * ssi_len + 1, ssi, ssi_len);
+	wpa_msg(wpa_s, MSG_INFO, NAN_RECEIVE
+		"id=%d peer_instance_id=%d address=" MACSTR " ssi=%s",
+		id, peer_instance_id, MAC2STR(peer_addr), ssi_hex);
+	os_free(ssi_hex);
+}
+
+
+int wpas_nan_usd_init(struct wpa_supplicant *wpa_s)
+{
+	struct nan_callbacks cb;
+
+	os_memset(&cb, 0, sizeof(cb));
+	cb.ctx = wpa_s;
+	cb.tx = wpas_nan_de_tx;
+	cb.listen = wpas_nan_de_listen;
+	cb.discovery_result = wpas_nan_de_discovery_result;
+	cb.replied = wpas_nan_de_replied;
+	cb.publish_terminated = wpas_nan_de_publish_terminated;
+	cb.subscribe_terminated = wpas_nan_de_subscribe_terminated;
+	cb.receive = wpas_nan_de_receive;
+
+	wpa_s->nan_de = nan_de_init(wpa_s->own_addr, false, &cb);
+	if (!wpa_s->nan_de)
+		return -1;
+	return 0;
+}
+
+
+void wpas_nan_usd_deinit(struct wpa_supplicant *wpa_s)
+{
+	nan_de_deinit(wpa_s->nan_de);
+	wpa_s->nan_de = NULL;
+}
+
+
+void wpas_nan_usd_rx_sdf(struct wpa_supplicant *wpa_s, const u8 *src,
+			 unsigned int freq, const u8 *buf, size_t len)
+{
+	if (!wpa_s->nan_de)
+		return;
+	nan_de_rx_sdf(wpa_s->nan_de, src, freq, buf, len);
+}
+
+
+void wpas_nan_usd_flush(struct wpa_supplicant *wpa_s)
+{
+	if (!wpa_s->nan_de)
+		return;
+	nan_de_flush(wpa_s->nan_de);
+}
+
+
+int wpas_nan_usd_publish(struct wpa_supplicant *wpa_s, const char *service_name,
+			 enum nan_service_protocol_type srv_proto_type,
+			 const struct wpabuf *ssi,
+			 struct nan_publish_params *params)
+{
+	int publish_id;
+	struct wpabuf *elems = NULL;
+
+	if (!wpa_s->nan_de)
+		return -1;
+
+	publish_id = nan_de_publish(wpa_s->nan_de, service_name, srv_proto_type,
+				    ssi, elems, params);
+	wpabuf_free(elems);
+	return publish_id;
+}
+
+
+void wpas_nan_usd_cancel_publish(struct wpa_supplicant *wpa_s, int publish_id)
+{
+	if (!wpa_s->nan_de)
+		return;
+	nan_de_cancel_publish(wpa_s->nan_de, publish_id);
+}
+
+
+int wpas_nan_usd_update_publish(struct wpa_supplicant *wpa_s, int publish_id,
+				const struct wpabuf *ssi)
+{
+	if (!wpa_s->nan_de)
+		return -1;
+	return nan_de_update_publish(wpa_s->nan_de, publish_id, ssi);
+}
+
+
+int wpas_nan_usd_subscribe(struct wpa_supplicant *wpa_s,
+			   const char *service_name,
+			   enum nan_service_protocol_type srv_proto_type,
+			   const struct wpabuf *ssi,
+			   struct nan_subscribe_params *params)
+{
+	int subscribe_id;
+	struct wpabuf *elems = NULL;
+
+	if (!wpa_s->nan_de)
+		return -1;
+
+	subscribe_id = nan_de_subscribe(wpa_s->nan_de, service_name,
+					srv_proto_type, ssi, elems, params);
+	wpabuf_free(elems);
+	return subscribe_id;
+}
+
+
+void wpas_nan_usd_cancel_subscribe(struct wpa_supplicant *wpa_s,
+				   int subscribe_id)
+{
+	if (!wpa_s->nan_de)
+		return;
+	nan_de_cancel_subscribe(wpa_s->nan_de, subscribe_id);
+}
+
+
+int wpas_nan_usd_transmit(struct wpa_supplicant *wpa_s, int handle,
+			  const struct wpabuf *ssi, const struct wpabuf *elems,
+			  const u8 *peer_addr, u8 req_instance_id)
+{
+	if (!wpa_s->nan_de)
+		return -1;
+	return nan_de_transmit(wpa_s->nan_de, handle, ssi, elems, peer_addr,
+			       req_instance_id);
+}
+
+
+void wpas_nan_usd_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+				       unsigned int freq, unsigned int duration)
+{
+	wpas_nan_usd_listen_work_done(wpa_s);
+
+	if (wpa_s->nan_de)
+		nan_de_listen_started(wpa_s->nan_de, freq, duration);
+}
+
+
+void wpas_nan_usd_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+					      unsigned int freq)
+{
+	if (wpa_s->nan_de)
+		nan_de_listen_ended(wpa_s->nan_de, freq);
+}
+
+
+void wpas_nan_usd_tx_wait_expire(struct wpa_supplicant *wpa_s)
+{
+	wpas_nan_usd_tx_work_done(wpa_s);
+
+	if (wpa_s->nan_de)
+		nan_de_tx_wait_ended(wpa_s->nan_de);
+}
+
+
+int * wpas_nan_usd_all_freqs(struct wpa_supplicant *wpa_s)
+{
+	int i, j;
+	int *freqs = NULL;
+
+	if (!wpa_s->hw.modes)
+		return NULL;
+
+	for (i = 0; i < wpa_s->hw.num_modes; i++) {
+		struct hostapd_hw_modes *mode = &wpa_s->hw.modes[i];
+
+		for (j = 0; j < mode->num_channels; j++) {
+			struct hostapd_channel_data *chan = &mode->channels[j];
+
+			/* All 20 MHz channels on 2.4 and 5 GHz band */
+			if (chan->freq < 2412 || chan->freq > 5900)
+				continue;
+
+			/* that allow frames to be transmitted */
+			if (chan->flag & (HOSTAPD_CHAN_DISABLED |
+					  HOSTAPD_CHAN_NO_IR |
+					  HOSTAPD_CHAN_RADAR))
+				continue;
+
+			int_array_add_unique(&freqs, chan->freq);
+		}
+	}
+
+	return freqs;
+}
diff --git a/wpa_supplicant/nan_usd.h b/wpa_supplicant/nan_usd.h
new file mode 100644
index 0000000..149ac9e
--- /dev/null
+++ b/wpa_supplicant/nan_usd.h
@@ -0,0 +1,46 @@
+/*
+ * NAN unsynchronized service discovery (USD)
+ * Copyright (c) 2024, Qualcomm Innovation Center, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef NAN_USD_H
+#define NAN_USD_H
+
+struct nan_subscribe_params;
+struct nan_publish_params;
+enum nan_service_protocol_type;
+
+int wpas_nan_usd_init(struct wpa_supplicant *wpa_s);
+void wpas_nan_usd_deinit(struct wpa_supplicant *wpa_s);
+void wpas_nan_usd_rx_sdf(struct wpa_supplicant *wpa_s, const u8 *src,
+			 unsigned int freq, const u8 *buf, size_t len);
+void wpas_nan_usd_flush(struct wpa_supplicant *wpa_s);
+int wpas_nan_usd_publish(struct wpa_supplicant *wpa_s, const char *service_name,
+			 enum nan_service_protocol_type srv_proto_type,
+			 const struct wpabuf *ssi,
+			 struct nan_publish_params *params);
+void wpas_nan_usd_cancel_publish(struct wpa_supplicant *wpa_s, int publish_id);
+int wpas_nan_usd_update_publish(struct wpa_supplicant *wpa_s, int publish_id,
+				const struct wpabuf *ssi);
+int wpas_nan_usd_subscribe(struct wpa_supplicant *wpa_s,
+			   const char *service_name,
+			   enum nan_service_protocol_type srv_proto_type,
+			   const struct wpabuf *ssi,
+			   struct nan_subscribe_params *params);
+void wpas_nan_usd_cancel_subscribe(struct wpa_supplicant *wpa_s,
+				   int subscribe_id);
+int wpas_nan_usd_transmit(struct wpa_supplicant *wpa_s, int handle,
+			  const struct wpabuf *ssi, const struct wpabuf *elems,
+			  const u8 *peer_addr, u8 req_instance_id);
+void wpas_nan_usd_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+				       unsigned int freq,
+				       unsigned int duration);
+void wpas_nan_usd_cancel_remain_on_channel_cb(struct wpa_supplicant *wpa_s,
+					      unsigned int freq);
+void wpas_nan_usd_tx_wait_expire(struct wpa_supplicant *wpa_s);
+int * wpas_nan_usd_all_freqs(struct wpa_supplicant *wpa_s);
+
+#endif /* NAN_USD_H */
diff --git a/wpa_supplicant/notify.c b/wpa_supplicant/notify.c
index a1746da..6f162f2 100644
--- a/wpa_supplicant/notify.c
+++ b/wpa_supplicant/notify.c
@@ -1103,15 +1103,6 @@
 #endif /* CONFIG_HS20 */
 }
 
-void wpas_notify_hs20_rx_terms_and_conditions_acceptance(
-		struct wpa_supplicant *wpa_s, const char *url) {
-#ifdef CONFIG_HS20
-	if (!wpa_s || !url)
-		return;
-
-	wpas_aidl_notify_hs20_rx_terms_and_conditions_acceptance(wpa_s, url);
-#endif /* CONFIG_HS20 */
-}
 
 #ifdef CONFIG_MESH
 
@@ -1437,3 +1428,16 @@
 
 	wpas_aidl_notify_qos_policy_scs_response(wpa_s, num_scs_resp, scs_resp);
 }
+
+void wpas_notify_hs20_t_c_acceptance(struct wpa_supplicant *wpa_s,
+				     const char *url)
+{
+#ifdef CONFIG_HS20
+	if (!wpa_s || !url)
+		return;
+
+	wpa_msg(wpa_s, MSG_INFO, HS20_T_C_ACCEPTANCE "%s", url);
+	wpas_aidl_notify_hs20_rx_terms_and_conditions_acceptance(wpa_s, url);
+	wpas_dbus_signal_hs20_t_c_acceptance(wpa_s, url);
+#endif /* CONFIG_HS20 */
+}
diff --git a/wpa_supplicant/notify.h b/wpa_supplicant/notify.h
index 260f439..d4656ad 100644
--- a/wpa_supplicant/notify.h
+++ b/wpa_supplicant/notify.h
@@ -182,8 +182,6 @@
 void wpas_notify_hs20_rx_deauth_imminent_notice(struct wpa_supplicant *wpa_s,
 						u8 code, u16 reauth_delay,
 						const char *url);
-void wpas_notify_hs20_rx_terms_and_conditions_acceptance(
-		struct wpa_supplicant *wpa_s, const char *url);
 void wpas_notify_dpp_config_received(struct wpa_supplicant *wpa_s,
 		struct wpa_ssid *ssid, bool conn_status_requested);
 void wpas_notify_dpp_config_sent(struct wpa_supplicant *wpa_s);
@@ -232,5 +230,7 @@
 		unsigned int num_scs_resp, int **scs_resp);
 void wpas_notify_mlo_info_change_reason(struct wpa_supplicant *wpa_s,
 					enum mlo_info_change_reason reason);
+void wpas_notify_hs20_t_c_acceptance(struct wpa_supplicant *wpa_s,
+				     const char *url);
 
 #endif /* NOTIFY_H */
diff --git a/wpa_supplicant/offchannel.c b/wpa_supplicant/offchannel.c
index e40cf5b..9e591d7 100644
--- a/wpa_supplicant/offchannel.c
+++ b/wpa_supplicant/offchannel.c
@@ -23,12 +23,12 @@
 {
 	struct wpa_supplicant *iface;
 
-	if (os_memcmp(src, wpa_s->own_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(src, wpa_s->own_addr)) {
 #ifdef CONFIG_P2P
 		if (wpa_s->p2p_mgmt && wpa_s != wpa_s->parent &&
 		    wpa_s->parent->ap_iface &&
-		    os_memcmp(wpa_s->parent->own_addr,
-			      wpa_s->own_addr, ETH_ALEN) == 0 &&
+		    ether_addr_equal(wpa_s->parent->own_addr,
+				     wpa_s->own_addr) &&
 		    wpabuf_len(wpa_s->pending_action_tx) >= 2 &&
 		    *wpabuf_head_u8(wpa_s->pending_action_tx) !=
 		    WLAN_ACTION_PUBLIC) {
@@ -52,7 +52,7 @@
 	 */
 	iface = wpa_s->global->ifaces;
 	while (iface) {
-		if (os_memcmp(src, iface->own_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(src, iface->own_addr))
 			break;
 		iface = iface->next;
 	}
@@ -186,7 +186,7 @@
 		return;
 	}
 
-	if (os_memcmp(dst, wpa_s->pending_action_dst, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(dst, wpa_s->pending_action_dst)) {
 		wpa_printf(MSG_DEBUG, "Off-channel: Ignore Action TX status - "
 			   "unknown destination address");
 		return;
diff --git a/wpa_supplicant/p2p_supplicant.c b/wpa_supplicant/p2p_supplicant.c
index c150863..3c121c8 100644
--- a/wpa_supplicant/p2p_supplicant.c
+++ b/wpa_supplicant/p2p_supplicant.c
@@ -1187,7 +1187,7 @@
 		   "group (GO Dev Addr " MACSTR ")", MAC2STR(go_dev_addr));
 	for (s = wpa_s->conf->ssid; s; s = s->next) {
 		if (s->disabled == 2 &&
-		    os_memcmp(go_dev_addr, s->bssid, ETH_ALEN) == 0 &&
+		    ether_addr_equal(go_dev_addr, s->bssid) &&
 		    s->ssid_len == ssid->ssid_len &&
 		    os_memcmp(ssid->ssid, s->ssid, ssid->ssid_len) == 0)
 			break;
@@ -1293,8 +1293,8 @@
 		return;
 
 	for (i = 0; s->p2p_client_list && i < s->num_p2p_clients; i++) {
-		if (os_memcmp(s->p2p_client_list + i * 2 * ETH_ALEN, addr,
-			      ETH_ALEN) != 0)
+		if (!ether_addr_equal(s->p2p_client_list + i * 2 * ETH_ALEN,
+				      addr))
 			continue;
 
 		if (i == s->num_p2p_clients - 1)
@@ -1595,8 +1595,8 @@
 
 	if (result != OFFCHANNEL_SEND_ACTION_SUCCESS &&
 	    wpa_s->pending_pd_before_join &&
-	    (os_memcmp(dst, wpa_s->pending_join_dev_addr, ETH_ALEN) == 0 ||
-	     os_memcmp(dst, wpa_s->pending_join_iface_addr, ETH_ALEN) == 0) &&
+	    (ether_addr_equal(dst, wpa_s->pending_join_dev_addr) ||
+	     ether_addr_equal(dst, wpa_s->pending_join_iface_addr)) &&
 	    wpa_s->p2p_fallback_to_go_neg) {
 		wpa_s->pending_pd_before_join = 0;
 		wpa_dbg(wpa_s, MSG_DEBUG, "P2P: No ACK for PD Req "
@@ -2937,8 +2937,8 @@
 	char params[20];
 
 	if (wpa_s->pending_pd_before_join &&
-	    (os_memcmp(peer, wpa_s->pending_join_dev_addr, ETH_ALEN) == 0 ||
-	     os_memcmp(peer, wpa_s->pending_join_iface_addr, ETH_ALEN) == 0)) {
+	    (ether_addr_equal(peer, wpa_s->pending_join_dev_addr) ||
+	     ether_addr_equal(peer, wpa_s->pending_join_iface_addr))) {
 		wpa_s->pending_pd_before_join = 0;
 		wpa_printf(MSG_DEBUG, "P2P: Starting pending "
 			   "join-existing-group operation");
@@ -3232,9 +3232,8 @@
 			   " to join an active group (SSID: %s)",
 			   MAC2STR(sa), wpa_ssid_txt(ssid, ssid_len));
 		if (!is_zero_ether_addr(wpa_s->p2p_auth_invite) &&
-		    (os_memcmp(go_dev_addr, wpa_s->p2p_auth_invite, ETH_ALEN)
-		     == 0 ||
-		     os_memcmp(sa, wpa_s->p2p_auth_invite, ETH_ALEN) == 0)) {
+		    (ether_addr_equal(go_dev_addr, wpa_s->p2p_auth_invite) ||
+		     ether_addr_equal(sa, wpa_s->p2p_auth_invite))) {
 			wpa_printf(MSG_DEBUG, "P2P: Accept previously "
 				   "authorized invitation");
 			goto accept_inv;
@@ -3271,7 +3270,7 @@
 	}
 
 	if (!is_zero_ether_addr(wpa_s->p2p_auth_invite) &&
-	    os_memcmp(sa, wpa_s->p2p_auth_invite, ETH_ALEN) == 0) {
+	    ether_addr_equal(sa, wpa_s->p2p_auth_invite)) {
 		wpa_printf(MSG_DEBUG, "P2P: Accept previously initiated "
 			   "invitation to re-invoke a persistent group");
 		os_memset(wpa_s->p2p_auth_invite, 0, ETH_ALEN);
@@ -3280,7 +3279,7 @@
 
 	for (s = wpa_s->conf->ssid; s; s = s->next) {
 		if (s->disabled == 2 &&
-		    os_memcmp(s->bssid, go_dev_addr, ETH_ALEN) == 0 &&
+		    ether_addr_equal(s->bssid, go_dev_addr) &&
 		    s->ssid_len == ssid_len &&
 		    os_memcmp(ssid, s->ssid, ssid_len) == 0)
 			break;
@@ -3482,13 +3481,13 @@
 		return;
 
 	for (i = 0; ssid->p2p_client_list && i < ssid->num_p2p_clients; i++) {
-		if (os_memcmp(ssid->p2p_client_list + i * 2 * ETH_ALEN, peer,
-			      ETH_ALEN) == 0)
+		if (ether_addr_equal(ssid->p2p_client_list + i * 2 * ETH_ALEN,
+				     peer))
 			break;
 	}
 	if (i >= ssid->num_p2p_clients || !ssid->p2p_client_list) {
 		if (ssid->mode != WPAS_MODE_P2P_GO &&
-		    os_memcmp(ssid->bssid, peer, ETH_ALEN) == 0) {
+		    ether_addr_equal(ssid->bssid, peer)) {
 			wpa_printf(MSG_DEBUG, "P2P: Remove persistent group %d "
 				   "due to invitation result", ssid->id);
 			wpas_notify_network_removed(wpa_s, ssid);
@@ -4185,7 +4184,7 @@
 	struct wpa_supplicant *wpa_s = ctx;
 
 	for (wpa_s = wpa_s->global->ifaces; wpa_s; wpa_s = wpa_s->next) {
-		if (os_memcmp(wpa_s->own_addr, interface_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(wpa_s->own_addr, interface_addr))
 			break;
 	}
 	if (wpa_s == NULL)
@@ -4224,7 +4223,7 @@
 		struct wpa_ssid *ssid = wpa_s->current_ssid;
 		if (ssid && (ssid->mode != WPAS_MODE_INFRA || !ssid->p2p_group))
 			continue;
-		if (os_memcmp(wpa_s->go_dev_addr, peer_dev_addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(wpa_s->go_dev_addr, peer_dev_addr))
 			return wpa_s;
 	}
 
@@ -4435,7 +4434,7 @@
 	while ((s = wpas_p2p_get_persistent(wpa_s, peer, NULL, 0))) {
 		if (go && ssid && ssid_len &&
 		    s->ssid_len == ssid_len &&
-		    os_memcmp(go, s->bssid, ETH_ALEN) == 0 &&
+		    ether_addr_equal(go, s->bssid) &&
 		    os_memcmp(ssid, s->ssid, ssid_len) == 0)
 			break;
 
@@ -4451,8 +4450,8 @@
 		}
 
 		for (i = 0; i < s->num_p2p_clients; i++) {
-			if (os_memcmp(s->p2p_client_list + i * 2 * ETH_ALEN,
-				      peer, ETH_ALEN) != 0)
+			if (!ether_addr_equal(s->p2p_client_list +
+					      i * 2 * ETH_ALEN, peer))
 				continue;
 
 			os_memmove(s->p2p_client_list + i * 2 * ETH_ALEN,
@@ -4606,7 +4605,7 @@
 			break;
 
 		if (s && s->ssid_len == stale->ssid_len &&
-		    os_memcmp(stale->bssid, s->bssid, ETH_ALEN) == 0 &&
+		    ether_addr_equal(stale->bssid, s->bssid) &&
 		    os_memcmp(stale->ssid, s->ssid, s->ssid_len) == 0)
 			break;
 
@@ -4622,9 +4621,8 @@
 			size_t i;
 
 			for (i = 0; i < stale->num_p2p_clients; i++) {
-				if (os_memcmp(stale->p2p_client_list +
-					      i * ETH_ALEN,
-					      dev, ETH_ALEN) == 0) {
+				if (ether_addr_equal(stale->p2p_client_list +
+						     i * ETH_ALEN, dev)) {
 					os_memmove(stale->p2p_client_list +
 						   i * ETH_ALEN,
 						   stale->p2p_client_list +
@@ -5474,8 +5472,8 @@
 	    p2p_get_interface_addr(wpa_s->global->p2p,
 				   wpa_s->pending_join_dev_addr,
 				   iface_addr) == 0 &&
-	    os_memcmp(iface_addr, wpa_s->pending_join_dev_addr, ETH_ALEN) != 0
-	    && !wpa_bss_get_bssid(wpa_s, wpa_s->pending_join_iface_addr)) {
+	    !ether_addr_equal(iface_addr, wpa_s->pending_join_dev_addr) &&
+	    !wpa_bss_get_bssid(wpa_s, wpa_s->pending_join_iface_addr)) {
 		wpa_printf(MSG_DEBUG, "P2P: Overwrite pending interface "
 			   "address for join from " MACSTR " to " MACSTR
 			   " based on newly discovered P2P peer entry",
@@ -5515,10 +5513,9 @@
 			   wpa_ssid_txt(bss->ssid, bss->ssid_len));
 		if (p2p_parse_dev_addr(wpa_bss_ie_ptr(bss), bss->ie_len,
 				       dev_addr) == 0 &&
-		    os_memcmp(wpa_s->pending_join_dev_addr,
-			      wpa_s->pending_join_iface_addr, ETH_ALEN) == 0 &&
-		    os_memcmp(dev_addr, wpa_s->pending_join_dev_addr,
-			      ETH_ALEN) != 0) {
+		    ether_addr_equal(wpa_s->pending_join_dev_addr,
+				     wpa_s->pending_join_iface_addr) &&
+		    !ether_addr_equal(dev_addr, wpa_s->pending_join_dev_addr)) {
 			wpa_printf(MSG_DEBUG,
 				   "P2P: Update target GO device address based on BSS entry: " MACSTR " (was " MACSTR ")",
 				   MAC2STR(dev_addr),
@@ -7504,9 +7501,10 @@
 }
 
 
-static void wpas_p2p_clear_pending_action_tx(struct wpa_supplicant *wpa_s)
+static void wpas_p2p_clear_pending_action_tx(struct wpa_supplicant *wpa_s,
+					     bool force)
 {
-	if (!offchannel_pending_action_tx(wpa_s))
+	if (!offchannel_pending_action_tx(wpa_s) && !force)
 		return;
 
 	if (wpa_s->p2p_send_action_work) {
@@ -7516,6 +7514,8 @@
 		offchannel_send_action_done(wpa_s);
 	}
 
+	if (!offchannel_pending_action_tx(wpa_s))
+		return;
 	wpa_printf(MSG_DEBUG, "P2P: Drop pending Action TX due to new "
 		   "operation request");
 	offchannel_clear_pending_action_tx(wpa_s);
@@ -7529,7 +7529,7 @@
 		  u8 seek_cnt, const char **seek_string, int freq,
 		  bool include_6ghz)
 {
-	wpas_p2p_clear_pending_action_tx(wpa_s);
+	wpas_p2p_clear_pending_action_tx(wpa_s, false);
 	wpa_s->global->p2p_long_listen = 0;
 
 	if (wpa_s->global->p2p_disabled || wpa_s->global->p2p == NULL ||
@@ -7575,7 +7575,7 @@
 
 static void wpas_p2p_stop_find_oper(struct wpa_supplicant *wpa_s)
 {
-	wpas_p2p_clear_pending_action_tx(wpa_s);
+	wpas_p2p_clear_pending_action_tx(wpa_s, true);
 	wpa_s->global->p2p_long_listen = 0;
 	eloop_cancel_timeout(wpas_p2p_long_listen_timeout, wpa_s, NULL);
 	eloop_cancel_timeout(wpas_p2p_join_scan, wpa_s, NULL);
@@ -7620,7 +7620,7 @@
 	}
 
 	wpa_supplicant_cancel_sched_scan(wpa_s);
-	wpas_p2p_clear_pending_action_tx(wpa_s);
+	wpas_p2p_clear_pending_action_tx(wpa_s, false);
 
 	if (timeout == 0) {
 		/*
@@ -8740,13 +8740,13 @@
 				return s;
 			continue;
 		}
-		if (os_memcmp(s->bssid, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(s->bssid, addr))
 			return s; /* peer is GO in the persistent group */
 		if (s->mode != WPAS_MODE_P2P_GO || s->p2p_client_list == NULL)
 			continue;
 		for (i = 0; i < s->num_p2p_clients; i++) {
-			if (os_memcmp(s->p2p_client_list + i * 2 * ETH_ALEN,
-				      addr, ETH_ALEN) == 0)
+			if (ether_addr_equal(s->p2p_client_list +
+					     i * 2 * ETH_ALEN, addr))
 				return s; /* peer is P2P client in persistent
 					   * group */
 		}
@@ -8888,9 +8888,9 @@
 	dl_list_for_each_safe(psk, tmp, &s->psk_list, struct psk_list_entry,
 			      list) {
 		if ((iface_addr && !psk->p2p &&
-		     os_memcmp(addr, psk->addr, ETH_ALEN) == 0) ||
+		     ether_addr_equal(addr, psk->addr)) ||
 		    (!iface_addr && psk->p2p &&
-		     os_memcmp(addr, psk->addr, ETH_ALEN) == 0)) {
+		     ether_addr_equal(addr, psk->addr))) {
 			wpa_dbg(wpa_s, MSG_DEBUG,
 				"P2P: Remove persistent group PSK list entry for "
 				MACSTR " p2p=%u",
@@ -9029,9 +9029,9 @@
 	prev = NULL;
 	psk = hapd->conf->ssid.wpa_psk;
 	while (psk) {
-		if ((iface_addr && os_memcmp(peer, psk->addr, ETH_ALEN) == 0) ||
+		if ((iface_addr && ether_addr_equal(peer, psk->addr)) ||
 		    (!iface_addr &&
-		     os_memcmp(peer, psk->p2p_dev_addr, ETH_ALEN) == 0)) {
+		     ether_addr_equal(peer, psk->p2p_dev_addr))) {
 			wpa_dbg(wpa_s, MSG_DEBUG, "P2P: Remove operating group PSK entry for "
 				MACSTR " iface_addr=%d",
 				MAC2STR(peer), iface_addr);
@@ -9885,6 +9885,7 @@
 	os_memset(&csa_settings, 0, sizeof(csa_settings));
 	csa_settings.cs_count = P2P_GO_CSA_COUNT;
 	csa_settings.block_tx = P2P_GO_CSA_BLOCK_TX;
+	csa_settings.link_id = -1;
 	csa_settings.freq_params.freq = params.freq;
 	csa_settings.freq_params.sec_channel_offset = conf->secondary_channel;
 	csa_settings.freq_params.ht_enabled = conf->ieee80211n;
diff --git a/wpa_supplicant/pasn_supplicant.c b/wpa_supplicant/pasn_supplicant.c
index edecfde..2e65cf0 100644
--- a/wpa_supplicant/pasn_supplicant.c
+++ b/wpa_supplicant/pasn_supplicant.c
@@ -320,7 +320,7 @@
 		return -1;
 	}
 
-	if (os_memcmp(entry->own_addr, own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(entry->own_addr, own_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "PASN: own addr " MACSTR " and PTKSA entry own addr "
 			   MACSTR " differ",
@@ -352,7 +352,7 @@
 	while (wpa_s->pasn_count < pasn_params->num_peers) {
 		peer = &pasn_params->peer[wpa_s->pasn_count];
 
-		if (os_memcmp(wpa_s->bssid, peer->peer_addr, ETH_ALEN) == 0) {
+		if (ether_addr_equal(wpa_s->bssid, peer->peer_addr)) {
 			wpa_printf(MSG_DEBUG,
 				   "PASN: Associated peer is not expected");
 			peer->status = PASN_STATUS_FAILURE;
@@ -426,6 +426,7 @@
 }
 
 
+#ifdef CONFIG_FILS
 static void wpas_pasn_initiate_eapol(struct pasn_data *pasn,
 				     struct wpa_ssid *ssid)
 {
@@ -443,6 +444,7 @@
 
 	eapol_sm_notify_config(pasn->eapol, &ssid->eap, &eapol_conf);
 }
+#endif /* CONFIG_FILS */
 
 
 static void wpas_pasn_reset(struct wpa_supplicant *wpa_s)
@@ -466,13 +468,13 @@
 	struct wpa_ie_data rsne_data;
 	int ret;
 
-	if (os_memcmp(wpa_s->bssid, peer_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(wpa_s->bssid, peer_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "PASN: Not doing authentication with current BSS");
 		return NULL;
 	}
 
-	bss = wpa_bss_get_bssid(wpa_s, peer_addr);
+	bss = wpa_bss_get_bssid_latest(wpa_s, peer_addr);
 	if (!bss) {
 		wpa_printf(MSG_DEBUG, "PASN: BSS not found");
 		return NULL;
@@ -509,8 +511,10 @@
 	struct wpa_ssid *ssid;
 	struct wpa_bss *bss;
 	const u8 *rsne, *rsnxe;
+#ifdef CONFIG_FILS
 	const u8 *indic;
 	u16 fils_info;
+#endif /* CONFIG_FILS */
 	u16 capab = 0;
 	bool derive_kdk;
 	int ret;
@@ -921,7 +925,7 @@
 	struct ieee80211_mgmt *deauth;
 	int ret;
 
-	if (os_memcmp(wpa_s->bssid, peer_addr, ETH_ALEN) == 0) {
+	if (ether_addr_equal(wpa_s->bssid, peer_addr)) {
 		wpa_printf(MSG_DEBUG,
 			   "PASN: Cannot deauthenticate from current BSS");
 		return -1;
diff --git a/wpa_supplicant/robust_av.c b/wpa_supplicant/robust_av.c
index 58edd9b..658103d 100644
--- a/wpa_supplicant/robust_av.c
+++ b/wpa_supplicant/robust_av.c
@@ -665,17 +665,91 @@
 }
 
 
+/* Element ID Extension(1) + Request Type(1) + User Priority Control(2) +
+ * Stream Timeout(4) */
+#define MSCS_DESCRIPTOR_FIXED_LEN 8
+
+static void wpas_parse_mscs_resp(struct wpa_supplicant *wpa_s,
+				 u16 status, const u8 *bssid,
+				 const u8 *mscs_desc_ie)
+{
+	struct robust_av_data robust_av;
+	const u8 *pos;
+
+	/* The MSCS Descriptor element is optional in the MSCS Response frame */
+	if (!mscs_desc_ie)
+		goto event_mscs_result;
+
+	if (mscs_desc_ie[1] < MSCS_DESCRIPTOR_FIXED_LEN) {
+		wpa_printf(MSG_INFO,
+			   "MSCS: Drop received frame: invalid MSCS Descriptor element length: %d",
+			   mscs_desc_ie[1]);
+		return;
+	}
+
+	os_memset(&robust_av, 0, sizeof(struct robust_av_data));
+
+	/* Skip Element ID, Length, and Element ID Extension */
+	pos = &mscs_desc_ie[3];
+
+	robust_av.request_type = *pos++;
+
+	switch (robust_av.request_type) {
+	case SCS_REQ_CHANGE:
+		/*
+		 * Inform the suggested set of parameters that could be accepted
+		 * by the AP in response to a subsequent request by the station.
+		 */
+		robust_av.up_bitmap = *pos++;
+		robust_av.up_limit = *pos++ & 0x07;
+		robust_av.stream_timeout = WPA_GET_LE32(pos);
+		wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_MSCS_RESULT "bssid=" MACSTR
+			" status_code=%u change up_bitmap=%u up_limit=%u stream_timeout=%u",
+			MAC2STR(bssid), status, robust_av.up_bitmap,
+			robust_av.up_limit, robust_av.stream_timeout);
+		wpa_s->mscs_setup_done = false;
+		return;
+	case SCS_REQ_ADD:
+		/*
+		 * This type is used in (Re)Association Response frame MSCS
+		 * Descriptor element if no change is required.
+		 */
+		break;
+	default:
+		wpa_printf(MSG_INFO,
+			   "MSCS: Drop received frame with unknown Request Type: %u",
+			   robust_av.request_type);
+		return;
+	}
+
+event_mscs_result:
+	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_MSCS_RESULT "bssid=" MACSTR
+		" status_code=%u", MAC2STR(bssid), status);
+	wpa_s->mscs_setup_done = status == WLAN_STATUS_SUCCESS;
+}
+
+
 void wpas_handle_robust_av_recv_action(struct wpa_supplicant *wpa_s,
 				       const u8 *src, const u8 *buf, size_t len)
 {
 	u8 dialog_token;
 	u16 status_code;
+	const u8 *mscs_desc_ie;
 
 	if (len < 3)
 		return;
 
 	dialog_token = *buf++;
-	if (dialog_token != wpa_s->robust_av.dialog_token) {
+	len--;
+
+	/* AP sets dialog token to 0 for unsolicited response */
+	if (!dialog_token && !wpa_s->mscs_setup_done) {
+		wpa_printf(MSG_INFO,
+			   "MSCS: Drop unsolicited received frame: inactive");
+		return;
+	}
+
+	if (dialog_token && dialog_token != wpa_s->robust_av.dialog_token) {
 		wpa_printf(MSG_INFO,
 			   "MSCS: Drop received frame due to dialog token mismatch: received:%u expected:%u",
 			   dialog_token, wpa_s->robust_av.dialog_token);
@@ -683,9 +757,11 @@
 	}
 
 	status_code = WPA_GET_LE16(buf);
-	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_MSCS_RESULT "bssid=" MACSTR
-		" status_code=%u", MAC2STR(src), status_code);
-	wpa_s->mscs_setup_done = status_code == WLAN_STATUS_SUCCESS;
+	buf += 2;
+	len -= 2;
+
+	mscs_desc_ie = get_ie_ext(buf, len, WLAN_EID_EXT_MSCS_DESCRIPTOR);
+	wpas_parse_mscs_resp(wpa_s, status_code, src, mscs_desc_ie);
 }
 
 
@@ -701,21 +777,19 @@
 		return;
 
 	mscs_desc_ie = get_ie_ext(ies, ies_len, WLAN_EID_EXT_MSCS_DESCRIPTOR);
-	if (!mscs_desc_ie || mscs_desc_ie[1] <= 8)
+	if (!mscs_desc_ie || mscs_desc_ie[1] <= MSCS_DESCRIPTOR_FIXED_LEN)
 		return;
 
-	/* Subelements start after (ie_id(1) + ie_len(1) + ext_id(1) +
-	 * request type(1) + upc(2) + stream timeout(4) =) 10.
-	 */
-	mscs_status = get_ie(&mscs_desc_ie[10], mscs_desc_ie[1] - 8,
+	/* Subelements start after element header and fixed fields */
+	mscs_status = get_ie(&mscs_desc_ie[2 + MSCS_DESCRIPTOR_FIXED_LEN],
+			     mscs_desc_ie[1] - MSCS_DESCRIPTOR_FIXED_LEN,
 			     MCSC_SUBELEM_STATUS);
 	if (!mscs_status || mscs_status[1] < 2)
 		return;
 
 	status = WPA_GET_LE16(mscs_status + 2);
-	wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_MSCS_RESULT "bssid=" MACSTR
-		" status_code=%u", MAC2STR(bssid), status);
-	wpa_s->mscs_setup_done = status == WLAN_STATUS_SUCCESS;
+
+	wpas_parse_mscs_resp(wpa_s, status, bssid, mscs_desc_ie);
 }
 
 
diff --git a/wpa_supplicant/rrm.c b/wpa_supplicant/rrm.c
index 8e51717..7ce854b 100644
--- a/wpa_supplicant/rrm.c
+++ b/wpa_supplicant/rrm.c
@@ -772,6 +772,7 @@
 
 
 static int wpas_beacon_rep_add_frame_body(struct bitfield *eids,
+					  struct bitfield *ext_eids,
 					  enum beacon_report_detail detail,
 					  struct wpa_bss *bss, u8 *buf,
 					  size_t buf_len, const u8 **ies_buf,
@@ -828,7 +829,9 @@
 	 */
 	while (ies_len > 2 && 2U + ies[1] <= ies_len && rem_len > 0) {
 		if (detail == BEACON_REPORT_DETAIL_ALL_FIELDS_AND_ELEMENTS ||
-		    (eids && bitfield_is_set(eids, ies[0]))) {
+		    (eids && bitfield_is_set(eids, ies[0])) ||
+		    (ext_eids && ies[0] == WLAN_EID_EXTENSION && ies[1] &&
+		     bitfield_is_set(ext_eids, ies[2]))) {
 			u8 elen = ies[1];
 
 			if (2 + elen > buf + buf_len - pos ||
@@ -876,7 +879,8 @@
 
 	os_memcpy(buf, rep, sizeof(*rep));
 
-	ret = wpas_beacon_rep_add_frame_body(data->eids, data->report_detail,
+	ret = wpas_beacon_rep_add_frame_body(data->eids, data->ext_eids,
+					     data->report_detail,
 					     bss, buf + sizeof(*rep),
 					     14 + *ie_len, ie, ie_len,
 					     idx == 0);
@@ -932,8 +936,8 @@
 	struct rrm_measurement_beacon_report rep;
 	u8 idx = 0;
 
-	if (os_memcmp(data->bssid, broadcast_ether_addr, ETH_ALEN) != 0 &&
-	    os_memcmp(data->bssid, bss->bssid, ETH_ALEN) != 0)
+	if (!ether_addr_equal(data->bssid, broadcast_ether_addr) &&
+	    !ether_addr_equal(data->bssid, bss->bssid))
 		return 0;
 
 	if (data->ssid_len &&
@@ -1043,6 +1047,7 @@
 					     struct beacon_rep_data *data,
 					     u8 sid, u8 slen, const u8 *subelem)
 {
+	struct bitfield *eids;
 	u8 report_info, i;
 
 	switch (sid) {
@@ -1096,6 +1101,7 @@
 
 		break;
 	case WLAN_BEACON_REQUEST_SUBELEM_REQUEST:
+	case WLAN_BEACON_REQUEST_SUBELEM_EXT_REQUEST:
 		if (data->report_detail !=
 		    BEACON_REPORT_DETAIL_REQUESTED_ONLY) {
 			wpa_printf(MSG_DEBUG,
@@ -1111,20 +1117,46 @@
 			return -1;
 		}
 
-		if (data->eids) {
+		if (sid == WLAN_BEACON_REQUEST_SUBELEM_EXT_REQUEST) {
+			if (slen < 2) {
+				wpa_printf(MSG_DEBUG,
+					   "Invalid extended request");
+				return -1;
+			}
+			if (subelem[0] != WLAN_EID_EXTENSION) {
+				wpa_printf(MSG_DEBUG,
+					   "Skip unknown Requested Element ID %u in Extended Request subelement",
+					   subelem[0]);
+				break;
+			}
+
+			/* Skip the Requested Element ID field */
+			subelem++;
+			slen--;
+		}
+
+		if ((sid == WLAN_BEACON_REQUEST_SUBELEM_REQUEST &&
+		     data->eids) ||
+		    (sid == WLAN_BEACON_REQUEST_SUBELEM_EXT_REQUEST &&
+		    data->ext_eids)) {
 			wpa_printf(MSG_DEBUG,
-				   "Beacon Request: Request subelement appears more than once");
+				   "Beacon Request: Request sub elements appear more than once");
 			return -1;
 		}
 
-		data->eids = bitfield_alloc(255);
-		if (!data->eids) {
+		eids = bitfield_alloc(255);
+		if (!eids) {
 			wpa_printf(MSG_DEBUG, "Failed to allocate EIDs bitmap");
 			return -1;
 		}
 
+		if (sid == WLAN_BEACON_REQUEST_SUBELEM_REQUEST)
+			data->eids = eids;
+		else
+			data->ext_eids = eids;
+
 		for (i = 0; i < slen; i++)
-			bitfield_set(data->eids, subelem[i]);
+			bitfield_set(eids, subelem[i]);
 		break;
 	case WLAN_BEACON_REQUEST_SUBELEM_AP_CHANNEL:
 		/* Skip - it will be processed when freqs are added */
@@ -1480,6 +1512,29 @@
 }
 
 
+static bool wpas_beacon_rep_scan_match(struct wpa_supplicant *wpa_s,
+				       const u8 *bssid)
+{
+	u8 i;
+
+	if (!wpa_s->valid_links)
+		return ether_addr_equal(wpa_s->current_bss->bssid, bssid);
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(wpa_s->valid_links & BIT(i)))
+			continue;
+
+		if (ether_addr_equal(wpa_s->links[i].bssid, bssid))
+			return true;
+	}
+
+	wpa_printf(MSG_DEBUG, "RRM: MLD: no match for TSF BSSID=" MACSTR,
+		   MAC2STR(bssid));
+
+	return false;
+}
+
+
 int wpas_beacon_rep_scan_process(struct wpa_supplicant *wpa_s,
 				 struct wpa_scan_results *scan_res,
 				 struct scan_info *info)
@@ -1501,8 +1556,7 @@
 		   MAC2STR(info->scan_start_tsf_bssid),
 		   MAC2STR(wpa_s->current_bss->bssid));
 	if ((wpa_s->drv_rrm_flags & WPA_DRIVER_FLAGS_SUPPORT_BEACON_REPORT) &&
-	    os_memcmp(info->scan_start_tsf_bssid, wpa_s->current_bss->bssid,
-		      ETH_ALEN) != 0) {
+	    !wpas_beacon_rep_scan_match(wpa_s, info->scan_start_tsf_bssid)) {
 		wpa_printf(MSG_DEBUG,
 			   "RRM: Ignore scan results due to mismatching TSF BSSID");
 		goto out;
@@ -1517,8 +1571,8 @@
 
 		if ((wpa_s->drv_rrm_flags &
 		     WPA_DRIVER_FLAGS_SUPPORT_BEACON_REPORT) &&
-		    os_memcmp(scan_res->res[i]->tsf_bssid,
-			      wpa_s->current_bss->bssid, ETH_ALEN) != 0) {
+		    !wpas_beacon_rep_scan_match(wpa_s,
+						scan_res->res[i]->tsf_bssid)) {
 			wpa_printf(MSG_DEBUG,
 				   "RRM: Ignore scan result for " MACSTR
 				   " due to mismatching TSF BSSID" MACSTR,
@@ -1587,6 +1641,7 @@
 
 	eloop_cancel_timeout(wpas_rrm_scan_timeout, wpa_s, NULL);
 	bitfield_free(data->eids);
+	bitfield_free(data->ext_eids);
 	os_free(data->scan_params.freqs);
 	os_memset(data, 0, sizeof(*data));
 }
diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c
index bab6a23..6e6f05d 100644
--- a/wpa_supplicant/scan.c
+++ b/wpa_supplicant/scan.c
@@ -261,8 +261,10 @@
 			wpa_s->scan_res_handler = NULL;
 		}
 
+#ifndef CONFIG_NO_RRM
 		if (wpa_s->beacon_rep_data.token)
 			wpas_rrm_refuse_request(wpa_s);
+#endif /* CONFIG_NO_RRM */
 
 		return;
 	}
@@ -2253,7 +2255,6 @@
 static int wpas_channel_width_tx_pwr(const u8 *ies, size_t ies_len,
 				     enum chan_width cw)
 {
-#define MIN(a, b) (a < b ? a : b)
 	int offset = wpas_channel_width_offset(cw);
 	const struct element *elem;
 	int max_tx_power = TX_POWER_NO_CONSTRAINT, tx_pwr = 0;
@@ -2329,7 +2330,6 @@
 	}
 
 	return max_tx_power;
-#undef MIN
 }
 
 
@@ -2371,7 +2371,6 @@
  * better. */
 static int wpa_scan_result_compar(const void *a, const void *b)
 {
-#define MIN(a,b) a < b ? a : b
 	struct wpa_scan_res **_wa = (void *) a;
 	struct wpa_scan_res **_wb = (void *) b;
 	struct wpa_scan_res *wa = *_wa;
@@ -2379,6 +2378,7 @@
 	int wpa_a, wpa_b;
 	int snr_a, snr_b, snr_a_full, snr_b_full;
 	size_t ies_len;
+	const u8 *rsne_a, *rsne_b;
 
 	/* WPA/WPA2 support preferred */
 	wpa_a = wpa_scan_get_vendor_ie(wa, WPA_IE_VENDOR_TYPE) != NULL ||
@@ -2422,6 +2422,32 @@
 		snr_b = snr_b_full = wb->level;
 	}
 
+	/* If SNR of a SAE BSS is good or at least as high as the PSK BSS,
+	 * prefer SAE over PSK for mixed WPA3-Personal transition mode and
+	 * WPA2-Personal deployments */
+	rsne_a = wpa_scan_get_ie(wa, WLAN_EID_RSN);
+	rsne_b = wpa_scan_get_ie(wb, WLAN_EID_RSN);
+	if (rsne_a && rsne_b) {
+		struct wpa_ie_data data;
+		bool psk_a = false, psk_b = false, sae_a = false, sae_b = false;
+
+		if (wpa_parse_wpa_ie_rsn(rsne_a, 2 + rsne_a[1], &data) == 0) {
+			psk_a = wpa_key_mgmt_wpa_psk_no_sae(data.key_mgmt);
+			sae_a = wpa_key_mgmt_sae(data.key_mgmt);
+		}
+		if (wpa_parse_wpa_ie_rsn(rsne_b, 2 + rsne_b[1], &data) == 0) {
+			psk_b = wpa_key_mgmt_wpa_psk_no_sae(data.key_mgmt);
+			sae_b = wpa_key_mgmt_sae(data.key_mgmt);
+		}
+
+		if (sae_a && !sae_b && psk_b &&
+		    (snr_a >= GREAT_SNR || snr_a >= snr_b))
+			return -1;
+		if (sae_b && !sae_a && psk_a &&
+		    (snr_b >= GREAT_SNR || snr_b >= snr_a))
+			return 1;
+	}
+
 	/* If SNR is close, decide by max rate or frequency band. For cases
 	 * involving the 6 GHz band, use the throughput estimate irrespective
 	 * of the SNR difference since the LPI/VLP rules may result in
@@ -2448,7 +2474,6 @@
 	if (snr_b_full == snr_a_full)
 		return wb->qual - wa->qual;
 	return snr_b_full - snr_a_full;
-#undef MIN
 }
 
 
@@ -2573,8 +2598,7 @@
 		return 1;
 
 	for (i = 0; i < wpa_s->bssid_filter_count; i++) {
-		if (os_memcmp(wpa_s->bssid_filter + i * ETH_ALEN, bssid,
-			      ETH_ALEN) == 0)
+		if (ether_addr_equal(wpa_s->bssid_filter + i * ETH_ALEN, bssid))
 			return 1;
 	}
 
@@ -2878,6 +2902,7 @@
 	 * been taken into account.
 	 */
 	int adjusted_snr;
+	bool ht40 = false, vht80 = false, vht160 = false;
 
 	/* Limit based on estimated SNR */
 	if (rate > 1 * 2 && snr < 1)
@@ -2933,11 +2958,14 @@
 		}
 	}
 
+	ie = get_ie(ies, ies_len, WLAN_EID_HT_OPERATION);
+	if (ie && ie[1] >= 2 &&
+	    (ie[3] & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK))
+		ht40 = true;
+
 	if (hw_mode &&
 	    (hw_mode->ht_capab & HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET)) {
-		ie = get_ie(ies, ies_len, WLAN_EID_HT_OPERATION);
-		if (ie && ie[1] >= 2 &&
-		    (ie[3] & HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
+		if (ht40) {
 			*max_cw = CHAN_WIDTH_40;
 			adjusted_snr = snr +
 				wpas_channel_width_rssi_bump(ies, ies_len,
@@ -2948,22 +2976,36 @@
 		}
 	}
 
+	/* Determine VHT BSS bandwidth based on IEEE Std 802.11-2020,
+	 * Table 11-23 (VHT BSS bandwidth) */
+	ie = get_ie(ies, ies_len, WLAN_EID_VHT_OPERATION);
+	if (ie && ie[1] >= 3) {
+		u8 cw = ie[2] & VHT_OPMODE_CHANNEL_WIDTH_MASK;
+		u8 seg0 = ie[3];
+		u8 seg1 = ie[4];
+
+		if (cw)
+			vht80 = true;
+		if (cw == 2 ||
+		    (cw == 3 && (seg1 > 0 && abs(seg1 - seg0) == 16)))
+			vht160 = true;
+		if (cw == 1 &&
+		    ((seg1 > 0 && abs(seg1 - seg0) == 8) ||
+		     (seg1 > 0 && abs(seg1 - seg0) == 16)))
+			vht160 = true;
+	}
+
 	if (hw_mode && hw_mode->vht_capab) {
 		/* Use +1 to assume VHT is always faster than HT */
 		ie = get_ie(ies, ies_len, WLAN_EID_VHT_CAP);
 		if (ie) {
-			bool vht80 = false, vht160 = false;
-
 			if (*max_cw == CHAN_WIDTH_UNKNOWN)
 				*max_cw = CHAN_WIDTH_20;
 			tmp = max_ht20_rate(snr, true) + 1;
 			if (tmp > est)
 				est = tmp;
 
-			ie = get_ie(ies, ies_len, WLAN_EID_HT_OPERATION);
-			if (ie && ie[1] >= 2 &&
-			    (ie[3] &
-			     HT_INFO_HT_PARAM_SECONDARY_CHNL_OFF_MASK)) {
+			if (ht40) {
 				*max_cw = CHAN_WIDTH_40;
 				adjusted_snr = snr +
 					wpas_channel_width_rssi_bump(
@@ -2973,26 +3015,6 @@
 					est = tmp;
 			}
 
-			/* Determine VHT BSS bandwidth based on IEEE Std
-			 * 802.11-2020, Table 11-23 (VHT BSs bandwidth) */
-			ie = get_ie(ies, ies_len, WLAN_EID_VHT_OPERATION);
-			if (ie && ie[1] >= 3) {
-				u8 cw = ie[2] & VHT_OPMODE_CHANNEL_WIDTH_MASK;
-				u8 seg0 = ie[3];
-				u8 seg1 = ie[4];
-
-				if (cw)
-					vht80 = true;
-				if (cw == 2 ||
-				    (cw == 3 &&
-				     (seg1 > 0 && abs(seg1 - seg0) == 16)))
-					vht160 = true;
-				if (cw == 1 &&
-				    ((seg1 > 0 && abs(seg1 - seg0) == 8) ||
-				     (seg1 > 0 && abs(seg1 - seg0) == 16)))
-					vht160 = true;
-			}
-
 			if (vht80) {
 				*max_cw = CHAN_WIDTH_80;
 				adjusted_snr = snr +
@@ -3052,9 +3074,10 @@
 
 		cw = he->he_phy_capab_info[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX] &
 			own_he->phy_cap[HE_PHYCAP_CHANNEL_WIDTH_SET_IDX];
-		if (cw &
-		    (IS_2P4GHZ(freq) ? HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G :
-		     HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)) {
+		if ((cw &
+		     (IS_2P4GHZ(freq) ?
+		      HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_IN_2G :
+		      HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)) && ht40) {
 			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
 			    *max_cw < CHAN_WIDTH_40)
 				*max_cw = CHAN_WIDTH_40;
@@ -3067,7 +3090,8 @@
 		}
 
 		if (!IS_2P4GHZ(freq) &&
-		    (cw & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G)) {
+		    (cw & HE_PHYCAP_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G) &&
+		    (!IS_5GHZ(freq) || vht80)) {
 			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
 			    *max_cw < CHAN_WIDTH_80)
 				*max_cw = CHAN_WIDTH_80;
@@ -3081,7 +3105,8 @@
 
 		if (!IS_2P4GHZ(freq) &&
 		    (cw & (HE_PHYCAP_CHANNEL_WIDTH_SET_160MHZ_IN_5G |
-			   HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G))) {
+			   HE_PHYCAP_CHANNEL_WIDTH_SET_80PLUS80MHZ_IN_5G)) &&
+		    (!IS_5GHZ(freq) || vht160)) {
 			if (*max_cw == CHAN_WIDTH_UNKNOWN ||
 			    *max_cw < CHAN_WIDTH_160)
 				*max_cw = CHAN_WIDTH_160;
diff --git a/wpa_supplicant/scan.h b/wpa_supplicant/scan.h
index f1739fa..8402e74 100644
--- a/wpa_supplicant/scan.h
+++ b/wpa_supplicant/scan.h
@@ -38,9 +38,6 @@
  */
 #define TX_POWER_NO_CONSTRAINT 64
 
-#define IS_2P4GHZ(n) (n >= 2412 && n <= 2484)
-#define IS_5GHZ(n) (n > 4000 && n < 5895)
-
 int wpa_supplicant_enabled_networks(struct wpa_supplicant *wpa_s);
 void wpa_supplicant_req_scan(struct wpa_supplicant *wpa_s, int sec, int usec);
 int wpa_supplicant_delayed_sched_scan(struct wpa_supplicant *wpa_s,
diff --git a/wpa_supplicant/sme.c b/wpa_supplicant/sme.c
index df2c68f..b8f7c65 100644
--- a/wpa_supplicant/sme.c
+++ b/wpa_supplicant/sme.c
@@ -1,6 +1,6 @@
 /*
  * wpa_supplicant - SME
- * Copyright (c) 2009-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2009-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -28,6 +28,7 @@
 #include "p2p_supplicant.h"
 #include "notify.h"
 #include "bss.h"
+#include "bssid_ignore.h"
 #include "scan.h"
 #include "sme.h"
 #include "hs20_supplicant.h"
@@ -165,7 +166,7 @@
 	}
 
 	if (reuse && wpa_s->sme.sae.tmp &&
-	    os_memcmp(addr, wpa_s->sme.sae.tmp->bssid, ETH_ALEN) == 0) {
+	    ether_addr_equal(addr, wpa_s->sme.sae.tmp->bssid)) {
 		wpa_printf(MSG_DEBUG,
 			   "SAE: Reuse previously generated PWE on a retry with the same AP");
 		use_pt = wpa_s->sme.sae.h2e;
@@ -242,7 +243,7 @@
 				  wpa_s->sme.sae_rejected_groups, NULL) < 0)
 		goto fail;
 	if (!use_pt &&
-	    sae_prepare_commit(wpa_s->own_addr, bssid,
+	    sae_prepare_commit(wpa_s->own_addr, addr,
 			       (u8 *) password, os_strlen(password),
 			       &wpa_s->sme.sae) < 0) {
 		wpa_printf(MSG_DEBUG, "SAE: Could not pick PWE");
@@ -378,220 +379,6 @@
 }
 
 
-static void wpas_process_tbtt_info(struct wpa_supplicant *wpa_s, const u8 *data)
-{
-	struct wpa_bss *neigh_bss;
-	const u8 *bssid;
-	u8 bss_params;
-	u8 link_id;
-
-	/* TBTT Information field
-	 * Neighbor AP TBTT Offset[1]
-	 * BSSID[6]
-	 * Short SSID[4]
-	 * BSS parameters[1]
-	 * 20 MHz PSD[1]
-	 * MLD Parameters[3]
-	 *   B0..B7: AP MLD ID
-	 *   B7..B11: Link ID
-	 *   B12..B19: BSS Parameters Change Count
-	 *   B20: All Updates Included
-	 *   B21: Disabled Link Indication */
-
-	bssid = data + 1;
-	bss_params = data[1 + ETH_ALEN + 4];
-
-	data += 13; /* MLD Parameters */
-	link_id = *(data + 1) & 0xF;
-
-	wpa_dbg(wpa_s, MSG_DEBUG,
-		"MLD: mld ID=%u, link ID=%u, bssid=" MACSTR ", bss_params=0x%x",
-		*data, link_id, MAC2STR(bssid), bss_params);
-
-	if (*data) {
-		wpa_printf(MSG_DEBUG, "MLD: Reported link not part of MLD");
-		return;
-	}
-
-	neigh_bss = wpa_bss_get_bssid(wpa_s, bssid);
-	if (!neigh_bss) {
-		wpa_printf(MSG_DEBUG, "MLD: Neighbor not found in scan");
-		return;
-	}
-
-	if (!((bss_params & RNR_BSS_PARAM_SAME_SSID) &&
-	      (bss_params & RNR_BSS_PARAM_CO_LOCATED)) &&
-	    !wpa_scan_res_match(wpa_s, 0, neigh_bss, wpa_s->current_ssid,
-				1, 0)) {
-		wpa_printf(MSG_DEBUG,
-			   "MLD: Neighbor doesn't match current SSID - skip link");
-		return;
-	}
-
-	wpa_s->valid_links |= BIT(link_id);
-	os_memcpy(wpa_s->links[link_id].bssid, bssid, ETH_ALEN);
-	wpa_s->links[link_id].freq = neigh_bss->freq;
-}
-
-
-static void wpas_process_rnr(struct wpa_supplicant *wpa_s, const u8 *pos,
-			     size_t rnr_ie_len)
-{
-	while (rnr_ie_len > sizeof(struct ieee80211_neighbor_ap_info)) {
-		const struct ieee80211_neighbor_ap_info *ap_info =
-			(const struct ieee80211_neighbor_ap_info *) pos;
-		/* The first TBTT Information field */
-		const u8 *data = ap_info->data;
-		u8 tbtt_count;
-		size_t len;
-		int tbtt_i;
-
-		if (rnr_ie_len < sizeof(struct ieee80211_neighbor_ap_info))
-			break;
-
-		tbtt_count = (ap_info->tbtt_info_hdr >> 4) + 1;
-		len = sizeof(struct ieee80211_neighbor_ap_info) +
-			ap_info->tbtt_info_len * tbtt_count;
-
-		wpa_printf(MSG_DEBUG, "MLD: op_class=%u, channel=%u",
-			   ap_info->op_class, ap_info->channel);
-
-		if (len > rnr_ie_len)
-			break;
-
-		if (ap_info->tbtt_info_len < 16) {
-			rnr_ie_len -= len;
-			pos += len;
-			continue;
-		}
-
-		for (tbtt_i = 0; tbtt_i < tbtt_count; tbtt_i++) {
-			wpas_process_tbtt_info(wpa_s, data);
-			data += ap_info->tbtt_info_len;
-		}
-
-		rnr_ie_len -= len;
-		pos += len;
-	}
-}
-
-
-static bool wpas_ml_element(struct wpa_supplicant *wpa_s, struct wpa_bss *bss,
-			    struct wpa_ssid *ssid)
-{
-	struct wpabuf *mlbuf;
-	const u8 *rnr_ie, *rsn_ie;
-	struct wpa_ie_data ie;
-	u8 ml_ie_len;
-	const struct ieee80211_eht_ml *eht_ml;
-	const struct eht_ml_basic_common_info *ml_basic_common_info;
-	u8 i;
-	const u16 control =
-		host_to_le16(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_MLD_CAPA);
-	bool ret = false;
-	int rnr_idx;
-
-	if (!(wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO))
-		return false;
-
-	mlbuf = wpa_bss_defrag_mle(bss, MULTI_LINK_CONTROL_TYPE_BASIC);
-	if (!mlbuf) {
-		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No ML element");
-		return false;
-	}
-
-	rsn_ie = wpa_bss_get_ie(bss, WLAN_EID_RSN);
-	if (!rsn_ie || wpa_parse_wpa_ie(rsn_ie, 2 + rsn_ie[1], &ie)) {
-		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No RSN element");
-		goto out;
-	}
-
-	if (!(ie.capabilities & WPA_CAPABILITY_MFPC) ||
-	    wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION) {
-		wpa_dbg(wpa_s, MSG_DEBUG,
-			"MLD: No management frame protection");
-		goto out;
-	}
-
-	ie.key_mgmt &= ~(WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_FT_PSK |
-			 WPA_KEY_MGMT_PSK_SHA256);
-	if (!(ie.key_mgmt & ssid->key_mgmt)) {
-		wpa_dbg(wpa_s, MSG_DEBUG, "MLD: No valid key management");
-		goto out;
-	}
-
-	ml_ie_len = wpabuf_len(mlbuf);
-
-	/* control + common info len + MLD address + MLD link information */
-	if (ml_ie_len < 2 + 1 + ETH_ALEN + 1)
-		goto out;
-
-	eht_ml = wpabuf_head(mlbuf);
-	if ((eht_ml->ml_control & control) != control) {
-		wpa_printf(MSG_DEBUG, "MLD: Unexpected ML element control=0x%x",
-			   eht_ml->ml_control);
-		goto out;
-	}
-
-	ml_basic_common_info =
-		(const struct eht_ml_basic_common_info *) eht_ml->variable;
-
-	/* common info length should be valid (self, mld_addr, link_id) */
-	if (ml_basic_common_info->len < 1 + ETH_ALEN + 1)
-		goto out;
-
-	/* get the MLD address and MLD link ID */
-	os_memcpy(wpa_s->ap_mld_addr, ml_basic_common_info->mld_addr,
-		  ETH_ALEN);
-	wpa_s->mlo_assoc_link_id = ml_basic_common_info->variable[0] &
-		EHT_ML_LINK_ID_MSK;
-
-	os_memcpy(wpa_s->links[wpa_s->mlo_assoc_link_id].bssid, bss->bssid,
-		  ETH_ALEN);
-	wpa_s->links[wpa_s->mlo_assoc_link_id].freq = bss->freq;
-
-	wpa_printf(MSG_DEBUG, "MLD: address=" MACSTR ", link ID=%u",
-		   MAC2STR(wpa_s->ap_mld_addr), wpa_s->mlo_assoc_link_id);
-
-	wpa_s->valid_links = BIT(wpa_s->mlo_assoc_link_id);
-
-	ret = true;
-
-	/* Process all Reduced Neighbor Report elements */
-	for (rnr_idx = 1; ; rnr_idx++) {
-		rnr_ie = wpa_bss_get_ie_nth(bss,
-					    WLAN_EID_REDUCED_NEIGHBOR_REPORT,
-					    rnr_idx);
-		if (!rnr_ie) {
-			if (rnr_idx == 0) {
-				wpa_dbg(wpa_s, MSG_DEBUG,
-					"MLD: No RNR element");
-				goto out;
-			}
-			break;
-		}
-		wpas_process_rnr(wpa_s, rnr_ie + 2, rnr_ie[1]);
-	}
-
-	wpa_printf(MSG_DEBUG, "MLD: valid_links=0x%x", wpa_s->valid_links);
-
-	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
-		if (!(wpa_s->valid_links & BIT(i)))
-			continue;
-
-		wpa_printf(MSG_DEBUG, "MLD: link=%u, bssid=" MACSTR,
-			   i, MAC2STR(wpa_s->links[i].bssid));
-	}
-
-out:
-	wpabuf_free(mlbuf);
-	return ret;
-}
-
-
 static void wpas_ml_handle_removed_links(struct wpa_supplicant *wpa_s,
 					 struct wpa_bss *bss)
 {
@@ -601,16 +388,98 @@
 }
 
 
-static void wpas_sme_ml_auth(struct wpa_supplicant *wpa_s,
-			     union wpa_event_data *data,
-			     int ie_offset)
+#ifdef CONFIG_TESTING_OPTIONS
+static struct wpa_bss * wpas_ml_connect_pref(struct wpa_supplicant *wpa_s,
+					     struct wpa_bss *bss)
+{
+	unsigned int low, high, i;
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: valid_links=%d, band_pref=%u, bssid_pref=" MACSTR,
+		   wpa_s->valid_links,
+		   wpa_s->conf->mld_connect_band_pref,
+		   MAC2STR(wpa_s->conf->mld_connect_bssid_pref));
+
+	/* Check if there are more than one link */
+	if (!(wpa_s->valid_links & (wpa_s->valid_links - 1)))
+		return bss;
+
+	if (!is_zero_ether_addr(wpa_s->conf->mld_connect_bssid_pref)) {
+		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+			if (!(wpa_s->valid_links & BIT(i)))
+				continue;
+
+			if (wpa_s->mlo_assoc_link_id == i)
+				continue;
+
+			if (ether_addr_equal(
+				    wpa_s->links[i].bssid,
+				    wpa_s->conf->mld_connect_bssid_pref))
+				goto found;
+		}
+	}
+
+	if (wpa_s->conf->mld_connect_band_pref == MLD_CONNECT_BAND_PREF_AUTO)
+		return bss;
+
+	switch (wpa_s->conf->mld_connect_band_pref) {
+	case MLD_CONNECT_BAND_PREF_2GHZ:
+		low = 2412;
+		high = 2472;
+		break;
+	case MLD_CONNECT_BAND_PREF_5GHZ:
+		low = 5180;
+		high = 5985;
+		break;
+	case MLD_CONNECT_BAND_PREF_6GHZ:
+		low = 5955;
+		high = 7125;
+		break;
+	default:
+		return bss;
+	}
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(wpa_s->valid_links & BIT(i)))
+			continue;
+
+		if (wpa_s->mlo_assoc_link_id == i)
+			continue;
+
+		if (wpa_s->links[i].freq >= low && wpa_s->links[i].freq <= high)
+			goto found;
+	}
+
+found:
+	if (i == MAX_NUM_MLD_LINKS) {
+		wpa_printf(MSG_DEBUG, "MLD: No match for connect/band pref");
+		return bss;
+	}
+
+	wpa_printf(MSG_DEBUG,
+		   "MLD: Change BSS for connect: " MACSTR " -> " MACSTR,
+		   MAC2STR(wpa_s->links[wpa_s->mlo_assoc_link_id].bssid),
+		   MAC2STR(wpa_s->links[i].bssid));
+
+	/* Get the BSS entry and do the switch */
+	bss = wpa_bss_get_bssid(wpa_s, wpa_s->links[i].bssid);
+	wpa_s->mlo_assoc_link_id = i;
+
+	return bss;
+}
+#endif /* CONFIG_TESTING_OPTIONS */
+
+
+static int wpas_sme_ml_auth(struct wpa_supplicant *wpa_s,
+			    union wpa_event_data *data,
+			    int ie_offset)
 {
 	struct ieee802_11_elems elems;
 	const u8 *mld_addr;
 	u16 status_code = data->auth.status_code;
 
 	if (!wpa_s->valid_links)
-		return;
+		return 0;
 
 	if (ieee802_11_parse_elems(data->auth.ies + ie_offset,
 				   data->auth.ies_len - ie_offset,
@@ -628,7 +497,7 @@
 			goto out;
 		/* Accept missing Multi-Link element in failed authentication
 		 * cases. */
-		return;
+		return 0;
 	}
 
 	mld_addr = get_basic_mle_mld_addr(elems.basic_mle, elems.basic_mle_len);
@@ -637,16 +506,39 @@
 
 	wpa_printf(MSG_DEBUG, "MLD: mld_address=" MACSTR, MAC2STR(mld_addr));
 
-	if (os_memcmp(wpa_s->ap_mld_addr, mld_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(wpa_s->ap_mld_addr, mld_addr)) {
 		wpa_printf(MSG_DEBUG, "MLD: Unexpected MLD address (expected "
 			   MACSTR ")", MAC2STR(wpa_s->ap_mld_addr));
 		goto out;
 	}
 
-	return;
+	return 0;
 out:
 	wpa_printf(MSG_DEBUG, "MLD: Authentication - clearing MLD state");
 	wpas_reset_mlo_info(wpa_s);
+	return -1;
+}
+
+
+static void wpas_sme_set_mlo_links(struct wpa_supplicant *wpa_s,
+				   struct wpa_bss *bss)
+{
+	int i;
+
+	wpa_s->valid_links = 0;
+
+	for (i = 0; i < bss->n_mld_links; i++) {
+		u8 link_id = bss->mld_links[i].link_id;
+		const u8 *bssid = bss->mld_links[i].bssid;
+
+		if (i == 0)
+			wpa_s->mlo_assoc_link_id = link_id;
+		wpa_s->valid_links |= BIT(link_id);
+		os_memcpy(wpa_s->links[link_id].bssid, bssid, ETH_ALEN);
+		wpa_s->links[link_id].freq = bss->mld_links[i].freq;
+		wpa_s->links[link_id].bss = wpa_bss_get_bssid(wpa_s, bssid);
+		wpa_s->links[link_id].disabled = bss->mld_links[i].disabled;
+	}
 }
 
 
@@ -681,11 +573,33 @@
 		return;
 	}
 
+	os_memset(&params, 0, sizeof(params));
+
+	if ((wpa_s->drv_flags2 & WPA_DRIVER_FLAGS2_MLO) &&
+	    !wpa_bss_parse_basic_ml_element(wpa_s, bss, wpa_s->ap_mld_addr,
+					    NULL, ssid, NULL) &&
+	    bss->n_mld_links) {
+		wpa_printf(MSG_DEBUG, "MLD: In authentication");
+		wpas_sme_set_mlo_links(wpa_s, bss);
+
+#ifdef CONFIG_TESTING_OPTIONS
+		bss = wpas_ml_connect_pref(wpa_s, bss);
+
+		if (wpa_s->conf->mld_force_single_link) {
+			wpa_printf(MSG_DEBUG, "MLD: Force single link");
+			wpa_s->valid_links = BIT(wpa_s->mlo_assoc_link_id);
+		}
+#endif /* CONFIG_TESTING_OPTIONS */
+		params.mld = true;
+		params.mld_link_id = wpa_s->mlo_assoc_link_id;
+		params.ap_mld_addr = wpa_s->ap_mld_addr;
+		wpas_ml_handle_removed_links(wpa_s, bss);
+	}
+
 	skip_auth = wpa_s->conf->reassoc_same_bss_optim &&
 		wpa_s->reassoc_same_bss;
 	wpa_s->current_bss = bss;
 
-	os_memset(&params, 0, sizeof(params));
 	wpa_s->reassociate = 0;
 
 	params.freq = bss->freq;
@@ -694,14 +608,6 @@
 	params.ssid_len = bss->ssid_len;
 	params.p2p = ssid->p2p_group;
 
-	if (wpas_ml_element(wpa_s, bss, ssid)) {
-		wpa_printf(MSG_DEBUG, "MLD: In authentication");
-		params.mld = true;
-		params.mld_link_id = wpa_s->mlo_assoc_link_id;
-		params.ap_mld_addr = wpa_s->ap_mld_addr;
-		wpas_ml_handle_removed_links(wpa_s, bss);
-	}
-
 	if (wpa_s->sme.ssid_len != params.ssid_len ||
 	    os_memcmp(wpa_s->sme.ssid, params.ssid, params.ssid_len) != 0)
 		wpa_s->sme.prev_bssid_set = 0;
@@ -970,10 +876,12 @@
 
 	sme_auth_handle_rrm(wpa_s, bss);
 
+#ifndef CONFIG_NO_RRM
 	wpa_s->sme.assoc_req_ie_len += wpas_supp_op_class_ie(
 		wpa_s, ssid, bss,
 		wpa_s->sme.assoc_req_ie + wpa_s->sme.assoc_req_ie_len,
 		sizeof(wpa_s->sme.assoc_req_ie) - wpa_s->sme.assoc_req_ie_len);
+#endif /* CONFIG_NO_RRM */
 
 	if (params.p2p)
 		wpa_drv_get_ext_capa(wpa_s, WPA_IF_P2P_CLIENT);
@@ -1122,7 +1030,7 @@
 		else
 			resp = sme_auth_build_sae_confirm(wpa_s, 0);
 		if (resp == NULL) {
-			wpas_connection_failed(wpa_s, bss->bssid);
+			wpas_connection_failed(wpa_s, bss->bssid, NULL);
 			return;
 		}
 		params.auth_data = wpabuf_head(resp);
@@ -1258,7 +1166,7 @@
 			if (wpas_p2p_handle_frequency_conflicts(wpa_s,
 								params.freq,
 								ssid) < 0) {
-				wpas_connection_failed(wpa_s, bss->bssid);
+				wpas_connection_failed(wpa_s, bss->bssid, NULL);
 				wpa_supplicant_mark_disassoc(wpa_s);
 				wpabuf_free(resp);
 				wpas_connect_work_done(wpa_s);
@@ -1281,7 +1189,7 @@
 	if (wpa_drv_authenticate(wpa_s, &params) < 0) {
 		wpa_msg(wpa_s, MSG_INFO, "SME: Authentication request to the "
 			"driver failed");
-		wpas_connection_failed(wpa_s, bss->bssid);
+		wpas_connection_failed(wpa_s, bss->bssid, NULL);
 		wpa_supplicant_mark_disassoc(wpa_s);
 		wpabuf_free(resp);
 		wpas_connect_work_done(wpa_s);
@@ -1719,8 +1627,7 @@
 
 	wpa_printf(MSG_DEBUG, "MLD: mld_address=" MACSTR, MAC2STR(mld_addr));
 
-	if (os_memcmp(wpa_s->sme.ext_auth_ap_mld_addr, mld_addr, ETH_ALEN) !=
-	    0) {
+	if (!ether_addr_equal(wpa_s->sme.ext_auth_ap_mld_addr, mld_addr)) {
 		wpa_printf(MSG_DEBUG, "MLD: Unexpected MLD address (expected "
 			   MACSTR ")",
 			   MAC2STR(wpa_s->sme.ext_auth_ap_mld_addr));
@@ -1998,7 +1905,7 @@
 		}
 		if (wpa_insert_pmkid(wpa_s->sme.assoc_req_ie,
 				     &wpa_s->sme.assoc_req_ie_len,
-				     wpa_s->sme.sae.pmkid) < 0)
+				     wpa_s->sme.sae.pmkid, true) < 0)
 			return -1;
 		wpa_hexdump(MSG_DEBUG,
 			    "SME: Updated Association Request IEs",
@@ -2075,9 +1982,9 @@
 		return;
 	}
 
-	if (os_memcmp(wpa_s->pending_bssid, data->auth.peer, ETH_ALEN) != 0 &&
+	if (!ether_addr_equal(wpa_s->pending_bssid, data->auth.peer) &&
 	    !(wpa_s->valid_links &&
-	      os_memcmp(wpa_s->ap_mld_addr, data->auth.peer, ETH_ALEN) == 0)) {
+	      ether_addr_equal(wpa_s->ap_mld_addr, data->auth.peer))) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "SME: Ignore authentication with "
 			"unexpected peer " MACSTR,
 			MAC2STR(data->auth.peer));
@@ -2103,7 +2010,8 @@
 				   data->auth.ies_len, 0, data->auth.peer,
 				   &ie_offset);
 		if (res < 0) {
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
 
 		}
@@ -2147,7 +2055,8 @@
 		    WLAN_STATUS_NOT_SUPPORTED_AUTH_ALG ||
 		    wpa_s->sme.auth_alg == data->auth.auth_type ||
 		    wpa_s->current_ssid->auth_alg == WPA_AUTH_ALG_LEAP) {
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
 			return;
 		}
@@ -2196,7 +2105,8 @@
 				" reason=%d locally_generated=1",
 				MAC2STR(wpa_s->pending_bssid),
 				WLAN_REASON_DEAUTH_LEAVING);
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_supplicant_mark_disassoc(wpa_s);
 			return;
 		}
@@ -2220,7 +2130,8 @@
 				" reason=%d locally_generated=1",
 				MAC2STR(wpa_s->pending_bssid),
 				WLAN_REASON_DEAUTH_LEAVING);
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_supplicant_mark_disassoc(wpa_s);
 			return;
 		}
@@ -2234,7 +2145,8 @@
 				" reason=%d locally_generated=1",
 				MAC2STR(wpa_s->pending_bssid),
 				WLAN_REASON_DEAUTH_LEAVING);
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_supplicant_mark_disassoc(wpa_s);
 			return;
 		}
@@ -2242,9 +2154,19 @@
 #endif /* CONFIG_FILS */
 
 	/* TODO: Support additional auth_type values as well */
-	if (data->auth.auth_type == WLAN_AUTH_OPEN ||
-	    data->auth.auth_type == WLAN_AUTH_SAE)
-		wpas_sme_ml_auth(wpa_s, data, ie_offset);
+	if ((data->auth.auth_type == WLAN_AUTH_OPEN ||
+	     data->auth.auth_type == WLAN_AUTH_SAE) &&
+	    wpas_sme_ml_auth(wpa_s, data, ie_offset) < 0) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"MLD: Failed to parse ML Authentication frame");
+		wpa_msg(wpa_s, MSG_INFO, WPA_EVENT_DISCONNECTED "bssid=" MACSTR
+			" reason=%d locally_generated=1",
+			MAC2STR(wpa_s->pending_bssid),
+			WLAN_REASON_DEAUTH_LEAVING);
+		wpas_connection_failed(wpa_s, wpa_s->pending_bssid, NULL);
+		wpa_supplicant_mark_disassoc(wpa_s);
+		return;
+	}
 
 	sme_associate(wpa_s, ssid->mode, data->auth.peer,
 		      data->auth.auth_type);
@@ -2287,6 +2209,9 @@
 
 	os_memset(&params, 0, sizeof(params));
 
+	/* Save auth type, in case we need to retry after comeback timer. */
+	wpa_s->sme.assoc_auth_type = auth_type;
+
 #ifdef CONFIG_FILS
 	if (auth_type == WLAN_AUTH_FILS_SK ||
 	    auth_type == WLAN_AUTH_FILS_SK_PFS) {
@@ -2453,6 +2378,7 @@
 pfs_fail:
 #endif /* CONFIG_DPP2 */
 
+#ifndef CONFIG_NO_ROBUST_AV
 	wpa_s->mscs_setup_done = false;
 	if (wpa_bss_ext_capab(wpa_s->current_bss, WLAN_EXT_CAPAB_MSCS) &&
 	    wpa_s->robust_av.valid_config) {
@@ -2486,6 +2412,7 @@
 		wpabuf_free(mscs_ie);
 	}
 mscs_fail:
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	if (ssid && ssid->multi_ap_backhaul_sta) {
 		size_t multi_ap_ie_len;
@@ -2682,19 +2609,48 @@
 				wpa_s->links[i].bssid;
 			params.mld_params.mld_links[i].freq =
 				wpa_s->links[i].freq;
+			params.mld_params.mld_links[i].disabled =
+				wpa_s->links[i].disabled;
 
-			wpa_printf(MSG_DEBUG, "MLD: id=%u, freq=%d, " MACSTR,
+			wpa_printf(MSG_DEBUG,
+				   "MLD: id=%u, freq=%d, disabled=%u, " MACSTR,
 				   i, wpa_s->links[i].freq,
+				   wpa_s->links[i].disabled,
 				   MAC2STR(wpa_s->links[i].bssid));
 		}
 	}
 
 	if (wpa_drv_associate(wpa_s, &params) < 0) {
+		unsigned int n_failed_links = 0;
+		int i;
+
 		wpa_msg(wpa_s, MSG_INFO, "SME: Association request to the "
 			"driver failed");
-		wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
-		wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
-		os_memset(wpa_s->pending_bssid, 0, ETH_ALEN);
+
+		/* Prepare list of failed links for error report */
+		for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+			if (!(wpa_s->valid_links & BIT(i)) ||
+			    wpa_s->mlo_assoc_link_id == i ||
+			    !params.mld_params.mld_links[i].error)
+				continue;
+
+			wpa_bssid_ignore_add(wpa_s, wpa_s->links[i].bssid);
+			n_failed_links++;
+		}
+
+		if (n_failed_links) {
+			/* Deauth and connect (possibly to the same AP MLD) */
+			wpa_drv_deauthenticate(wpa_s, wpa_s->ap_mld_addr,
+					       WLAN_REASON_DEAUTH_LEAVING);
+			wpas_connect_work_done(wpa_s);
+			wpa_supplicant_mark_disassoc(wpa_s);
+			wpas_request_connection(wpa_s);
+		} else {
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
+			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
+			os_memset(wpa_s->pending_bssid, 0, ETH_ALEN);
+		}
 		return;
 	}
 
@@ -2734,20 +2690,26 @@
 }
 
 
-static void sme_deauth(struct wpa_supplicant *wpa_s)
+static void sme_deauth(struct wpa_supplicant *wpa_s, const u8 **link_bssids)
 {
 	int bssid_changed;
+	const u8 *bssid;
 
 	bssid_changed = !is_zero_ether_addr(wpa_s->bssid);
 
-	if (wpa_drv_deauthenticate(wpa_s, wpa_s->pending_bssid,
+	if (wpa_s->valid_links)
+		bssid = wpa_s->ap_mld_addr;
+	else
+		bssid = wpa_s->pending_bssid;
+
+	if (wpa_drv_deauthenticate(wpa_s, bssid,
 				   WLAN_REASON_DEAUTH_LEAVING) < 0) {
 		wpa_msg(wpa_s, MSG_INFO, "SME: Deauth request to the driver "
 			"failed");
 	}
 	wpa_s->sme.prev_bssid_set = 0;
 
-	wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+	wpas_connection_failed(wpa_s, wpa_s->pending_bssid, link_bssids);
 	wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
 	os_memset(wpa_s->bssid, 0, ETH_ALEN);
 	os_memset(wpa_s->pending_bssid, 0, ETH_ALEN);
@@ -2756,14 +2718,115 @@
 }
 
 
-void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
-			    union wpa_event_data *data)
+static void sme_assoc_comeback_timer(void *eloop_ctx, void *timeout_ctx)
 {
+	struct wpa_supplicant *wpa_s = eloop_ctx;
+
+	if (!wpa_s->current_bss || !wpa_s->current_ssid) {
+		wpa_msg(wpa_s, MSG_DEBUG,
+			"SME: Comeback timeout expired; SSID/BSSID cleared; ignoring");
+		return;
+	}
+
+	wpa_msg(wpa_s, MSG_DEBUG,
+		"SME: Comeback timeout expired; retry associating with "
+		MACSTR "; mode=%d auth_type=%u",
+		MAC2STR(wpa_s->current_bss->bssid),
+		wpa_s->current_ssid->mode,
+		wpa_s->sme.assoc_auth_type);
+
+	/* Authentication state was completed already; just try association
+	 * again. */
+	sme_associate(wpa_s, wpa_s->current_ssid->mode,
+		      wpa_s->current_bss->bssid,
+		      wpa_s->sme.assoc_auth_type);
+}
+
+
+static bool sme_try_assoc_comeback(struct wpa_supplicant *wpa_s,
+				   union wpa_event_data *data)
+{
+	struct ieee802_11_elems elems;
+	u32 timeout_interval;
+	unsigned long comeback_usec;
+	u8 type = WLAN_TIMEOUT_ASSOC_COMEBACK;
+
+#ifdef CONFIG_TESTING_OPTIONS
+	if (wpa_s->test_assoc_comeback_type != -1)
+		type = wpa_s->test_assoc_comeback_type;
+#endif /* CONFIG_TESTING_OPTIONS */
+
+	if (ieee802_11_parse_elems(data->assoc_reject.resp_ies,
+				   data->assoc_reject.resp_ies_len,
+				   &elems, 0) == ParseFailed) {
+		wpa_msg(wpa_s, MSG_INFO,
+			"SME: Temporary assoc reject: failed to parse (Re)Association Response frame elements");
+		return false;
+	}
+
+	if (!elems.timeout_int) {
+		wpa_msg(wpa_s, MSG_INFO,
+			"SME: Temporary assoc reject: missing timeout interval IE");
+		return false;
+	}
+
+	if (elems.timeout_int[0] != type) {
+		wpa_msg(wpa_s, MSG_INFO,
+			"SME: Temporary assoc reject: missing association comeback time");
+		return false;
+	}
+
+	timeout_interval = WPA_GET_LE32(&elems.timeout_int[1]);
+	if (timeout_interval > 60000) {
+		/* This is unprotected information and there is no point in
+		 * getting stuck waiting for very long duration based on it */
+		wpa_msg(wpa_s, MSG_DEBUG,
+			"SME: Ignore overly long association comeback interval: %u TUs",
+			timeout_interval);
+		return false;
+	}
+	wpa_msg(wpa_s, MSG_DEBUG, "SME: Association comeback interval: %u TUs",
+		timeout_interval);
+
+	comeback_usec = timeout_interval * 1024;
+	eloop_register_timeout(comeback_usec / 1000000, comeback_usec % 1000000,
+			       sme_assoc_comeback_timer, wpa_s, NULL);
+	return true;
+}
+
+
+void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
+			    union wpa_event_data *data,
+			    const u8 **link_bssids)
+{
+	const u8 *bssid;
+
+	if (wpa_s->valid_links)
+		bssid = wpa_s->ap_mld_addr;
+	else
+		bssid = wpa_s->pending_bssid;
+
 	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Association with " MACSTR " failed: "
 		"status code %d", MAC2STR(wpa_s->pending_bssid),
 		data->assoc_reject.status_code);
 
 	eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
+	eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
+
+	/* Authentication phase has been completed at this point. Check whether
+	 * the AP rejected association temporarily due to still holding a
+	 * security associationis with us (MFP). If so, we must wait for the
+	 * AP's association comeback timeout period before associating again. */
+	if (data->assoc_reject.status_code ==
+	    WLAN_STATUS_ASSOC_REJECTED_TEMPORARILY) {
+		wpa_msg(wpa_s, MSG_DEBUG,
+			"SME: Temporary association reject from BSS " MACSTR,
+			MAC2STR(bssid));
+		if (sme_try_assoc_comeback(wpa_s, data)) {
+			/* Break out early; comeback error is not a failure. */
+			return;
+		}
+	}
 
 #ifdef CONFIG_SAE
 	if (wpa_s->sme.sae_pmksa_caching && wpa_s->current_ssid &&
@@ -2776,7 +2839,7 @@
 			struct wpa_bss *bss = wpa_s->current_bss;
 			struct wpa_ssid *ssid = wpa_s->current_ssid;
 
-			wpa_drv_deauthenticate(wpa_s, wpa_s->pending_bssid,
+			wpa_drv_deauthenticate(wpa_s, bssid,
 					       WLAN_REASON_DEAUTH_LEAVING);
 			wpas_connect_work_done(wpa_s);
 			wpa_supplicant_mark_disassoc(wpa_s);
@@ -2821,7 +2884,7 @@
 	 * benefit from using the previous authentication, so this could be
 	 * optimized in the future.
 	 */
-	sme_deauth(wpa_s);
+	sme_deauth(wpa_s, link_bssids);
 }
 
 
@@ -2829,7 +2892,7 @@
 			      union wpa_event_data *data)
 {
 	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Authentication timed out");
-	wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+	wpas_connection_failed(wpa_s, wpa_s->pending_bssid, NULL);
 	wpa_supplicant_mark_disassoc(wpa_s);
 }
 
@@ -2838,7 +2901,7 @@
 			       union wpa_event_data *data)
 {
 	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Association timed out");
-	wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+	wpas_connection_failed(wpa_s, wpa_s->pending_bssid, NULL);
 	wpa_supplicant_mark_disassoc(wpa_s);
 }
 
@@ -2867,7 +2930,7 @@
 	struct wpa_supplicant *wpa_s = eloop_ctx;
 	if (wpa_s->wpa_state == WPA_AUTHENTICATING) {
 		wpa_msg(wpa_s, MSG_DEBUG, "SME: Authentication timeout");
-		sme_deauth(wpa_s);
+		sme_deauth(wpa_s, NULL);
 	}
 }
 
@@ -2877,7 +2940,7 @@
 	struct wpa_supplicant *wpa_s = eloop_ctx;
 	if (wpa_s->wpa_state == WPA_ASSOCIATING) {
 		wpa_msg(wpa_s, MSG_DEBUG, "SME: Association timeout");
-		sme_deauth(wpa_s);
+		sme_deauth(wpa_s, NULL);
 	}
 }
 
@@ -2885,35 +2948,15 @@
 void sme_state_changed(struct wpa_supplicant *wpa_s)
 {
 	/* Make sure timers are cleaned up appropriately. */
-	if (wpa_s->wpa_state != WPA_ASSOCIATING)
+	if (wpa_s->wpa_state != WPA_ASSOCIATING) {
 		eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
+		eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
+	}
 	if (wpa_s->wpa_state != WPA_AUTHENTICATING)
 		eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
 }
 
 
-void sme_disassoc_while_authenticating(struct wpa_supplicant *wpa_s,
-				       const u8 *prev_pending_bssid)
-{
-	/*
-	 * mac80211-workaround to force deauth on failed auth cmd,
-	 * requires us to remain in authenticating state to allow the
-	 * second authentication attempt to be continued properly.
-	 */
-	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Allow pending authentication "
-		"to proceed after disconnection event");
-	wpa_supplicant_set_state(wpa_s, WPA_AUTHENTICATING);
-	os_memcpy(wpa_s->pending_bssid, prev_pending_bssid, ETH_ALEN);
-
-	/*
-	 * Re-arm authentication timer in case auth fails for whatever reason.
-	 */
-	eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
-	eloop_register_timeout(SME_AUTH_TIMEOUT, 0, sme_auth_timer, wpa_s,
-			       NULL);
-}
-
-
 void sme_clear_on_disassoc(struct wpa_supplicant *wpa_s)
 {
 	wpa_s->sme.prev_bssid_set = 0;
@@ -2941,6 +2984,7 @@
 	eloop_cancel_timeout(sme_assoc_timer, wpa_s, NULL);
 	eloop_cancel_timeout(sme_auth_timer, wpa_s, NULL);
 	eloop_cancel_timeout(sme_obss_scan_timeout, wpa_s, NULL);
+	eloop_cancel_timeout(sme_assoc_comeback_timer, wpa_s, NULL);
 }
 
 
@@ -3374,7 +3418,7 @@
 	ssid = wpa_s->current_ssid;
 	if (wpas_get_ssid_pmf(wpa_s, ssid) == NO_MGMT_FRAME_PROTECTION)
 		return;
-	if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0)
+	if (!ether_addr_equal(sa, wpa_s->bssid))
 		return;
 	if (reason_code != WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA &&
 	    reason_code != WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA)
@@ -3479,7 +3523,7 @@
 	wpa_dbg(wpa_s, MSG_DEBUG, "SME: Received SA Query response from "
 		MACSTR " (trans_id %02x%02x)", MAC2STR(sa), data[1], data[2]);
 
-	if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0)
+	if (!ether_addr_equal(sa, wpa_s->bssid))
 		return;
 
 	for (i = 0; i < wpa_s->sme.sa_query_count; i++) {
diff --git a/wpa_supplicant/sme.h b/wpa_supplicant/sme.h
index c797d2e..f8fd06b 100644
--- a/wpa_supplicant/sme.h
+++ b/wpa_supplicant/sme.h
@@ -19,7 +19,8 @@
 int sme_update_ft_ies(struct wpa_supplicant *wpa_s, const u8 *md,
 		      const u8 *ies, size_t ies_len);
 void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
-			    union wpa_event_data *data);
+			    union wpa_event_data *data,
+			    const u8 **link_bssids);
 void sme_event_auth_timed_out(struct wpa_supplicant *wpa_s,
 			      union wpa_event_data *data);
 void sme_event_assoc_timed_out(struct wpa_supplicant *wpa_s,
@@ -32,8 +33,6 @@
 void sme_sa_query_rx(struct wpa_supplicant *wpa_s, const u8 *da, const u8 *sa,
 		     const u8 *data, size_t len);
 void sme_state_changed(struct wpa_supplicant *wpa_s);
-void sme_disassoc_while_authenticating(struct wpa_supplicant *wpa_s,
-				       const u8 *prev_pending_bssid);
 void sme_clear_on_disassoc(struct wpa_supplicant *wpa_s);
 void sme_deinit(struct wpa_supplicant *wpa_s);
 
@@ -65,7 +64,8 @@
 
 
 static inline void sme_event_assoc_reject(struct wpa_supplicant *wpa_s,
-					  union wpa_event_data *data)
+					  union wpa_event_data *data,
+					  const u8 **link_bssids)
 {
 }
 
@@ -98,12 +98,6 @@
 {
 }
 
-static inline void
-sme_disassoc_while_authenticating(struct wpa_supplicant *wpa_s,
-				  const u8 *prev_pending_bssid)
-{
-}
-
 static inline void sme_clear_on_disassoc(struct wpa_supplicant *wpa_s)
 {
 }
diff --git a/wpa_supplicant/wmm_ac.c b/wpa_supplicant/wmm_ac.c
index d0fdd55..37e2ed4 100644
--- a/wpa_supplicant/wmm_ac.c
+++ b/wpa_supplicant/wmm_ac.c
@@ -678,7 +678,7 @@
 	}
 
 	/* make sure the params are the same */
-	if (os_memcmp(req->address, sa, ETH_ALEN) != 0 ||
+	if (!ether_addr_equal(req->address, sa) ||
 	    tsid != wmm_ac_get_tsid(&req->tspec) ||
 	    up != wmm_ac_get_user_priority(&req->tspec) ||
 	    dir != wmm_ac_get_direction(&req->tspec)) {
@@ -755,13 +755,13 @@
 	}
 
 	/* WMM AC action frame */
-	if (os_memcmp(da, wpa_s->own_addr, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(da, wpa_s->own_addr)) {
 		wpa_printf(MSG_DEBUG, "WMM AC: frame destination addr="MACSTR
 			   " is other than ours, ignoring frame", MAC2STR(da));
 		return;
 	}
 
-	if (os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(sa, wpa_s->bssid)) {
 		wpa_printf(MSG_DEBUG, "WMM AC: ignore frame with sa " MACSTR
 			   " different other than our bssid", MAC2STR(da));
 		return;
diff --git a/wpa_supplicant/wnm_sta.c b/wpa_supplicant/wnm_sta.c
index 56183ff..775e75b 100644
--- a/wpa_supplicant/wnm_sta.c
+++ b/wpa_supplicant/wnm_sta.c
@@ -686,9 +686,8 @@
 	if (reason) {
 		for (i = 0; i < info->num; i++) {
 			if (first_candidate_bssid &&
-			    os_memcmp(first_candidate_bssid,
-				      info->candidates[i].bssid, ETH_ALEN) == 0)
-			{
+			    ether_addr_equal(first_candidate_bssid,
+					     info->candidates[i].bssid)) {
 				*reason = info->candidates[i].reject_reason;
 				break;
 			}
@@ -1091,7 +1090,7 @@
 		wpabuf_put_data(buf, "\0\0\0\0\0\0", ETH_ALEN);
 	}
 
-	if (status == WNM_BSS_TM_ACCEPT)
+	if (status == WNM_BSS_TM_ACCEPT && !wpa_s->wnm_link_removal)
 		wnm_add_cand_list(wpa_s, &buf);
 
 #ifdef CONFIG_MBO
@@ -1194,8 +1193,8 @@
 	}
 
 	if (!wpa_s->current_bss ||
-	    os_memcmp(wpa_s->wnm_cand_from_bss, wpa_s->current_bss->bssid,
-		      ETH_ALEN) != 0) {
+	    !ether_addr_equal(wpa_s->wnm_cand_from_bss,
+			      wpa_s->current_bss->bssid)) {
 		wpa_printf(MSG_DEBUG, "WNM: Stored BSS transition candidate list not from the current BSS - ignore it");
 		return 0;
 	}
@@ -1388,7 +1387,7 @@
 			const u8 *ssid_ie;
 
 			res = scan_res->res[j];
-			if (os_memcmp(nei->bssid, res->bssid, ETH_ALEN) != 0 ||
+			if (!ether_addr_equal(nei->bssid, res->bssid) ||
 			    res->age > WNM_SCAN_RESULT_AGE * 1000)
 				continue;
 			bss = wpa_s->current_bss;
@@ -1437,6 +1436,7 @@
 #ifdef CONFIG_MBO
 	const u8 *vendor;
 #endif /* CONFIG_MBO */
+	bool disassoc_imminent;
 
 	if (wpa_s->disable_mbo_oce || wpa_s->conf->disable_btm)
 		return;
@@ -1461,6 +1461,7 @@
 	wpa_s->wnm_dialog_token = pos[0];
 	wpa_s->wnm_mode = pos[1];
 	wpa_s->wnm_dissoc_timer = WPA_GET_LE16(pos + 2);
+	wpa_s->wnm_link_removal = false;
 	valid_int = pos[4];
 	wpa_s->wnm_reply = reply;
 
@@ -1532,7 +1533,26 @@
 		return;
 	}
 
-	if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT) {
+	disassoc_imminent = wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT;
+
+	/*
+	 * Based on IEEE P802.11be/D5.0, when a station is a non-AP MLD with
+	 * more than one affiliated link, the Link Removal Imminent field is
+	 * set to 1, and the BSS Termination Included field is set to 1, only
+	 * one of the links is removed and the other links remain associated.
+	 * Ignore the Disassociation Imminent field in such a case.
+	 */
+	if (disassoc_imminent &&
+	    (wpa_s->valid_links & (wpa_s->valid_links - 1)) != 0 &&
+	    (wpa_s->wnm_mode & WNM_BSS_TM_REQ_LINK_REMOVAL_IMMINENT) &&
+	    (wpa_s->wnm_mode & WNM_BSS_TM_REQ_BSS_TERMINATION_INCLUDED)) {
+		wpa_printf(MSG_INFO,
+			   "WNM: BTM request for a single MLO link - ignore disassociation imminent since other links remain associated");
+		disassoc_imminent = false;
+		wpa_s->wnm_link_removal = true;
+	}
+
+	if (disassoc_imminent) {
 		wpa_msg(wpa_s, MSG_INFO, "WNM: Disassociation Imminent - "
 			"Disassociation Timer %u", wpa_s->wnm_dissoc_timer);
 		if (wpa_s->wnm_dissoc_timer && !wpa_s->scanning &&
@@ -1572,8 +1592,7 @@
 				wnm_parse_neighbor_report(wpa_s, pos, len, rep);
 				if ((wpa_s->wnm_mode &
 				     WNM_BSS_TM_REQ_DISASSOC_IMMINENT) &&
-				    os_memcmp(rep->bssid, wpa_s->bssid,
-					      ETH_ALEN) == 0)
+				    ether_addr_equal(rep->bssid, wpa_s->bssid))
 					rep->disassoc_imminent = 1;
 
 				wpa_s->wnm_num_neighbor_report++;
@@ -1666,7 +1685,9 @@
 		wpa_supplicant_req_scan(wpa_s, 0, 0);
 	} else if (reply) {
 		enum bss_trans_mgmt_status_code status;
-		if (wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT)
+
+		if ((wpa_s->wnm_mode & WNM_BSS_TM_REQ_ESS_DISASSOC_IMMINENT) ||
+		    wpa_s->wnm_link_removal)
 			status = WNM_BSS_TM_ACCEPT;
 		else {
 			wpa_msg(wpa_s, MSG_INFO, "WNM: BSS Transition Management Request did not include candidates");
@@ -1890,7 +1911,9 @@
 		    pos, end - pos);
 
 	if (wpa_s->wpa_state != WPA_COMPLETED ||
-	    os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) {
+	    (!ether_addr_equal(sa, wpa_s->bssid) &&
+	     (!wpa_s->valid_links ||
+	      !ether_addr_equal(sa, wpa_s->ap_mld_addr)))) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "WNM: WNM-Notification frame not "
 			"from our AP - ignore it");
 		return;
@@ -1934,7 +1957,9 @@
 		return; /* only nonzero values are used for request */
 
 	if (wpa_s->wpa_state != WPA_COMPLETED ||
-	    os_memcmp(sa, wpa_s->bssid, ETH_ALEN) != 0) {
+	    (!ether_addr_equal(sa, wpa_s->bssid) &&
+	     (!wpa_s->valid_links ||
+	      !ether_addr_equal(sa, wpa_s->ap_mld_addr)))) {
 		wpa_dbg(wpa_s, MSG_DEBUG,
 			"WNM: Collocated Interference Request frame not from current AP - ignore it");
 		return;
@@ -1964,7 +1989,9 @@
 	wpa_printf(MSG_DEBUG, "WNM: RX action %u from " MACSTR,
 		   act, MAC2STR(mgmt->sa));
 	if (wpa_s->wpa_state < WPA_ASSOCIATED ||
-	    os_memcmp(mgmt->sa, wpa_s->bssid, ETH_ALEN) != 0) {
+	    (!ether_addr_equal(mgmt->sa, wpa_s->bssid) &&
+	     (!wpa_s->valid_links ||
+	      !ether_addr_equal(mgmt->sa, wpa_s->ap_mld_addr)))) {
 		wpa_printf(MSG_DEBUG, "WNM: Ignore unexpected WNM Action "
 			   "frame");
 		return;
@@ -2048,8 +2075,43 @@
 
 void wnm_clear_coloc_intf_reporting(struct wpa_supplicant *wpa_s)
 {
-#ifdef CONFIG_WNM
 	wpa_s->coloc_intf_dialog_token = 0;
 	wpa_s->coloc_intf_auto_report = 0;
-#endif /* CONFIG_WNM */
+}
+
+
+bool wnm_is_bss_excluded(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
+{
+	unsigned int i;
+
+	if (!(wpa_s->wnm_mode & WNM_BSS_TM_REQ_DISASSOC_IMMINENT))
+		return false;
+
+	/*
+	 * In case disassociation imminent is set, do no try to use a BSS to
+	 * which we are connected.
+	 */
+
+	if (wpa_s->current_bss &&
+	    ether_addr_equal(wpa_s->current_bss->bssid, bss->bssid)) {
+		wpa_dbg(wpa_s, MSG_DEBUG,
+			"WNM: Disassociation imminent: current BSS");
+		return true;
+	}
+
+	if (!wpa_s->valid_links)
+		return false;
+
+	for (i = 0; i < MAX_NUM_MLD_LINKS; i++) {
+		if (!(wpa_s->valid_links & BIT(i)))
+			continue;
+
+		if (ether_addr_equal(wpa_s->links[i].bssid, bss->bssid)) {
+			wpa_dbg(wpa_s, MSG_DEBUG,
+				"WNM: MLD: Disassociation imminent: current link");
+			return true;
+		}
+	}
+
+	return false;
 }
diff --git a/wpa_supplicant/wnm_sta.h b/wpa_supplicant/wnm_sta.h
index e4957e4..2a473db 100644
--- a/wpa_supplicant/wnm_sta.h
+++ b/wpa_supplicant/wnm_sta.h
@@ -70,6 +70,7 @@
 			       const struct wpabuf *elems);
 void wnm_set_coloc_intf_elems(struct wpa_supplicant *wpa_s,
 			      struct wpabuf *elems);
+bool wnm_is_bss_excluded(struct wpa_supplicant *wpa_s, struct wpa_bss *bss);
 
 
 #ifdef CONFIG_WNM
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
index 65078ed..b1334e2 100644
--- a/wpa_supplicant/wpa_cli.c
+++ b/wpa_supplicant/wpa_cli.c
@@ -2847,6 +2847,8 @@
 }
 
 
+#ifndef CONFIG_NO_WMM_AC
+
 static int wpa_cli_cmd_wmm_ac_addts(struct wpa_ctrl *ctrl, int argc,
 				    char *argv[])
 {
@@ -2867,6 +2869,8 @@
 	return wpa_ctrl_command(ctrl, "WMM_AC_STATUS");
 }
 
+#endif /* CONFIG_NO_WMM_AC */
+
 
 static int wpa_cli_cmd_tdls_chan_switch(struct wpa_ctrl *ctrl, int argc,
 					char *argv[])
@@ -3889,6 +3893,7 @@
 	{ "tdls_link_status", wpa_cli_cmd_tdls_link_status, NULL,
 	  cli_cmd_flag_none,
 	  "<addr> = TDLS link status with <addr>" },
+#ifndef CONFIG_NO_WMM_AC
 	{ "wmm_ac_addts", wpa_cli_cmd_wmm_ac_addts, NULL,
 	  cli_cmd_flag_none,
 	  "<uplink/downlink/bidi> <tsid=0..7> <up=0..7> [nominal_msdu_size=#] "
@@ -3900,6 +3905,7 @@
 	{ "wmm_ac_status", wpa_cli_cmd_wmm_ac_status, NULL,
 	  cli_cmd_flag_none,
 	  "= show status for Wireless Multi-Media Admission-Control" },
+#endif /* CONFIG_NO_WMM_AC */
 	{ "tdls_chan_switch", wpa_cli_cmd_tdls_chan_switch, NULL,
 	  cli_cmd_flag_none,
 	  "<addr> <oper class> <freq> [sec_channel_offset=] [center_freq1=] "
diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c
index d4401ff..a851024 100644
--- a/wpa_supplicant/wpa_supplicant.c
+++ b/wpa_supplicant/wpa_supplicant.c
@@ -1,6 +1,6 @@
 /*
  * WPA Supplicant
- * Copyright (c) 2003-2022, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -65,6 +65,7 @@
 #include "wpas_kay.h"
 #include "mesh.h"
 #include "dpp_supplicant.h"
+#include "nan_usd.h"
 #ifdef CONFIG_MESH
 #include "ap/ap_config.h"
 #include "ap/hostapd.h"
@@ -584,7 +585,9 @@
 	wpa_tdls_deinit(wpa_s->wpa);
 #endif /* CONFIG_TDLS */
 
+#ifndef CONFIG_NO_WMM_AC
 	wmm_ac_clear_saved_tspecs(wpa_s);
+#endif /* CONFIG_NO_WMM_AC */
 	pmksa_candidate_free(wpa_s->wpa);
 	ptksa_cache_deinit(wpa_s->ptksa);
 	wpa_s->ptksa = NULL;
@@ -701,7 +704,9 @@
 		wpa_s->vendor_elem[i] = NULL;
 	}
 
+#ifndef CONFIG_NO_WMM_AC
 	wmm_ac_notify_disassoc(wpa_s);
+#endif /* CONFIG_NO_WMM_AC */
 
 	wpa_s->sched_scan_plans_num = 0;
 	os_free(wpa_s->sched_scan_plans);
@@ -717,7 +722,9 @@
 
 	wpabuf_free(wpa_s->lci);
 	wpa_s->lci = NULL;
+#ifndef CONFIG_NO_RRM
 	wpas_clear_beacon_rep_data(wpa_s);
+#endif /* CONFIG_NO_RRM */
 
 #ifdef CONFIG_PMKSA_CACHE_EXTERNAL
 #ifdef CONFIG_MESH
@@ -746,11 +753,17 @@
 	wpa_s->dpp = NULL;
 #endif /* CONFIG_DPP */
 
+#ifdef CONFIG_NAN_USD
+	wpas_nan_usd_deinit(wpa_s);
+#endif /* CONFIG_NAN_USD */
+
 #ifdef CONFIG_PASN
 	wpas_pasn_auth_stop(wpa_s);
 #endif /* CONFIG_PASN */
+#ifndef CONFIG_NO_ROBUST_AV
 	wpas_scs_deinit(wpa_s);
 	wpas_dscp_deinit(wpa_s);
+#endif /* CONFIG_NO_ROBUST_AV */
 
 #ifdef CONFIG_OWE
 	os_free(wpa_s->owe_trans_scan_freq);
@@ -1068,8 +1081,10 @@
 	if (state == WPA_DISCONNECTED || state == WPA_INACTIVE)
 		wpa_supplicant_start_autoscan(wpa_s);
 
+#ifndef CONFIG_NO_WMM_AC
 	if (old_state >= WPA_ASSOCIATED && wpa_s->wpa_state < WPA_ASSOCIATED)
 		wmm_ac_notify_disassoc(wpa_s);
+#endif /* CONFIG_NO_WMM_AC */
 
 	if (wpa_s->wpa_state != old_state) {
 		wpas_notify_state_changed(wpa_s, wpa_s->wpa_state, old_state);
@@ -2009,7 +2024,7 @@
 		(wpa_s->connection_ht || wpa_s->connection_vht ||
 		 wpa_s->connection_he || wpa_s->connection_eht);
 	if (!wmm && bss)
-		wmm = wpa_bss_get_vendor_ie(bss, WMM_IE_VENDOR_TYPE);
+		wmm = !!wpa_bss_get_vendor_ie(bss, WMM_IE_VENDOR_TYPE);
 	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_WMM_ENABLED, wmm);
 
 	if (!skip_default_rsne) {
@@ -2111,7 +2126,9 @@
 static void wpas_ext_capab_byte(struct wpa_supplicant *wpa_s, u8 *pos, int idx,
 				struct wpa_bss *bss)
 {
+#ifndef CONFIG_NO_ROBUST_AV
 	bool scs = true, mscs = true;
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	*pos = 0x00;
 
@@ -2127,7 +2144,9 @@
 	case 2: /* Bits 16-23 */
 #ifdef CONFIG_WNM
 		*pos |= 0x02; /* Bit 17 - WNM-Sleep Mode */
-		if (!wpa_s->disable_mbo_oce && !wpa_s->conf->disable_btm)
+		if ((wpas_driver_bss_selection(wpa_s) ||
+		     !wpa_s->disable_mbo_oce) &&
+		    !wpa_s->conf->disable_btm)
 			*pos |= 0x08; /* Bit 19 - BSS Transition */
 #endif /* CONFIG_WNM */
 		break;
@@ -2156,6 +2175,7 @@
 #endif /* CONFIG_MBO */
 		break;
 	case 6: /* Bits 48-55 */
+#ifndef CONFIG_NO_ROBUST_AV
 #ifdef CONFIG_TESTING_OPTIONS
 		if (wpa_s->disable_scs_support)
 			scs = false;
@@ -2169,6 +2189,7 @@
 		}
 		if (scs)
 			*pos |= 0x40; /* Bit 54 - SCS */
+#endif /* CONFIG_NO_ROBUST_AV */
 		break;
 	case 7: /* Bits 56-63 */
 		break;
@@ -2185,6 +2206,7 @@
 #endif /* CONFIG_FILS */
 		break;
 	case 10: /* Bits 80-87 */
+#ifndef CONFIG_NO_ROBUST_AV
 #ifdef CONFIG_TESTING_OPTIONS
 		if (wpa_s->disable_mscs_support)
 			mscs = false;
@@ -2198,6 +2220,7 @@
 		}
 		if (mscs)
 			*pos |= 0x20; /* Bit 85 - Mirrored SCS */
+#endif /* CONFIG_NO_ROBUST_AV */
 		break;
 	}
 }
@@ -2315,8 +2338,7 @@
 		if (style == WPAS_MAC_ADDR_STYLE_DEDICATED_PER_ESS) {
 			/* Pregenerated addresses do not expire but their value
 			 * might have changed, so let's check that. */
-			if (os_memcmp(wpa_s->own_addr, ssid->mac_value,
-				      ETH_ALEN) == 0)
+			if (ether_addr_equal(wpa_s->own_addr, ssid->mac_value))
 				return 0;
 		} else if ((wpa_s->last_mac_addr_change.sec != 0 ||
 			    wpa_s->last_mac_addr_change.usec != 0) &&
@@ -2495,7 +2517,12 @@
 
 	wpa_s->eapol_failed = 0;
 	wpa_s->multi_ap_ie = 0;
+#ifndef CONFIG_NO_WMM_AC
 	wmm_ac_clear_saved_tspecs(wpa_s);
+#endif /* CONFIG_NO_WMM_AC */
+#ifdef CONFIG_WNM
+	wpa_s->wnm_mode = 0;
+#endif /* CONFIG_WNM */
 	wpa_s->reassoc_same_bss = 0;
 	wpa_s->reassoc_same_ess = 0;
 #ifdef CONFIG_TESTING_OPTIONS
@@ -2506,7 +2533,9 @@
 		wpa_dbg(wpa_s, MSG_DEBUG, "Re-association to the same ESS");
 		wpa_s->reassoc_same_ess = 1;
 		if (wpa_s->current_bss && wpa_s->current_bss == bss) {
+#ifndef CONFIG_NO_WMM_AC
 			wmm_ac_save_tspecs(wpa_s);
+#endif /* CONFIG_NO_WMM_AC */
 			wpa_s->reassoc_same_bss = 1;
 		} else if (wpa_s->current_bss && wpa_s->current_bss != bss) {
 			os_get_reltime(&wpa_s->roam_start);
@@ -3074,7 +3103,7 @@
 	struct hostapd_hw_modes *mode = NULL;
 	int i, obss_scan = 1;
 	u8 channel;
-	bool is_6ghz;
+	bool is_6ghz, is_24ghz;
 
 	freq->freq = ssid->frequency;
 
@@ -3103,6 +3132,9 @@
 	if (!mode)
 		return;
 
+	is_24ghz = hw_mode == HOSTAPD_MODE_IEEE80211G ||
+		hw_mode == HOSTAPD_MODE_IEEE80211B;
+
 	is_6ghz = is_6ghz_freq(freq->freq);
 
 	freq->ht_enabled = 0;
@@ -3114,7 +3146,7 @@
 		freq->ht_enabled = ibss_mesh_can_use_ht(wpa_s, ssid, mode);
 	if (freq->ht_enabled)
 		freq->vht_enabled = ibss_mesh_can_use_vht(wpa_s, ssid, mode);
-	if (freq->vht_enabled || is_6ghz)
+	if (freq->vht_enabled || (freq->ht_enabled && is_24ghz) || is_6ghz)
 		freq->he_enabled = ibss_mesh_can_use_he(wpa_s, ssid, mode,
 							ieee80211_mode);
 	freq->channel = channel;
@@ -3256,8 +3288,10 @@
 	size_t wfa_ie_len, buf_len;
 
 	os_memset(wfa_capa, 0, sizeof(wfa_capa));
+#ifndef CONFIG_NO_ROBUST_AV
 	if (wpa_s->enable_dscp_policy_capa)
 		wfa_capa[0] |= WFA_CAPA_QM_DSCP_POLICY;
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	if (wpa_is_non_eht_scs_traffic_desc_supported(bss))
 		wfa_capa[0] |= WFA_CAPA_QM_NON_EHT_SCS_TRAFFIC_DESC;
@@ -3521,12 +3555,14 @@
 	os_memset(wpa_s->p2p_ip_addr_info, 0, sizeof(wpa_s->p2p_ip_addr_info));
 #endif /* CONFIG_P2P */
 
+#ifndef CONFIG_NO_RRM
 	if (bss) {
 		wpa_ie_len += wpas_supp_op_class_ie(wpa_s, ssid, bss,
 						    wpa_ie + wpa_ie_len,
 						    max_wpa_ie_len -
 						    wpa_ie_len);
 	}
+#endif /* CONFIG_NO_RRM */
 
 	/*
 	 * Workaround: Add Extended Capabilities element only if the AP
@@ -3753,6 +3789,7 @@
 		wpa_ie_len += wpa_s->rsnxe_len;
 	}
 
+#ifndef CONFIG_NO_ROBUST_AV
 #ifdef CONFIG_TESTING_OPTIONS
 	if (wpa_s->disable_mscs_support)
 		goto mscs_end;
@@ -3787,6 +3824,7 @@
 		wpabuf_free(mscs_ie);
 	}
 mscs_end:
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	wpa_ie_len = wpas_populate_wfa_capa(wpa_s, bss, wpa_ie, wpa_ie_len,
 					    max_wpa_ie_len);
@@ -4071,7 +4109,9 @@
 	wpa_sm_set_assoc_wpa_ie(wpa_s->wpa, NULL, 0);
 	wpa_sm_set_assoc_rsnxe(wpa_s->wpa, NULL, 0);
 	wpa_s->rsnxe_len = 0;
+#ifndef CONFIG_NO_ROBUST_AV
 	wpa_s->mscs_setup_done = false;
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	wpa_ie = wpas_populate_assoc_ies(wpa_s, bss, ssid, &params, NULL);
 	if (!wpa_ie) {
@@ -4466,7 +4506,8 @@
 			 * can stop right here; the association will not
 			 * succeed.
 			 */
-			wpas_connection_failed(wpa_s, wpa_s->pending_bssid);
+			wpas_connection_failed(wpa_s, wpa_s->pending_bssid,
+					       NULL);
 			wpa_s->assoc_status_code = WLAN_STATUS_UNSPECIFIED_FAILURE;
 			wpas_notify_assoc_status_code(wpa_s, wpa_s->pending_bssid, 0, NULL, 0);
 			wpa_supplicant_set_state(wpa_s, WPA_DISCONNECTED);
@@ -4562,8 +4603,10 @@
 	if (old_ssid != wpa_s->current_ssid)
 		wpas_notify_network_changed(wpa_s);
 
+#ifndef CONFIG_NO_ROBUST_AV
 	wpas_scs_deinit(wpa_s);
 	wpas_dscp_deinit(wpa_s);
+#endif /* CONFIG_NO_ROBUST_AV */
 	eloop_cancel_timeout(wpa_supplicant_timeout, wpa_s, NULL);
 }
 
@@ -5343,14 +5386,14 @@
 		       os_memcmp(ssid, entry->ssid, ssid_len) == 0)) ||
 		     wired) &&
 		    (!entry->bssid_set ||
-		     os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0))
+		     ether_addr_equal(bssid, entry->bssid)))
 			return entry;
 #ifdef CONFIG_WPS
 		if (!wpas_network_disabled(wpa_s, entry) &&
 		    (entry->key_mgmt & WPA_KEY_MGMT_WPS) &&
 		    (entry->ssid == NULL || entry->ssid_len == 0) &&
 		    (!entry->bssid_set ||
-		     os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0))
+		     ether_addr_equal(bssid, entry->bssid)))
 			return entry;
 #endif /* CONFIG_WPS */
 
@@ -5360,13 +5403,13 @@
 		     owe_trans_ssid_match(wpa_s, bssid, entry->ssid,
 					  entry->ssid_len)) &&
 		    (!entry->bssid_set ||
-		     os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0))
+		     ether_addr_equal(bssid, entry->bssid)))
 			return entry;
 #endif /* CONFIG_OWE */
 
 		if (!wpas_network_disabled(wpa_s, entry) && entry->bssid_set &&
 		    entry->ssid_len == 0 &&
-		    os_memcmp(bssid, entry->bssid, ETH_ALEN) == 0)
+		    ether_addr_equal(bssid, entry->bssid))
 			return entry;
 
 		entry = entry->next;
@@ -5494,7 +5537,7 @@
 #ifdef CONFIG_AP
 	     !wpa_s->ap_iface &&
 #endif /* CONFIG_AP */
-	     os_memcmp(src_addr, connected_addr, ETH_ALEN) != 0)) {
+	     !ether_addr_equal(src_addr, connected_addr))) {
 		/*
 		 * There is possible race condition between receiving the
 		 * association event and the EAPOL frame since they are coming
@@ -5524,7 +5567,7 @@
 	}
 
 	wpa_s->last_eapol_matches_bssid =
-		os_memcmp(src_addr, connected_addr, ETH_ALEN) == 0;
+		ether_addr_equal(src_addr, connected_addr);
 
 #ifdef CONFIG_AP
 	if (wpa_s->ap_iface) {
@@ -5681,7 +5724,7 @@
 		fst_update_mac_addr(wpa_s->fst, wpa_s->own_addr);
 #endif /* CONFIG_FST */
 
-	if (os_memcmp(prev_mac_addr, wpa_s->own_addr, ETH_ALEN) != 0)
+	if (!ether_addr_equal(prev_mac_addr, wpa_s->own_addr))
 		wpas_notify_mac_address_changed(wpa_s);
 
 	return 0;
@@ -5698,7 +5741,7 @@
 		return;
 	eth = (const struct l2_ethhdr *) buf;
 
-	if (os_memcmp(eth->h_dest, wpa_s->own_addr, ETH_ALEN) != 0 &&
+	if (!ether_addr_equal(eth->h_dest, wpa_s->own_addr) &&
 	    !(eth->h_dest[0] & 0x01)) {
 		wpa_dbg(wpa_s, MSG_DEBUG, "RX EAPOL from " MACSTR " to " MACSTR
 			" (bridge - not for this interface - ignore)",
@@ -5873,8 +5916,11 @@
 	dl_list_init(&wpa_s->fils_hlp_req);
 #ifdef CONFIG_TESTING_OPTIONS
 	dl_list_init(&wpa_s->drv_signal_override);
+	wpa_s->test_assoc_comeback_type = -1;
 #endif /* CONFIG_TESTING_OPTIONS */
+#ifndef CONFIG_NO_ROBUST_AV
 	dl_list_init(&wpa_s->active_scs_ids);
+#endif /* CONFIG_NO_ROBUST_AV */
 	wpa_s->ml_probe_mld_id = -1;
 
 	return wpa_s;
@@ -6387,7 +6433,7 @@
 {
 	struct wpa_supplicant *wpa_s = ctx;
 
-	if (os_memcmp(wpa_s->bssid, da, ETH_ALEN) != 0) {
+	if (!ether_addr_equal(wpa_s->bssid, da)) {
 		wpa_printf(MSG_INFO, "FST:%s:bssid=" MACSTR " != da=" MACSTR,
 			   __func__, MAC2STR(wpa_s->bssid), MAC2STR(da));
 		return -1;
@@ -6403,7 +6449,7 @@
 {
 	struct wpa_supplicant *wpa_s = ctx;
 
-	WPA_ASSERT(os_memcmp(wpa_s->bssid, addr, ETH_ALEN) == 0);
+	WPA_ASSERT(ether_addr_equal(wpa_s->bssid, addr));
 	return wpa_s->received_mb_ies;
 }
 
@@ -6414,7 +6460,7 @@
 	struct wpa_supplicant *wpa_s = ctx;
 	struct mb_ies_info info;
 
-	WPA_ASSERT(os_memcmp(wpa_s->bssid, addr, ETH_ALEN) == 0);
+	WPA_ASSERT(ether_addr_equal(wpa_s->bssid, addr));
 
 	if (!mb_ies_info_by_ies(&info, buf, size)) {
 		wpabuf_free(wpa_s->received_mb_ies);
@@ -7203,6 +7249,9 @@
 		return -1;
 	}
 
+	wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_FT_PREPEND_PMKID,
+			 wpa_s->conf->ft_prepend_pmkid);
+
 	wpa_s->hw.modes = wpa_drv_get_hw_feature_data(wpa_s,
 						      &wpa_s->hw.num_modes,
 						      &wpa_s->hw.flags,
@@ -7251,7 +7300,9 @@
 		wpa_s->extended_capa_len = capa.extended_capa_len;
 		wpa_s->num_multichan_concurrent =
 			capa.num_multichan_concurrent;
+#ifndef CONFIG_NO_WMM_AC
 		wpa_s->wmm_ac_supported = capa.wmm_ac_supported;
+#endif /* CONFIG_NO_WMM_AC */
 		wpa_s->max_num_akms = capa.max_num_akms;
 
 		if (capa.mac_addr_rand_scan_supported)
@@ -7339,6 +7390,11 @@
 		return -1;
 #endif /* CONFIG_DPP */
 
+#ifdef CONFIG_NAN_USD
+	if (wpas_nan_usd_init(wpa_s) < 0)
+		return -1;
+#endif /* CONFIG_NAN_USD */
+
 	if (wpa_supplicant_init_eapol(wpa_s) < 0)
 		return -1;
 	wpa_sm_set_eapol(wpa_s->wpa, wpa_s->eapol);
@@ -7408,7 +7464,9 @@
 	if (wpas_init_ext_pw(wpa_s) < 0)
 		return -1;
 
+#ifndef CONFIG_NO_RRM
 	wpas_rrm_reset(wpa_s);
+#endif /* CONFIG_NO_RRM */
 
 	wpas_sched_scan_plans_set(wpa_s, wpa_s->conf->sched_scan_plans);
 
@@ -8173,6 +8231,10 @@
 	if (wpa_s->conf->changed_parameters & CFG_CHANGED_DISABLE_BTM)
 		wpa_supplicant_set_default_scan_ies(wpa_s);
 
+	if (wpa_s->conf->changed_parameters & CFG_CHANGED_FT_PREPEND_PMKID)
+		wpa_sm_set_param(wpa_s->wpa, WPA_PARAM_FT_PREPEND_PMKID,
+				 wpa_s->conf->ft_prepend_pmkid);
+
 #ifdef CONFIG_BGSCAN
 	/*
 	 * We default to global bgscan parameters only when per-network bgscan
@@ -8241,7 +8303,8 @@
 }
 
 
-void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid)
+void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid,
+			    const u8 **link_bssids)
 {
 	int timeout;
 	int count;
@@ -8276,6 +8339,12 @@
 		return;
 	}
 
+	/* Also mark links as failed */
+	while (link_bssids && *link_bssids) {
+		wpa_bssid_ignore_add(wpa_s, *link_bssids);
+		link_bssids++;
+	}
+
 	/*
 	 * Add the failed BSSID into the ignore list and speed up next scan
 	 * attempt if there could be other APs that could accept association.
@@ -8692,9 +8761,16 @@
 int pmf_in_use(struct wpa_supplicant *wpa_s, const u8 *addr)
 {
 	if (wpa_s->current_ssid == NULL ||
-	    wpa_s->wpa_state < WPA_4WAY_HANDSHAKE ||
-	    os_memcmp(addr, wpa_s->bssid, ETH_ALEN) != 0)
+	    wpa_s->wpa_state < WPA_4WAY_HANDSHAKE)
 		return 0;
+	if (wpa_s->valid_links) {
+		if (!ether_addr_equal(addr, wpa_s->ap_mld_addr) &&
+		    !wpas_ap_link_address(wpa_s, addr))
+			return 0;
+	} else {
+		if (!ether_addr_equal(addr, wpa_s->bssid))
+			return 0;
+	}
 	return wpa_sm_pmf_enabled(wpa_s->wpa);
 }
 
@@ -8819,8 +8895,8 @@
 		return 0;
 
 	for (i = 0; i < wpa_s->disallow_aps_bssid_count; i++) {
-		if (os_memcmp(wpa_s->disallow_aps_bssid + i * ETH_ALEN,
-			      bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(wpa_s->disallow_aps_bssid + i * ETH_ALEN,
+				     bssid))
 			return 1;
 	}
 
@@ -9130,7 +9206,7 @@
 
 	dl_list_for_each(bss, &wpa_s->bss_tmp_disallowed,
 			 struct wpa_bss_tmp_disallowed, list) {
-		if (os_memcmp(bssid, bss->bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(bssid, bss->bssid))
 			return bss;
 	}
 
@@ -9213,7 +9289,7 @@
 
 	dl_list_for_each_safe(tmp, prev, &wpa_s->bss_tmp_disallowed,
 			 struct wpa_bss_tmp_disallowed, list) {
-		if (os_memcmp(bss->bssid, tmp->bssid, ETH_ALEN) == 0) {
+		if (ether_addr_equal(bss->bssid, tmp->bssid)) {
 			disallowed = tmp;
 			break;
 		}
@@ -9311,8 +9387,7 @@
 
 		dl_list_for_each(dso, &wpa_s->drv_signal_override,
 				 struct driver_signal_override, list) {
-			if (os_memcmp(wpa_s->bssid, dso->bssid,
-				      ETH_ALEN) != 0)
+			if (!ether_addr_equal(wpa_s->bssid, dso->bssid))
 				continue;
 			wpa_printf(MSG_DEBUG,
 				   "Override driver signal_poll information: current_signal: %d->%d avg_signal: %d->%d avg_beacon_signal: %d->%d current_noise: %d->%d",
@@ -9357,7 +9432,7 @@
 
 		dl_list_for_each(dso, &wpa_s->drv_signal_override,
 				 struct driver_signal_override, list) {
-			if (os_memcmp(res->bssid, dso->bssid, ETH_ALEN) != 0)
+			if (!ether_addr_equal(res->bssid, dso->bssid))
 				continue;
 			wpa_printf(MSG_DEBUG,
 				   "Override driver scan signal level %d->%d for "
@@ -9379,7 +9454,7 @@
 }
 
 
-static bool wpas_ap_link_address(struct wpa_supplicant *wpa_s, const u8 *addr)
+bool wpas_ap_link_address(struct wpa_supplicant *wpa_s, const u8 *addr)
 {
 	int i;
 
@@ -9390,7 +9465,7 @@
 		if (!(wpa_s->valid_links & BIT(i)))
 			continue;
 
-		if (os_memcmp(wpa_s->links[i].bssid, addr, ETH_ALEN) == 0)
+		if (ether_addr_equal(wpa_s->links[i].bssid, addr))
 			return true;
 	}
 
diff --git a/wpa_supplicant/wpa_supplicant.conf b/wpa_supplicant/wpa_supplicant.conf
index f6e4f83..d689441 100644
--- a/wpa_supplicant/wpa_supplicant.conf
+++ b/wpa_supplicant/wpa_supplicant.conf
@@ -982,9 +982,11 @@
 # parameter uses following format: "<bgscan module name>:<module parameters>"
 # Following bgscan modules are available:
 # simple - Periodic background scans based on signal strength
+# send_btm_query > 0 means do this many BTM queries before attempting a scan.
 # bgscan="simple:<short bgscan interval in seconds>:<signal strength threshold>:
-# <long interval>"
+# <long interval>[:<send_btm_query>]"
 # bgscan="simple:30:-45:300"
+# bgscan="simple:30:-45:300:3"
 # learn - Learn channels used by the network and try to avoid bgscans on other
 # channels (experimental)
 # bgscan="learn:<short bgscan interval in seconds>:<signal strength threshold>:
diff --git a/wpa_supplicant/wpa_supplicant_i.h b/wpa_supplicant/wpa_supplicant_i.h
index 0890110..3a5f61c 100644
--- a/wpa_supplicant/wpa_supplicant_i.h
+++ b/wpa_supplicant/wpa_supplicant_i.h
@@ -1,6 +1,6 @@
 /*
  * wpa_supplicant - Internal definitions
- * Copyright (c) 2003-2014, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2024, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -494,6 +494,7 @@
 	u8 bssid[ETH_ALEN];
 	enum beacon_report_detail report_detail;
 	struct bitfield *eids;
+	struct bitfield *ext_eids;
 };
 
 
@@ -678,6 +679,13 @@
 };
 
 
+struct ml_sta_link_info {
+	u8 link_id;
+	u8 bssid[ETH_ALEN];
+	u16 status;
+};
+
+
 /**
  * struct wpa_supplicant - Internal data for wpa_supplicant interface
  *
@@ -740,11 +748,12 @@
 	u8 ap_mld_addr[ETH_ALEN];
 	u8 mlo_assoc_link_id;
 	u16 valid_links; /* bitmap of valid MLO link IDs */
-	struct ml_sta_link_info {
+	struct {
 		u8 addr[ETH_ALEN];
 		u8 bssid[ETH_ALEN];
 		unsigned int freq;
 		struct wpa_bss *bss;
+		bool disabled;
 	} links[MAX_NUM_MLD_LINKS];
 	u8 *last_con_fail_realm;
 	size_t last_con_fail_realm_len;
@@ -1046,6 +1055,7 @@
 		bool ext_ml_auth;
 		int *sae_rejected_groups;
 #endif /* CONFIG_SAE */
+		u16 assoc_auth_type;
 	} sme;
 #endif /* CONFIG_SME */
 
@@ -1207,6 +1217,7 @@
 	struct wpa_ssid *bgscan_ssid;
 	const struct bgscan_ops *bgscan;
 	void *bgscan_priv;
+	int signal_threshold;
 
 	const struct autoscan_ops *autoscan;
 	struct wpa_driver_scan_params *autoscan_params;
@@ -1315,6 +1326,7 @@
 	u8 wnm_reply;
 	u8 wnm_num_neighbor_report;
 	u8 wnm_mode;
+	bool wnm_link_removal;
 	u16 wnm_dissoc_timer;
 	u8 wnm_bss_termination_duration[12];
 	struct neighbor_report *wnm_neighbor_report_elements;
@@ -1381,6 +1393,7 @@
 	unsigned int oci_freq_override_fils_assoc;
 	unsigned int oci_freq_override_wnm_sleep;
 	unsigned int disable_eapol_g2_tx;
+	int test_assoc_comeback_type;
 #endif /* CONFIG_TESTING_OPTIONS */
 
 	struct wmm_ac_assoc_data *wmm_ac_assoc_info;
@@ -1567,8 +1580,24 @@
 	unsigned int multi_ap_ie:1;
 	unsigned int multi_ap_backhaul:1;
 	unsigned int multi_ap_fronthaul:1;
+
+#ifndef CONFIG_NO_ROBUST_AV
 	struct robust_av_data robust_av;
 	bool mscs_setup_done;
+	struct scs_robust_av_data scs_robust_av_req;
+	u8 scs_dialog_token;
+	struct dl_list active_scs_ids;
+	bool ongoing_scs_req;
+	u8 dscp_req_dialog_token;
+	u8 dscp_query_dialog_token;
+	unsigned int enable_dscp_policy_capa:1;
+	unsigned int connection_dscp:1;
+	unsigned int wait_for_dscp_req:1;
+#ifdef CONFIG_TESTING_OPTIONS
+	unsigned int disable_scs_support:1;
+	unsigned int disable_mscs_support:1;
+#endif /* CONFIG_TESTING_OPTIONS */
+#endif /* CONFIG_NO_ROBUST_AV */
 
 	bool wps_scan_done; /* Set upon receiving scan results event */
 	bool supp_pbc_active; /* Set for interface when PBC is triggered */
@@ -1580,19 +1609,7 @@
 	unsigned int pasn_count;
 	struct pasn_auth *pasn_params;
 #endif /* CONFIG_PASN */
-	struct scs_robust_av_data scs_robust_av_req;
-	u8 scs_dialog_token;
-#ifdef CONFIG_TESTING_OPTIONS
-	unsigned int disable_scs_support:1;
-	unsigned int disable_mscs_support:1;
-#endif /* CONFIG_TESTING_OPTIONS */
-	struct dl_list active_scs_ids;
-	bool ongoing_scs_req;
-	u8 dscp_req_dialog_token;
-	u8 dscp_query_dialog_token;
-	unsigned int enable_dscp_policy_capa:1;
-	unsigned int connection_dscp:1;
-	unsigned int wait_for_dscp_req:1;
+
 	bool is_6ghz_enabled;
 	bool crossed_6ghz_dom;
 	bool last_scan_all_chan;
@@ -1609,6 +1626,12 @@
 	 * owe_transition_search == 1 */
 	int *owe_trans_scan_freq;
 #endif /* CONFIG_OWE */
+
+#ifdef CONFIG_NAN_USD
+	struct nan_de *nan_de;
+	struct wpa_radio_work *nan_usd_listen_work;
+	struct wpa_radio_work *nan_usd_tx_work;
+#endif /* CONFIG_NAN_USD */
 };
 
 
@@ -1715,7 +1738,8 @@
 			     enum frame_encryption encrypted);
 void wpa_supplicant_update_config(struct wpa_supplicant *wpa_s);
 void wpa_supplicant_clear_status(struct wpa_supplicant *wpa_s);
-void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid);
+void wpas_connection_failed(struct wpa_supplicant *wpa_s, const u8 *bssid,
+			    const u8 **link_bssids);
 void fils_connection_failure(struct wpa_supplicant *wpa_s);
 void fils_pmksa_cache_flush(struct wpa_supplicant *wpa_s);
 int wpas_driver_bss_selection(struct wpa_supplicant *wpa_s);
@@ -2015,5 +2039,6 @@
 bool wpas_is_6ghz_supported(struct wpa_supplicant *wpa_s, bool only_enabled);
 
 bool wpa_is_non_eht_scs_traffic_desc_supported(struct wpa_bss *bss);
+bool wpas_ap_link_address(struct wpa_supplicant *wpa_s, const u8 *addr);
 
 #endif /* WPA_SUPPLICANT_I_H */
diff --git a/wpa_supplicant/wpas_glue.c b/wpa_supplicant/wpas_glue.c
index 9804b91..111680c 100644
--- a/wpa_supplicant/wpas_glue.c
+++ b/wpa_supplicant/wpas_glue.c
@@ -418,7 +418,7 @@
 	const u8 *ie;
 
 	dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
-		if (os_memcmp(bss->bssid, wpa_s->bssid, ETH_ALEN) != 0)
+		if (!ether_addr_equal(bss->bssid, wpa_s->bssid))
 			continue;
 		if (ssid == NULL ||
 		    ((bss->ssid_len == ssid->ssid_len &&
diff --git a/wpa_supplicant/wpas_module_tests.c b/wpa_supplicant/wpas_module_tests.c
index ce5398c..9e7a57c 100644
--- a/wpa_supplicant/wpas_module_tests.c
+++ b/wpa_supplicant/wpas_module_tests.c
@@ -17,9 +17,12 @@
 static int wpas_bssid_ignore_module_tests(void)
 {
 	struct wpa_supplicant wpa_s;
+	struct wpa_global global;
 	int ret = -1;
 
 	os_memset(&wpa_s, 0, sizeof(wpa_s));
+	os_memset(&global, 0, sizeof(global));
+	wpa_s.global = &global;
 
 	wpa_bssid_ignore_clear(&wpa_s);
 
diff --git a/wpa_supplicant/wps_supplicant.c b/wpa_supplicant/wps_supplicant.c
index cd94b64..81e11e7 100644
--- a/wpa_supplicant/wps_supplicant.c
+++ b/wpa_supplicant/wps_supplicant.c
@@ -295,8 +295,7 @@
 		if (ssid->bssid_set || new_ssid->bssid_set) {
 			if (ssid->bssid_set != new_ssid->bssid_set)
 				continue;
-			if (os_memcmp(ssid->bssid, new_ssid->bssid, ETH_ALEN) !=
-			    0)
+			if (!ether_addr_equal(ssid->bssid, new_ssid->bssid))
 				continue;
 		}
 
@@ -1062,7 +1061,7 @@
 		 */
 #ifndef CONFIG_P2P
 		dl_list_for_each(bss, &wpa_s->bss, struct wpa_bss, list) {
-			if (os_memcmp(bssid, bss->bssid, ETH_ALEN) != 0)
+			if (!ether_addr_equal(bssid, bss->bssid))
 				continue;
 
 			os_free(ssid->ssid);
@@ -1812,7 +1811,7 @@
 	}
 
 	if (!ret && ssid->bssid_set &&
-	    os_memcmp(ssid->bssid, bss->bssid, ETH_ALEN) == 0) {
+	    ether_addr_equal(ssid->bssid, bss->bssid)) {
 		/* allow wildcard SSID due to hardcoded BSSID match */
 		ret = 1;
 	}
@@ -1851,11 +1850,11 @@
 				    const u8 *sel_uuid)
 {
 	if (!ap->pbc_active ||
-	    os_memcmp(selected->bssid, ap->bssid, ETH_ALEN) == 0)
+	    ether_addr_equal(selected->bssid, ap->bssid))
 		return false;
 
 	if (!is_zero_ether_addr(ssid->bssid) &&
-	    os_memcmp(ap->bssid, ssid->bssid, ETH_ALEN) != 0) {
+	    !ether_addr_equal(ap->bssid, ssid->bssid)) {
 		wpa_printf(MSG_DEBUG, "WPS: Ignore another BSS " MACSTR
 			   " in active PBC mode due to local BSSID limitation",
 			   MAC2STR(ap->bssid));
@@ -2943,7 +2942,7 @@
 
 	for (i = 0; i < wpa_s->num_wps_ap; i++) {
 		struct wps_ap_info *ap = &wpa_s->wps_ap[i];
-		if (os_memcmp(ap->bssid, bssid, ETH_ALEN) == 0)
+		if (ether_addr_equal(ap->bssid, bssid))
 			return ap;
 	}