Commit Graph
5270 Commits
Author SHA1 Message Date
Nick Hainke 22aa500f74 qosify: update to Git HEAD (2026-10-14)
16c95b04c020 main: tear down the DNS ifb device on exit
d8b47df02cdf interface: fix the socket() error check in qosify_iface_init()
21cc61818226 interface: fix the ifb device name for long interface names
bbb96c522f39 interface: add option for multiqueue cake
39889ba2d94b interface: only use cake_mq on devices with several transmit queues
b2f9e2818d1c interface: create the ifb device with one queue per receive queue
c6983494fe42 qosify-bpf: only override unmarked packets with a fallback value
551491cead1c dns: check the length of address records
f840a74077f3 qosify-bpf: classify IPv4 fragments like their first fragment
90c756581a62 interface: factor out the setup of filter netlink messages
aa3aa6b52c63 interface: redirect DNS replies with one flower filter
ddd72827f252 qosify-bpf: add a macro for timeouts in seconds
b3ee50eff9d9 map: accept port 65535 in port rules

DNS replies are now redirected with flower, add kmod-sched-flower to
the dependencies. Add the multiqueue option to the config.

Link: https://github.com/openwrt/openwrt/pull/25696
Signed-off-by: Nick Hainke <vincent@systemli.org>
2026-10-16 20:40:45 +02:00
Satoshi Ikeuchi 99426e4f7d wifi-scripts: identify the IPQ9574 radio
Add the IPQ9574 compatible to the radio hardware-name table used by
the ucode iwinfo implementation.

Link: https://github.com/openwrt/openwrt/pull/25595#issuecomment-6009991907
Signed-off-by: Satoshi Ikeuchi <satosan.ikm@gmail.com>
Link: https://github.com/openwrt/openwrt/pull/25595
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-16 13:58:50 +02:00
Felix Fietkau 863679b8cc wifi-scripts: write snoop_iface only for a BSS with proxy_arp
iface_setup() and hostapd_set_bss_options() write snoop_iface, the
netifd network device, for every BSS. hostapd reads the option only in
x_snoop.c, and hostapd_setup_bss() starts that code only for a BSS with
proxy_arp. When the network of a BSS changes, the value changes, and
hostapd.uc cannot apply snoop_iface to a running BSS: it replaces the
BSS and every station on it loses its association:

  bss ap-mld1 changed in snoop_iface, replacing it

Write snoop_iface only where proxy_arp is enabled. A BSS with proxy_arp
is still replaced when its network changes, since the snooping sockets
are bound to that device.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Felix Fietkau 62100ef999 wpa_supplicant: use an object for the phy list of mld_add()
mld_add() keeps the phy devices it opens in phy_list, under the name
of the phy. A caller without a list gets an array from mld_add(), and
an array has no place for a string key.

Make the default an object, like the list that mld_start() passes.

Fixes: 9aca8a97d7 ("wpa_supplicant: add MLO client support")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Felix Fietkau 5b32ecf8ae wifi-scripts: mac80211.sh: declare he_spr_sr_control as a number
he_spr_sr_control is the SR Control field, a number (default 3), but
the script declared it boolean. netifd's parse_bool() turns any value
other than 0 or 1 into null, so a configured 2 reached hostapd as the
default 3, with PSR Disallowed set, and bit 4 was lost.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Felix Fietkau 8c544d299e wifi-scripts: mac80211.sh: pass the SRG spatial reuse settings to hostapd
The shell scripts lacked what the ucode scripts gained in "wifi-scripts:
pass the SRG spatial reuse settings to hostapd": the SRG OBSS PD offsets
and the SRG BSS colour and partial BSSID lists, and SR Control bit 2
set by the presence of the non-SRG offset. An offset of 0 left the
field out of the element.

Add he_spr_srg_obss_pd_min_offset, he_spr_srg_obss_pd_max_offset,
he_spr_srg_bss_colors and he_spr_srg_partial_bssid. Either SRG offset
sets bit 3, and the non-SRG offset sets bit 2 whenever it is given.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Felix Fietkau 619cdb5ac4 wifi-scripts: clear psr_enabled before the SR Control bits take it
device_htmode_append() cleared he_spr_psr_enabled for a PHY without
PSR-based spatial reuse support only after it built he_spr_sr_control.
Where BSS Color is enabled and the configuration gives
he_spr_sr_control with bit 0 clear and he_spr_psr_enabled set, a radio
without PSR then advertised PSR as allowed. The schema default of 3
already has bit 0 set.

Clear it first, then build the bits.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Felix Fietkau 5bcbf3aa77 wifi-scripts: pass the SRG spatial reuse settings to hostapd
The Spatial Reuse Parameter Set takes SRG OBSS PD offsets and the SRG
BSS colour and partial BSSID bitmaps (IEEE Std 802.11-2024 9.4.2.251),
and hostapd reads them as he_spr_srg_obss_pd_min_offset,
he_spr_srg_obss_pd_max_offset, he_spr_srg_bss_colors and
he_spr_srg_partial_bssid. The schema had none of them, so a radio could
not run with SRG information.

Add the four options. Either offset sets SR Control bit 3 (SRG
Information Present). The non-SRG offset now sets bit 2 by its
presence: an offset of 0 is a valid offset, and before it left the
field out of the element.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-14 16:31:46 +02:00
Shiji Yang 556dc4525b nftables: update to version 1.1.7
Changes: https://www.netfilter.org/pub/nftables/changes-nftables-1.1.7.txt

The apk size changes from 299044 to 305258 on mipsel_24kc target.

Upstream merged patches:
- 000-build-fix-configure-with-non-bash-shell.patch
- 001-build-simplify-the-instantiation-of-nftversion-h.patch
- 002-build-generate-build-time-stamp-once-at-configure.patch
- 003-build-support-SOURCE_DATE_EPOCH-for-build-time-stamp.patch

Signed-off-by: Shiji Yang <yangshiji66@outlook.com>
Link: https://github.com/openwrt/openwrt/pull/25253
[Removed unneeded fixup autoreconf]
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-10-13 01:17:20 +02:00
Shiji Yang 5ee6f1fdbe wifi-scripts: fix handling for 0 dBm TX power setting
When txpower is set to 0 dBm, the current script will set it to auto.

Signed-off-by: Shiji Yang <yangshiji66@outlook.com>
Link: https://github.com/openwrt/openwrt/pull/25479
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-10-13 00:55:17 +02:00
Julius Bairaktaris cc5e87cfad bpftool: update to 7.8.0
Drop 003-skip-crypto.patch, which is part of this release, and refresh
002-includes.patch.

Changes:
https://github.com/libbpf/bpftool/compare/v7.7.0...v7.8.0

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25725
Signed-off-by: Nick Hainke <vincent@systemli.org>
2026-10-11 09:49:28 +02:00
Julius Bairaktaris 2bbb6cbac7 libbpf: update to 1.8.0
Release notes:
https://github.com/libbpf/libbpf/releases/tag/v1.8.0

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25725
Signed-off-by: Nick Hainke <vincent@systemli.org>
2026-10-11 09:49:28 +02:00
Nick Hainke 84802285f4 hostapd: pack hostapd-utils and wpa-cli in one variant only
hostapd-utils and wpa-cli use VARIANT:=*, so every variant packs them.
Since the variants are built in parallel, they write the same package
file and pkginfo files at the same time. This makes the build fail
randomly:

  Package hostapd-utils is missing dependencies for the following libraries:
  libgcc_s.so.1

Only pack them in the first selected variant that builds them.

Fixes: b029d56e0b ("hostapd: build the variants in parallel")
Link: https://github.com/openwrt/openwrt/pull/25796
Signed-off-by: Nick Hainke <vincent@systemli.org>
2026-10-11 09:26:39 +02:00
Julius Bairaktaris ad18024862 ppp: build the PPPoA plugin without libatm
The PPPoA plugin only uses text2atm() and text2qos() from libatm. ppp
ships copies of both and uses them unless configured --with-atm, so
link the bundled copies and drop the dependency on linux-atm.

Assisted-by: Claude:claude-opus-5-5
Signed-off-by: Julius Bairaktaris <julius@bairaktaris.de>
Link: https://github.com/openwrt/openwrt/pull/25707
Signed-off-by: Robert Marko <robimarko@gmail.com>
2026-10-08 15:23:35 +02:00
Felix Fietkau 665fcb0f05 hostapd: ubus get_status reports the spatial reuse settings
The spatial reuse settings that a radio runs with were not visible at
runtime: get_status did not report them, and nl80211 does not carry
them. A radio with he_spr_sr_control=3 could not be told apart from an
unrestricted one.

get_status now adds, beside the BSS Color fields, at the top level and
in each entry of the links of an AP MLD, the running values of the
radio's configuration:
- he_spr_sr_control, he_spr_non_srg_obss_pd_max_offset,
  he_spr_srg_obss_pd_min_offset and he_spr_srg_obss_pd_max_offset, as
  numbers;
- he_spr_srg_bss_colors and he_spr_srg_partial_bssid, as the bitmap
  value with bit n for colour or partial BSSID n (IEEE Std 802.11-2024
  9.4.2.251), in 16 lowercase hex digits. The element holds the value
  little-endian; a ubus integer is signed, and colour 63 is bit 63.
  he_spr_srg_bss_colors=7 8 reads "0000000000000180".

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau e0e11b78f7 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>
2026-10-08 09:50:39 +02:00
Felix Fietkau f3371c7625 hostapd: abort a pending interface setup when its config is removed
iface_restart() aborts the pending setup of a phy before it removes the
interface, but iface_config_remove() does not. A config_set without
interfaces, the shutdown and the removal of the last link of an AP MLD
left the setup pending, and its wpa_supplicant reply later added an
interface for a configuration that no longer exists.

Abort the pending setup in iface_config_remove() as well.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 1e0e1065b0 hostapd: undo a failed AP BSS add in the nl80211 driver
When nl80211_setup_ap() fails in wpa_driver_nl80211_if_add(), the new
i802_bss stays linked into the driver and in *drv_priv. For an AP MLD
BSS, the caller's cleanup then runs driver_nl80211_link_remove() on
that BSS, which reports EVENT_MLD_INTERFACE_FREED with a NULL context,
and hostapd crashes in hostapd_wpa_event(). A failure of
nl80211_set_mode() leaks the BSS.

198-nl80211-undo-a-failed-ap-bss-add undoes the add on both failures,
as the other error paths of the function do. Upstream hostap has the
same code.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau a75b4c405f hostapd: continue a pending interface setup from the event loop
The bss_remove hook of hostapd.uc sends a service event with a
synchronous ubus call. While libubus waits for that reply, it runs the
completion callbacks of other pending requests, among them the
wpa_supplicant phy_status or phy_set_state request of an interface
setup in progress. The callback advanced the setup to
hostapd.add_iface() nested inside the teardown that called the hook.

With an AP MLD, a config_set for radio 0 followed by one for radio 1
added the radio 0 link while the radio 1 link was half removed and
still held its driver BSS and management frame registrations. The add
failed with "Match already configured", and its cleanup crashed hostapd
in hostapd_wpa_event(). This happened on the first such reconfiguration
after a bring-up.

Run the next step of the pending setup from a uloop timer instead, and
cancel the timer where the pending request is aborted.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
John Crispin 0e1e3a0919 hostapd: ACS scans for all links of an AP MLD with one request
The links of an AP MLD share one wiphy, which runs one scan at a time.
With ACS on several links, each link scans for itself, and every link
after the first gets -EBUSY and retries every 5 seconds.

172-acs-scan-for-all-links-of-an-ap-mld-with-one-request (from John
Crispin) adds the channels of every partner link in ACS to the scan
request, and hands the survey of that scan to each link. The partner
scan check of hostapd_driver_scan() from 171 moves into
hostapd_mld_partner_scanning(). Changes to the patch as received:
- every link, the requesting one included, continues from the event
  loop. The partner fallback of EVENT_SCAN_RESULTS otherwise runs the
  scan callback of a partner whose next scan a link started from its
  own callback, and counts that scan as completed at once;
- every link keeps its scan callback until the survey is fetched, so
  that each link of the scan gets its survey on whichever link the
  driver reports it;
- a link that the scan of a partner link includes waits at most
  ACS_SCAN_WAIT_TIMEOUT seconds for its results and then scans for
  itself, so that it does not stay in ACS for good when the results
  never come, for example when the partner link is torn down during the
  scan;
- acs_cleanup() drops the ACS scan callback, so that a link disabled
  during its scan does not make hostapd_mld_partner_scanning() refuse
  the scans of its partner links.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau c0c3751cd8 wifi-scripts: map the encryption of an MLD station by all its bands
setup_sta() mapped the encryption mode by the band of the radio that
rendered the network block. An MLD station on 2.4, 5 and 6 GHz radios
was set up for 6 GHz, so psk2 became sae with PMF required. The
station then skipped a WPA2-PSK BSS on 2.4 GHz.

An AP MLD maps each link by its own band: the 6 GHz link takes SAE,
and the other links keep psk2. A station has one network block for
all its links, so it must allow the AKM of each band it can join.

netifd passes mlo_bands to an MLD station as well. With 6 GHz among
other bands, psk2 now becomes sae-mixed and wpa2 becomes wpa3-mixed.
wpa_supplicant removes the PSK AKMs for a 6 GHz BSS (events.c, "WPA
PSK is not allowed on the 6 GHz band"), so the station uses SAE
there. PMF optional meets the MFPR that a 6 GHz BSS sets. With 6 GHz
bands only, the 6 GHz mapping applies as before. Other modes keep
their station behaviour on a mixed MLD.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau a7c1609a28 hostapd: undo BSS Color Disabled if no colour change starts
hostapd_switch_color_timeout_handler() sets he_bss_color_disabled
before it starts the colour change of each BSS of the radio. The
driver installs the CCA beacons, which carry BSS Color Disabled, only
for a change it starts. Where hostapd_drv_switch_color() fails, or the
settings cannot be built, for every BSS, no beacon carries the disabled
state, but the configuration keeps it: get_status reports -1 for a
colour the beacons still use, and a later beacon rebuild disables BSS
Color without a colour change.

607-ap-restore-bss-color-disabled-when-no-color-change-starts counts
the colour changes that started, and where none did, restores the
state BSS Color Disabled had before. That also resumes the ageing of
the OBSS colours, which the disabled state paused (174). The
bss-color-change notification after the loop (606) then reports the
restored state.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau ae3fabae25 hostapd: ubus notifies a change of the BSS Color fields
A subscriber that reports the BSS Color and the OBSS colours of a radio
must know when they change without a configuration change. The kernel announces a colour change that the
driver starts, aborts or completes, but hostapd takes the new colour,
or leaves BSS Color disabled, only when it handles that event. Some
changes have no event at all:
- with no free colour left, the collision timeout disables BSS Color
  and picks a new colour itself;
- a collision timeout whose colour change the driver does not start
  leaves BSS Color disabled;
- the OBSS colours of the radio change.

606-ap-notify-ubus-of-a-bss-color-change calls the new
hostapd_ubus_notify_bss_color():
- in the no free colour branch of the collision timeout;
- after the colour change loop of the collision timeout. This call
  fires in every case, whether the driver started the change or not;
- at the end of hostapd_event_color_change(), after hostapd took the
  new colour at a completion, or left BSS Color disabled at an abort;
- when the aged set of OBSS colours of the radio changes, as a colour
  is added or leaves it (174), and not at each repeated collision
  event.

It sends the bss-color-change notification, { ifname, bssid, link_id }
(link_id for an affiliated AP of an AP MLD), on the object of the BSS.
get_status carries the new values.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 5fe8fade02 hostapd: ubus get_status adds partial and in-use BSS colours
A subscriber that reports the BSS colours of a radio needs three
fields: the BSS Color subfield and the Partial BSS Color subfield that
the radio transmits, and the colours of the OBSSs that the AP
identified itself or learnt from the BSS Color Collision reports of its
stations. get_status named the
colour, as -1 while BSS Color is disabled, and neither of the others.

hostapd kept its colours in use per BSS, and each collision event
replaced them. It also added the colour of a BSS Color In Use report,
which names a colour a station uses with a peer (IEEE Std 802.11-2024
11.21.2.8), not an OBSS colour. 174-ap-keep-the-obss-colors-of-a-radio
keeps two sets per radio:
- the OBSS colours, which the kernel's collision events and the BSS
  Color Collision reports of stations add to. A colour leaves the set
  60 s after an event named it, or 130 s after a report did (the
  longest dot11BSSColorCollisionSTAPeriod, 120 s, plus a 10 s margin),
  and the ageing pauses while BSS Color is disabled;
- the colours of BSS Color In Use reports, held per station until a
  report of colour 0 or until the station leaves.
A colour change picks a colour outside both sets.

get_status now adds, beside bss_color, at the top level and in each
entry of the links of an AP MLD:
- he_bss_color: the BSS Color subfield of the HE Operation element.
  It keeps the colour while BSS Color is disabled
  (IEEE Std 802.11-2024 26.11.4); bss_color keeps -1 then;
- bss_color_partial: he_bss_color_partial of the radio;
- bss_colors_in_use: the OBSS colours of the radio, 1 to 63. The
  colours of BSS Color In Use reports are not among them.

On mt76 the kernel's collision events name the colliding colour only:
mt7996 does not detect collisions itself, and mac80211
(ieee80211_rx_check_bss_color_collision()) reports an OBSS whose colour
equals the BSS's own. Other OBSS colours reach the set only through
the BSS Color Collision reports of stations.

The colours are a list rather than the 64-bit bitmap, because a blobmsg
int64 with bit 63 set reads as a negative number in ucode and JSON.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 02f3f670f2 hostapd: ubus get_status lists the links of an AP MLD
The links of an AP MLD share one ubus object, and get_status describes
the BSS of one link (mld->fbss, or the first link that joined the
object). Each affiliated AP runs on its own radio, with its own
hostapd_iface and so its own channel and BSS Color. A subscriber could
read the BSS Color of one radio of an AP MLD only, not the BSS Color
that each radio transmits.

For an AP MLD, get_status now adds links [{ link_id, bssid, freq,
bss_color }], one entry per link that joined the object: the link ID,
the BSSID of the affiliated AP, its frequency, and its BSS Color with
the meaning of the top-level field (-1 while BSS Color is disabled).
The top-level fields are unchanged. A BSS that is not affiliated with
an AP MLD gets no new field.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 0772ebc5ae hostapd: ubus key-mismatch names the BSSID of its link
The links of an AP MLD share one ubus object, and the ifname of the
key-mismatch event is the MLD netdev. A subscriber could only take the
BSSID of get_status, the BSSID of the first BSS of the MLD, whichever
link the station used. A subscriber that reports a failed connection
needs the BSSID of the BSS that the station tried to connect to, so it
named the wrong BSS for an attempt on another link.

hostapd_ubus_notify_key_mismatch() now adds:
- bssid: own_addr of the hostapd_data that handles the failure, the
  BSSID of the link for an affiliated AP, as it is: the link of the
  SAE Authentication frame, or the association link for the 4-way
  handshake;
- link_id: the link ID of that affiliated AP, for an AP MLD only.

The 4-way PSK failure of hostapd_wpa_auth_psk_failure_report() (an
invalid MIC in EAPOL message 2) now goes through the same function, so
its event carries the two fields too. It carries no rsn_error:
HOSTAPD_UBUS_RSN_ERROR_NONE (0, a reserved value) leaves the field out.
RSN Error 3 (EAPOL message 2) belongs to a separate patch that changes
the psk_failure_report callback to pass the EAPOL-Key frame. Until that
patch lands, the event of a PSK mismatch on an RSNA BSS carries no
rsn_error. The SAE Confirm event does not change otherwise.

The two lines the call takes in 600 move the wpa_auth_glue.c hunks of
601 and 730 by one; refresh them so that the series applies without
offsets.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 27608c1fc7 wifi-scripts: wifi-station PSKs with a user's wpa_psk_file
iface_wpa_stations() wrote the wifi-station PSKs to
/var/run/hostapd-<ifname>.psk and named that file only where the user
had not set wpa_psk_file. With a user's file, hostapd read that file
alone, and the wifi-station PSKs never reached it, as it was for SAE
before the commit "wifi-scripts: wifi-station SAE passwords with a
user's file".

hostapd keeps one wpa_psk_file: config_file.c replaces the path at each
wpa_psk_file line, and hostapd_config_read_wpa_psk() reads lines of
"[keyid=..] [wps=..] [vlanid=..] <MAC> <PSK or passphrase>", skips
empty lines and lines that start with '#', and fails the setup of the
BSS when the file is missing. A second wpa_psk_file line is therefore
no option, and the user's lines are copied into the generated file.

Move the copy that iface_sae_stations() does into station_file_write()
and use it for both files: it writes the user's lines, then the
wifi-station lines, and names the generated file; a user's file that
cannot be read stays named, so hostapd reports it as before.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 79cb1d926f wifi-scripts: wifi-station SAE passwords with a user's file
iface_sae_stations() wrote the wifi-station SAE passwords to
/var/run/hostapd-<ifname>.sae and named that file only where the user
had not set sae_password_file. With a user's file, hostapd read that
file alone, and the wifi-station passwords never reached it.

hostapd's parse_sae_password_file() adds each line of the file to the
SAE password list, and each sae_password_file line reads one more file,
so two lines would work for hostapd. The hostapd service keeps one hash
per file option (file_fields in hostapd.uc), though, and a second line
would replace the hash of the first, so a change in one of the files
could go unnoticed. Copy the lines of the user's file into the generated
file instead, before the wifi-station entries, and always name the
generated file. Where the user's file cannot be read, keep naming it, so
hostapd reports it as before.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau a89d1c9d7e wifi-scripts: refuse a key of 64 characters that is not hex
key_kind() took every key of 64 characters as a raw PSK, and ap.uc wrote
it as wpa_psk, or as multi_ap_backhaul_wpa_psk for the Multi-AP backhaul
key. hostapd's config_file.c reads both with hexstr2bin() and counts a
key that is not 64 hex digits as an error ("Invalid PSK"), so it
rejected the configuration of the whole radio, and every BSS on it
stayed down.

Take a key of 64 characters as a raw PSK only where all of them are hex
digits, upper or lower case, and as invalid otherwise. bss_refusal()
then refuses that BSS with INVALID_WPA_PSK, for the BSS key and for the
backhaul key alike, and the other BSSes of the radio come up. A psk BSS
without a key stays as it is: outside 6 GHz, wpa_psk_file gives its
per-station PSKs.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 6abd9c2ac5 wifi-scripts: refuse an AP MLD on every link
Each radio renders its own link of an AP MLD, and bss_refusal() looked
at that link only. Some refusals depend on the band: a 6 GHz link of a
transition mode is SAE only, and WPS with its backhaul key runs outside
6 GHz only. With a raw PSK, the 6 GHz link of a sae-mixed AP MLD was
refused while its 2.4 and 5 GHz links came up, and a Multi-AP backhaul
key that is no key refused the 2.4 and 5 GHz links but not 6 GHz.

All links run one configuration. For an AP MLD, mld_refusal() maps and
parses the configuration of each band in mlo_bands, in its order, and
returns the first refusal.

The RSNE Override 2 element of a psk2 AP MLD, and so MLD_SAE_NO_PASSWORD,
followed the htmode of the radio that renders the link. A link on an HE
radio was written while the EHT links were refused. hostapd refuses
mld_ap without IEEE 802.11be ("Cannot enable mld_ap when IEEE 802.11be
is disabled"), so parse_encryption() now treats an AP MLD as EHT for
that element. Every link gets the same result, so either the AP MLD is
refused on every link with the same reason, or on none.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 762c707df2 netifd: parse_bool() accepts JSON booleans and numbers
parse_bool() compared its argument with the strings "1", "true", "0"
and "false" only. A service-injected wireless config arrives as JSON, so
a value of true, false, 1 or 0 gave null. wireless.uc then read
"mlo": true of such a wifi-iface as no MLO, and parse_attribute_list()
dropped every boolean attribute that came as JSON.

Accept true and 1 as true, and false and 0 as false. The result for the
UCI strings stays the same.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau d9340555b1 wifi-scripts: declare the rsn_override option in the schema
Commit adb1fce19b ("wifi-scripts: set rsn_overriding for client mode
interfaces") made setup_sta() write rsn_overriding=1 for a station with
rsn_override set and an HE or EHT htmode. The wifi-iface schema did not
declare rsn_override, so netifd dropped the option before the handler
ran, and that branch could not be reached.

Declare it as a boolean without a default.

Fixes: adb1fce19b ("wifi-scripts: set rsn_overriding for client mode interfaces")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 01494494ce wifi-scripts: allow GCMP-256 for EHT stations
WPA3 Specification v3.5 2.5 item 10: "A STA that enables EHT or MLO
shall, in its Network Profile, allow GCMP-256 to be selected as a
pairwise cipher." setup_sta() wrote pairwise=CCMP for every RSN mode,
because parse_encryption() got no phy features in station mode.

Read the GCMP-256 support of the phy in supplicant.generate() for an
EHT htmode, and write pairwise=CCMP GCMP-256 for an RSN station when
the phy supports GCMP-256. An explicit cipher or gcmp256 0 is kept.
The GCMP-256 test moves to iface.phy_cipher_gcmp256(), which
hostapd.uc uses as well.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 17557a6692 wifi-scripts: wpa2 for wpa and wpa-mixed on any AP MLD
An AP MLD with wpa or wpa-mixed and no 6 GHz link enabled WPA version
1, which has no RSNE, so a multi-link association had no RSN parameters
to use. With a 6 GHz link these modes already became wpa2.

Render wpa and wpa-mixed as wpa2 on every AP MLD, as psk and psk-mixed
become psk2.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 1e81475343 wifi-scripts: offer SAE in the RSNO2E of a psk2 AP MLD
An AP MLD with psk, psk2 or psk-mixed advertised PSK alone. A
multi-link association then used PSK. On an AP MLD with a 6 GHz link,
where the 6 GHz link uses SAE, the links had no AKM in common.

For every AP MLD, keep PSK in the RSNE of the 2.4 and 5 GHz links for
single-link stations, and offer SAE with MFP required in the RSNE
Override 2 element of every link. A multi-link association with RSN
overriding uses that element (WPA3 Specification v3.5 14.2), so it
always uses SAE, and all links share it. The 6 GHz link keeps SAE in
its RSNE. psk and psk-mixed become psk2, because an AP MLD needs RSN
on every link and WPA3 Specification v3.5 2.5 item 1 forbids WPA
version 1 beside WPA3-Personal.

The element carries the SAE-EXT-KEY AKM, which IEEE 802.11be-2024
12.12.9 requires for SAE between EHT stations, with GCMP-256 (CCMP
without phy support), as for the WPA3-Personal modes on EHT. psk2 on
an AP MLD now sets up SAE:

- the password file, sae_pwe, sae_groups and sae_require_mfp;
- okc on by default, as for the SAE and OWE auth types, so that its
  SAE stations get PMKSA caching;
- with ieee80211r, FT-SAE-EXT-KEY in the element. hostapd's local FT
  response covers FT-PSK only ("Whether to generate FT response locally
  for PSK networks", hostapd.conf), so ft_psk_generate_local stays off,
  the r0kh and r1kh key holders are written, and bss_refusal() requires
  a key to derive them from.

A key of 64 hex digits is a raw PSK, which SAE cannot use. Without
another SAE password, every SAE authentication would end in "SAE: No
password available", so bss_refusal() refuses such a BSS with
MLD_SAE_NO_PASSWORD, as an SAE-only BSS without a password is refused.
The BSS is left out of the radio, and netifd shows the reason as the
`error` of the interface. The same sources give SAE a password as
there: a passphrase, PPSK, a wifi-station password, a
sae_password_file option, or a sae_password or sae_password_file line
in hostapd_bss_options.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau f513748946 wifi-scripts: SAE-EXT-KEY and GCMP-256 in the EHT RSNO2E
WPA3 Specification v3.5 2.5, which applies to all WPA3-Personal modes:
"4. If an AP's BSS Configuration enables EHT or MLO, it shall enable AKM
suite selector 00-0F-AC:24. 5. If an AP's BSS Configuration enables EHT
or MLO. It shall enable GCMP-256 as a pairwise cipher." Items 9 and 10
require the same of a station profile that enables EHT or MLO.

Commits fc652db52a and 5e067465ff kept both out of the RSNE of sae
and sae-mixed, because clients failed with them there, and every
station parses the RSNE.

Advertise them in the RSNE Override 2 element, for sae, psk3,
sae-mixed and psk3-mixed with an EHT htmode: SAE-EXT-KEY, GCMP-256 (or
CCMP when the phy has no GCMP-256), and MFP required. The RSNE keeps
its AKM and cipher lists. A station reads the override elements only
if it supports RSN overriding, and a multi-link association uses the
RSNE Override 2 element (WPA3 Specification v3.5 14.2). sae-compat
keeps its own elements. With ieee80211r the element also carries
FT-SAE-EXT-KEY, as it does for sae-compat.

An explicit sae_ext_key 1 or gcmp256 1 also offers the item in the RSNE,
and the RSNE Override 2 element stays as it is. Only 0 keeps the item
out of both elements.

The station profile allows SAE-EXT-KEY on EHT (2.5 item 9).

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 6b3278fa8d wifi-scripts: share an AKM on all links of a 6 GHz AP MLD
IEEE 802.11be-2024 12.6.2: "All APs affiliated with an AP MLD shall
advertise the same RSNE, and RSNXE if included, with the exception of
the AKM Suite List field and the MFPR subfield of the RSN Capabilities
field" and "shall advertise at least one common AKM suite selector".

The 6 GHz link of an AP MLD renders none as owe and the WPA2-Enterprise
modes as wpa3. The other links kept open or WPA, so some links had no
RSNE, or the links had no common AKM.

netifd now passes the bands of all radios of an AP MLD to each link as
mlo_bands. With a 6 GHz band among them, the other links render none
as owe, and wpa and wpa-mixed as wpa2. Every link then shares OWE or
WPA-EAP-SHA256 with the 6 GHz link, and the MFPC of the links agrees.
For such an AP MLD, this also meets WPA3 Specification v3.5 11.3,
which forbids legacy open with EHT or MLO. A disabled radio, which runs no
link, and a radio without a band option add nothing to mlo_bands.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 150b99fd18 wifi-scripts: set up a sae-compat station as Transition Mode
setup_sta() had no case for the auth type psk-sae-compat, so a station
with encryption sae-compat got key_mgmt=NONE in every band.

WPA3 Specification v3.5 2.4: "There is no separate WPA3-Personal
Compatibility Mode for STAs. A STA in WPA3-Personal Transition Mode or
a WPA2-Personal mode can, irrespective of whether it supports RSN
overriding, connect with an AP in WPA3-Personal Compatibility Mode".

Treat sae-compat as sae-mixed in station mode, which 2.3 defines for a
station: WPA-PSK, WPA-PSK-SHA256 and SAE with PMF capable, and SAE
alone in the 6 GHz band. RSN overriding stays off, as before.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau ffa99e2960 wifi-scripts: sae-compat MLD: accept SAE on partner links
In an AP MLD with sae-compat, the 6 GHz link has SAE in its base RSNE,
and the 2.4 and 5 GHz links have WPA-PSK there and SAE in the RSN
Override element. A station without RSN overriding support that sets up
the MLD on the 6 GHz link with SAE sends SAE in the per-STA profile of a
partner link. hostapd's wpa_validate_wpa_ie() matches that against the
base wpa_key_mgmt of the partner link, WPA-PSK, and rejects it.

hostap's hostapd/hostapd.conf documents the option for this case:
"Allow MLO partner link association from a non-RSNO-capable STA that
negotiates an AKM from the override set (e.g., SAE on the 6 GHz primary
link) but whose partner link per-STA profile RSNE would not match the
base wpa_key_mgmt of that link (e.g., WPA-PSK on 2.4 GHz in WPA3-Personal
Compatibility Mode). When enabled, the AP MLD falls back to
rsn_override_key_mgmt for the partner link key_mgmt validation".
wpa_validate_wpa_ie() uses it only for a partner link, and only when
the base AKM list has no match.

Write rsn_override_mlo_compat=1 on every sae-compat MLD link outside
6 GHz. A link does not know the bands of its partner links, and the
option has no effect when no partner link offers SAE in its base RSNE.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 800ef75653 wifi-scripts: set up wpa3-mixed stations
parse_encryption() turns wpa3-mixed into the auth type eap-eap2, which
setup_sta() did not handle: the station got key_mgmt=NONE and no EAP
settings, so it could not join a WPA3-Enterprise transition network.

Handle eap-eap2 with the other EAP auth types, and set ieee80211w to 1
(optional) unless it is set, as the AP side does for this mode.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 1b2fb79a4b wifi-scripts: map the 6 GHz encryption mode for stations too
In the 6 GHz band an AP turns none into owe, the psk modes into sae, the
WPA2-Enterprise modes into wpa3, and a transition mode into its WPA3
part. A station with the same configuration kept WPA-PSK, NONE, the WPA
protocol or the 802.1X SHA-1 AKM. wpa_supplicant skips a 6 GHz BSS
without an RSNE and removes the PSK AKMs there (events.c, "WPA PSK is
not allowed on the 6 GHz band"), so that station could not join. WPA3
Specification v3.5 11.2 also says that a WPA3 station connecting there
"shall not allow ... 802.1X SHA-1 AKM".

Move encryption_band() from ap.uc to iface.uc and apply it in
setup_sta() for mode sta. Mesh and ad-hoc keep their own handling.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau ef76457063 wifi-scripts: no OWE transition BSS on 6 GHz
With encryption owe and owe_transition, setup() writes a hidden OWE BSS
and an open companion BSS that points to it. IEEE 802.11-2024 12.12.2
allows no open BSS in the 6 GHz band. hostapd refused the companion
there, which stopped the radio. Since none became owe in that band, the
companion was a second OWE BSS that pointed to the first.

Decide the pair in ap.owe_transition(), which answers no for the 6 GHz
band, and let setup() call it. A 6 GHz BSS is then one plain OWE BSS
with its configured SSID.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 4fbf6b826d wifi-scripts: refuse an SAE-only BSS without a password
hostapd's sae_get_password() takes an SAE password entry, the
wpa_passphrase, or a passphrase that RADIUS returns for the station.
wifi-scripts writes a key of 64 hex digits as wpa_psk, a raw PSK, which
SAE cannot use. With encryption sae, hostapd accepted the BSS, and every
SAE authentication then ended in "SAE: No password available". Since
psk, psk2 and psk-mixed become sae in the 6 GHz band, the same happened
to them there, and to sae-compat, whose 6 GHz base RSNE has SAE only.

Refuse the BSS with SAE_NO_PASSWORD in bss_refusal() when SAE is the
only password AKM and it has no password: no passphrase, no PPSK, no
wifi-station password, no sae_password_file option and no sae_password
or sae_password_file line in hostapd_bss_options. The BSS is left out of
the radio, and netifd shows the reason as the `error` of the interface.
station_password_count() counts the entries iface_sae_stations()
writes, before the BSS is generated. Transition and compatibility modes
outside 6 GHz keep their PSK AKM, which works with a raw PSK, so they
are not refused.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 739b1f7484 wifi-scripts: leave a BSS that cannot run out of the radio
For an invalid key, an invalid Multi-AP backhaul key and an FT key that
cannot be derived, ap.generate() called netifd.setup_failed(). That
logs the reason and stops later retries, but the handler still wrote
the BSS into the hostapd configuration and set the radio up. A reason
of one BSS also concerned the radio as a whole.

Decide these reasons in bss_refusal(), after parse_encryption() and
before ap.generate() writes anything. A refused BSS is left out:
- ap.bss_add() rewinds the configuration to where the BSS started and
  reports the reason with netifd.bss_failed(), which logs the section
  and the ifname of the BSS;
- netifd.bss_failed() sends CMD_SET_DATA for the interface with an
  `error` field, which netifd shows in "ubus call network.wireless
  status";
- hostapd.uc setup_interface() gives the index of a refused BSS to the
  next one, so the first BSS that is written has the `interface=` line;
- setup() skips the OWE transition companion of a refused BSS.
The other BSSes of the radio come up. setup_failed() stays for the
radio-level reasons HOSTAPD_START_FAILED, SUPPLICANT_START_FAILED and
BRIDGE_NOT_ALLOWED.

netifd's wdev_set_data() replaces the handler data of the interface, so
the data that bss_failed() sends drops the ifname that set_vif() gave
before. A refused interface has no ifname in the status, and readers
rely on that, as the BSS has no netdev.

With mbssid, the first BSS written is the transmitting BSS. Where a
refusal changes which BSS comes first, the hostapd service can find no
match for the new first BSS, logs "First BSS changed with MBSSID
enabled" and restarts the radio.

wps_enabled() and ft_psk_local_default() hold the conditions that
iface_wps() and iface_roaming() used, so the refusal and the written
BSS use the same ones.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau a7b21264b1 wifi-scripts: show a BSS setup error in the wireless status
A handler can report data for one wifi-iface section through notify()
with CMD_SET_DATA, and wdev_set_data() keeps it as the handler data of
that section. get_status_data() copied only the ifname from it into the
interface entry of "ubus call network.wireless status".

Copy an `error` field too, so a handler that leaves one interface out of
the radio can name the reason where the interface is listed. The handler
data is reset on each setup and teardown, so the error lasts until the
next setup of the radio.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 6ac9cf15c9 wifi-scripts: accept an 8 character backhaul passphrase
iface_wps() wrote multi_ap_backhaul_key as a passphrase only above 8
characters, so a valid passphrase of 8 characters reported
INVALID_WPA_PSK, and one above 63 characters reached hostapd, which
rejects it. IEEE 802.11-2024 J.4.1: "passPhrase is a sequence of
between 8 and 63 ASCII-encoded characters".

Test both keys with key_kind(), which iface_auth_type() uses for the
BSS key as well: 64 characters are a raw PSK, 8 to 63 a passphrase.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 7519263cb1 wifi-scripts: no WPS on 6 GHz
A WPS enrollee associates with Open System authentication and without
an RSNE; hostapd's check_assoc_ies() accepts a station with a WPS IE
and drops its RSNE. IEEE 802.11-2024 12.12.2 forbids Open System
authentication without encryption in the 6 GHz band.

iface_wps() enabled WPS on any BSS that iface_auth_type() marked
wps_possible (open, OWE and the personal modes) when wps_pushbutton or
wps_label was set, whatever the band. hostapd_config_check_bss_6g()
does not look at wps_state, so the 6 GHz link of an AP MLD ran WPS, and
an enrollee was accepted with Open System authentication there.

Clear wps_possible in iface_wps() for the 6 GHz band, as it already does
for multi_ap 1. iface_wps() is the one place that decides whether WPS is
written, so the rule holds for every auth type that sets wps_possible.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau e6ae8f914a wifi-scripts: CCMP in the sae-compat RSNO2E without GCMP-256
On a sae-compat BSS, the SAE-EXT-KEY AKM goes into the RSN Override 2
element. generate() writes that element only when its AKM list, its
pairwise cipher and its MFP setting are all set, and parse_encryption()
set the pairwise cipher only for GCMP-256. With sae_ext_key on and no
GCMP-256, because gcmp256 is off or the phy does not support it, the AKM
was silently dropped. Commit fc652db52a meant the AKM and the cipher
to follow their own options.

hostap's hostapd/hostapd.conf says the second override set is "required
to be used for Wi-Fi 7 (EHT/MLO) associations with RSN overriding", and
sae-compat enables sae_ext_key by default on EHT. On a phy without
GCMP-256, an EHT sae-compat BSS therefore had no such element.

Use CCMP for the RSN Override 2 element when sae_ext_key is set and
GCMP-256 is not used.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau 53c7adda92 wifi-scripts: enable PMF by default on EHT APs
IEEE 802.11be-2024 12.12.9 says "An EHT non-AP STA and EHT AP shall
have dot11BeaconProtectionEnabled set to 1 when using RSN", and its note
adds that such an AP has PMF activated, which its RSNE advertises with
MFPC = 1. 12.6.2 requires all APs of an AP MLD to advertise the same
RSNE except for the AKM list and MFPR, so the MFPC of the links must
agree.

psk2, psk-mixed, wpa2 and wpa-mixed left ieee80211w at 0 on EHT, so
hostapd enabled no beacon protection. In an AP MLD with a 6 GHz link,
which requires PMF, the 2.4 and 5 GHz links then advertised MFPC = 0
and the 6 GHz link MFPC = 1.

With an EHT htmode, default ieee80211w to 1 (optional) for every mode
with WPA2 or later except sae-compat. An explicit ieee80211w still
wins.

WPA3 Specification v3.5 2.4 item 4a, WPA3-Personal Compatibility Mode:
"The AP shall set MFPC in the RSNE in the 2.4 and 5 GHz bands to 1 if
it has enabled functionality that requires PMF to be enabled with
WPA2-Personal (e.g. Wi-Fi Agile Multiband), or to 0 otherwise. The AP
shall set MFPR to 0 in the RSNE in the 2.4 and 5 GHz bands." The base
RSNE of sae-compat therefore keeps MFPC 0 unless mbo, ocv or dpp needs
PMF. The RSNE Override elements still set MFPR, so hostapd enables
beacon protection, which IEEE 802.11be-2024 12.12.9 requires for EHT.
In an AP MLD, a station uses the RSNE Override 2 element, which is the
same on every link (WPA3 Specification v3.5 14.2).

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00
Felix Fietkau b5608ead44 wifi-scripts: use OWE for an open BSS on 6 GHz
IEEE 802.11-2024 12.12.2 forbids an open BSS without encryption in the
6 GHz band. It names the replacement: "The STA shall use Opportunistic
Wireless Encryption (12.14), when connecting in an infrastructure BSS
without authentication (as a replacement for Open System authentication
without encryption)". hostapd_config_check_bss_6g() refuses the open
form, so encryption none stopped a 6 GHz radio, and in an AP MLD the phy
of every link.

In the 6 GHz band, replace none with owe. An unset encryption is none.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:39 +02:00