diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json index 0a8c34539e3..c23b80218a2 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/schema/wireless.wifi-iface.json @@ -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": { diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc index 606e7b164f4..de474351f96 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/ap.uc @@ -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; diff --git a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc index 0ee0f261f5c..4cddc03d43c 100644 --- a/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc +++ b/package/network/config/wifi-scripts/files-ucode/usr/share/ucode/wifi/iface.uc @@ -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) {