wifi-scripts: no RSNO2E where the RSNE offers its items

On an EHT BSS or an AP MLD with sae, psk3, sae-mixed or psk3-mixed, the
RSNE Override 2 element carries SAE-EXT-KEY and GCMP-256 (CCMP without
phy support). An explicit sae_ext_key 1 and gcmp256 1 also put both
items into the RSNE, and the element stayed as a duplicate.

A configuration that asks for an RSNE with AKM 24 and GCMP-256 and an
RSNXE only sets sae_ext_key 1 and gcmp256 1. The Beacon then had an
RSNO2E and an RSNXOE in addition.

A duplicate RSNO2E adds nothing. A client sees both items in the RSNE,
and the RSNE meets WPA3 Specification v3.5 2.5 items 4 and 5.

Build the element only when the RSNE lacks its AKM or its pairwise
cipher. Without an RSNO2E, hostapd writes no RSNXOE either
(wpa_auth_gen_wpa_ie()). The default keeps the element as before, with
the client fixes of commits fc652db52a and 5e067465ff, and so does a
BSS with one of the two options. One exception: without phy support for
GCMP-256, the element carries SAE-EXT-KEY and CCMP, which an RSNE with
sae_ext_key 1 already offers, so that BSS sends no element. 0 still
keeps an item out of both elements. sae-compat keeps its own elements.

On an AP MLD, the decision covers all links (ap.uc mld_rsno2()): WPA3
Specification v3.5 14.2 requires an identical RSNE Override 2 element
on all links of an AP MLD that uses RSN overriding. The element goes
only where the RSNE of every link offers both items. A psk2 or
psk-mixed AP MLD has PSK in the RSNE of its 2.4 and 5 GHz links, so all
its links keep the element, also the 6 GHz link, whose RSNE has SAE. An
AP MLD of sae whose RSNE has both items on every link uses no RSN
overriding. IEEE 802.11be-2024 12.6.2 then requires the same RSNE on
every link except the AKM list and MFPR, and one common AKM. The links
share SAE and SAE-EXT-KEY, also on a 6 GHz link.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
Felix Fietkau committed 2026-10-08 09:50:39 +02:00
1 parent f3c75e3562
commit e0e11b78f7
3 files changed
+49 -15

No files matched your search

@@ -397,7 +397,7 @@
"type": "string"
},
"gcmp256": {
"description": "Advertise the GCMP-256 pairwise cipher on HE/EHT BSSes (alongside CCMP for sae/sae-mixed, in the RSNE Override 2 element for sae-compat). Mandatory for EHT (WPA3 Specification v3.5 2.5), recommended otherwise. Off by default, because some clients and chipsets misbehave when GCMP-256 is offered. With an EHT htmode, sae, sae-mixed and sae-compat BSSes offer GCMP-256 in the RSNE Override 2 element unless this option is 0; 1 also offers it in the RSNE. Only advertised when the driver supports the GCMP-256 cipher.",
"description": "Advertise the GCMP-256 pairwise cipher on HE/EHT BSSes (alongside CCMP for sae/sae-mixed, in the RSNE Override 2 element for sae-compat). Mandatory for EHT (WPA3 Specification v3.5 2.5), recommended otherwise. Off by default, because some clients and chipsets misbehave when GCMP-256 is offered. With an EHT htmode, sae, sae-mixed and sae-compat BSSes offer GCMP-256 in the RSNE Override 2 element unless this option is 0; 1 also offers it in the RSNE. Only advertised when the driver supports the GCMP-256 cipher. The sae and sae-mixed BSSes send no RSNE Override 2 element when the RSNE already offers SAE-EXT-KEY and GCMP-256 (CCMP without driver support for GCMP-256). An AP MLD sends none only when this holds on every link, so a psk2 AP MLD sends it on every link unless sae_ext_key is 0.",
"type": "boolean"
},
"hidden": {
@@ -1021,7 +1021,7 @@
"type": "boolean"
},
"sae_ext_key": {
"description": "Advertise the SAE-EXT-KEY AKM (SAE using a group-dependent hash) alongside plain SAE. Mandatory for EHT (WPA3 Specification v3.5 2.5), recommended otherwise. Off by default in the RSNE, because some clients misbehave when SAE-EXT-KEY is offered there. With an EHT htmode, sae, sae-mixed and sae-compat BSSes offer it in the RSNE Override 2 element unless this option is 0; 1 also offers it in the RSNE (not for sae-compat).",
"description": "Advertise the SAE-EXT-KEY AKM (SAE using a group-dependent hash) alongside plain SAE. Mandatory for EHT (WPA3 Specification v3.5 2.5), recommended otherwise. Off by default in the RSNE, because some clients misbehave when SAE-EXT-KEY is offered there. With an EHT htmode, sae, sae-mixed and sae-compat BSSes offer it in the RSNE Override 2 element unless this option is 0; 1 also offers it in the RSNE (not for sae-compat). The sae and sae-mixed BSSes send no RSNE Override 2 element when the RSNE already offers SAE-EXT-KEY and GCMP-256 (CCMP without driver support for GCMP-256). An AP MLD sends none only when this holds on every link, so a psk2 AP MLD sends it on every link unless sae_ext_key is 0.",
"type": "boolean"
},
"sae_password_file": {
@@ -89,14 +89,33 @@ function station_password_count(stas) {
return n;
}
/* a refusal on any band refuses every link of the AP MLD */
function mld_refusal(link_config, dev_config, phy_features) {
function mld_links(link_config, dev_config, phy_features, rsno2) {
let links = [];
for (let band in link_config.mlo_bands) {
let config = { ...link_config };
config.encryption = iface.encryption_band(config.encryption, band, config.mlo_bands);
iface.parse_encryption(config, { ...dev_config, band }, phy_features);
iface.parse_encryption(config, { ...dev_config, band }, phy_features, rsno2);
push(links, { band, config });
}
const reason = bss_refusal(config, band);
return links;
}
/* WPA3 Specification v3.5 14.2: an AP MLD includes an identical RSNE
* Override 2 element on all links, so a link that needs it adds it to all */
function mld_rsno2(link_config, dev_config, phy_features) {
for (let link in mld_links(link_config, dev_config, phy_features))
if (link.config.rsno2_sae)
return true;
return false;
}
/* a refusal on any band refuses every link of the AP MLD */
function mld_refusal(link_config, dev_config, phy_features, rsno2) {
for (let link in mld_links(link_config, dev_config, phy_features, rsno2)) {
const reason = bss_refusal(link.config, link.band);
if (reason)
return reason;
}
@@ -691,12 +710,15 @@ export function generate(interface, data, config, vlans, stas, phy_features) {
config.sae_station_passwords = station_password_count(stas);
const link_config = { ...config };
const mld = config.mlo && length(config.mlo_bands);
const rsno2 = mld ? mld_rsno2(link_config, data.config, phy_features) : null;
config.encryption = iface.encryption_band(config.encryption, data.config.band,
config.mlo ? (config.mlo_bands ?? []) : null);
iface.parse_encryption(config, data.config, phy_features);
iface.parse_encryption(config, data.config, phy_features, rsno2);
const refusal = (config.mlo && length(config.mlo_bands)) ?
mld_refusal(link_config, data.config, phy_features) : bss_refusal(config, data.config.band);
const refusal = mld ?
mld_refusal(link_config, data.config, phy_features, rsno2) : bss_refusal(config, data.config.band);
if (refusal)
return refusal;
@@ -61,6 +61,11 @@ function encryption_map(encryption, modes) {
return join('+', enc);
}
function rsne_offers_rsno2(config, rsno2_pairwise) {
return !!config.sae_ext_key && config.auth_type in [ 'sae', 'psk-sae' ] &&
index(split(config.wpa_pairwise ?? '', ' '), rsno2_pairwise) >= 0;
}
/* mld_bands: null for a single-link BSS, else the bands of the AP MLD */
export function encryption_band(encryption, band, mld_bands) {
if (band == '6g')
@@ -85,7 +90,9 @@ export function encryption_sta_band(encryption, band, mld_bands) {
return encryption_map(encryption, encryption_sta_mixed_6g);
};
export function parse_encryption(config, dev_config, phy_features) {
/* rsno2: null decides the RSNO2E for this link alone, a bool gives the
* decision of the AP MLD */
export function parse_encryption(config, dev_config, phy_features, rsno2) {
if (!config.encryption)
config.encryption = 'none';
@@ -105,19 +112,20 @@ export function parse_encryption(config, dev_config, phy_features) {
* WPA3 Specification v3.5 2.5 requires SAE-EXT-KEY and GCMP-256 with
* EHT or MLO. Some clients fail when these are offered in the RSNE, so on
* EHT they go into the RSNE Override 2 element. Explicit sae_ext_key and
* gcmp256 options apply to the RSNE; 0 also keeps them out of RSNO2.
* gcmp256 options apply to the RSNE; 0 also keeps them out of RSNO2. An
* RSNE that already offers the AKM and the pairwise cipher of the RSNO2E
* makes the RSNO2E a duplicate, so the BSS sends none. An AP MLD sends
* none only where that holds on every link (ap.uc mld_rsno2()).
*/
let eht = wildcard(dev_config?.htmode ?? '', 'EHT*');
let compat = (config.auth_type == 'sae-compat');
let rsno2_mode = config.auth_type in [ 'sae', 'psk3', 'sae-mixed', 'psk3-mixed' ] ||
(!!config.mlo && config.auth_type == 'psk2');
/* all links of an AP MLD must reach the same decision */
config.rsno2_sae = (eht || !!config.mlo) && rsno2_mode && config.sae_ext_key !== false;
let rsno2_gcmp256 = config.gcmp256 !== false && phy_features?.cipher_gcmp256;
let rsno2_sae = (eht || !!config.mlo) && rsno2_mode && config.sae_ext_key !== false;
let rsno2_pairwise = (config.gcmp256 !== false && phy_features?.cipher_gcmp256) ? 'GCMP-256' : 'CCMP';
config.gcmp256 ??= compat && eht;
config.sae_ext_key ??= compat && eht;
if (config.rsno2_sae)
config.rsn_override_pairwise_2 = rsno2_gcmp256 ? 'GCMP-256' : 'CCMP';
switch(config.auth_type) {
case 'owe':
@@ -213,6 +221,10 @@ export function parse_encryption(config, dev_config, phy_features) {
config.wpa_pairwise ??= 'GCMP-256 CCMP';
else
config.wpa_pairwise ??= 'CCMP';
config.rsno2_sae = rsno2_sae && (rsno2 ?? !rsne_offers_rsno2(config, rsno2_pairwise));
if (config.rsno2_sae)
config.rsn_override_pairwise_2 = rsno2_pairwise;
};
export function wpa_key_mgmt(config, band) {