Commit Graph
897 Commits
Author SHA1 Message Date
Felix Fietkau 86d3d90753 hostapd: answer a frame from an unknown station on its own link
The class 3 deauth went out on the link whose address equals the AP MLD
address, where a station on another link never takes it.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 529f60df8e hostapd: unlink a BSS from its AP MLD when ucode add_bss fails
The error path of iface.add_bss() freed an AP MLD link that
hostapd_setup_bss() had already added to mld->links, without the MLD
reference it took. The next link to join that MLD wrote through the
freed node, which crashed hostapd after "Failed to add new bss ap-mld0
on phy phy0.0" and the restart of phy0.0 that follows it. The error
path now unlinks the BSS, drops the reference and frees an MLD left
unused, as hostapd_add_iface() and bss.delete() do.

Patch 182 makes hostapd_mld_remove_link() skip a node that is on no
list, which the new unlink reaches for a setup that fails before the
link joins.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 897296cd31 hostapd: pass a Beacon Report without a Measurement Report field to ubus
A Measurement Report element of a Beacon report can come without its
Measurement Report field: from a station that refuses the request or
cannot perform it, with the Refused or Incapable bit set (802.11-2024
9.4.2.20.1), and as the empty Beacon report of a station that has no
beacon information for the request, with no bit set (11.10.9.1.1). An
AX210 class station sends the latter for a Passive request on a channel
with no AP. hostapd_handle_beacon_report() passed only reports that
carry the field to hostapd_ubus_notify_beacon_report(), which dropped a
report without one, so such an answer never reached ubus.

Such a report now goes out on ubus with its address, tokens, interface
and rep-mode, and without the fields of the Measurement Report.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau a1270a5073 wpa_supplicant: move the APs of a radio when a station link switches
When the AP a station MLD is connected to switches the channel of one
link, wpa_supplicant receives EVENT_LINK_CH_SWITCH_STARTED rather than
EVENT_CH_SWITCH_STARTED. wpas_ucode_event() passed only the latter to
ucode, so hostapd was never told to move the APs on the radio of that
link. mac80211 then could not finalize the shared channel context
("some interfaces aren't switching") and dropped the station link.

Pass the link event with the same fields plus link_id, and handle it
like the interface event; iface_channel_switch() already maps the
frequency to the radio of an MLD.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau bf0b333927 hostapd: list the elements an AP MLD link profile does not carry
A per-STA profile in the Basic Multi-Link element of an (Re)Association
Response inherits each element of the frame that carries the Basic
Multi-Link element, unless a Non-Inheritance element lists it (IEEE
802.11be 35.3.3.5.1). ieee80211_ml_build_assoc_resp() adds no such
element. When the station associates on a 5 GHz link, the profile of a
2.4 GHz partner link omits VHT Capabilities and VHT Operation and so
inherits those of the 5 GHz link. mac80211 with IEEE80211_HW_STRICT then
rejects the association ("[link 0] VHT capabilities mismatch"), and a
driver without it sets up the 2.4 GHz link with VHT parameters the AP
does not use there.

List HT Capabilities, HT Operation, VHT Capabilities, VHT Operation and
HE 6 GHz Band Capabilities in a Non-Inheritance element where the
carrying frame has them and the link does not, as a Non-Inheritance
element names only elements of that frame (9.4.2.239). A Link
Reconfiguration Response carries none of them outside the Basic
Multi-Link element, so its profiles get no Non-Inheritance element.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau d9c461b73b hostapd: clamp the DPP chirp wait time to the driver limit
wpas_dpp_chirp_start() sends each Presence Announcement with a wait time
of 2000 ms. cfg80211 refuses a frame whose wait exceeds the driver's
maximum remain-on-channel duration with -EINVAL (nl80211_tx_mgmt()), so
on a driver with a smaller maximum, such as mac80211_hwsim with 1000 ms,
no chirp is ever transmitted and the enrollee cannot be found.

Clamp the wait time to max_remain_on_chan, as the other DPP transmit
paths in this file do.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 178994ca82 hostapd: keep the first link when a later AP MLD link fails
A later link of an AP MLD takes the driver instance of the first link
(hapd->drv_priv = h_hapd->drv_priv) in hostapd_driver_init() and joins
the MLD only after hostapd_drv_link_add() succeeds. When that call, or
the check for driver MLO support, fails, the link returns -1 with the
borrowed pointer still set.

The caller's cleanup, hostapd_deinit_driver(), then treats the instance
as its own: hostapd_cleanup_driver() removes the link with this BSS's
link ID from the shared instance, which is the link of another BSS when
the link IDs collide, or deinits the instance when it looks unshared.
The first link keeps a pointer to a torn down or freed instance, and the
next link that is set up calls get_capa() on it and crashes hostapd.

Seen with an AP MLD whose links span two wiphys: the link on the second
wiphy was assigned link ID 0, hostapd_drv_link_add() refused it, and the
cleanup removed link 0 of the first wiphy.

A link that never joined the MLD now drops the borrowed instance before
it returns, so the cleanup leaves the first link alone.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau d67accc71b hostapd: block a scan of an AP MLD link only while a partner link scans
hostapd_driver_scan() refuses a scan with -EBUSY while a partner link
of the same AP MLD is in HAPD_IFACE_ACS. A link stays in that state
while it waits to retry a refused scan. When two links start ACS at the
same time, the second one is refused, and from then on each link finds
the other in HAPD_IFACE_ACS on every retry. Neither link scans again,
and both give up after ACS_SCAN_RETRY_MAX_COUNT attempts.

The check exists because scan results without a matching scan cookie
go to the link that received the event, or to a partner link with a
scan callback. That needs at most one partner with a scan outstanding,
not at most one partner in ACS. Test for the scan callback instead of
the interface state. This also covers the HT 20/40 coexistence scan,
which sets the scan callback as well.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau c975fec986 hostapd: start DPP Network Introduction unless a DPP PMKSA is cached
wpas_dpp_check_connect() skipped Network Introduction whenever any
PMKSA for the AP existed, because wpa_sm_pmksa_exists() matches any
AKM. A backhaul STA that had joined with SAE while the AP offered no
DPP AKM kept that SAE PMKSA, and once the AP offered DPP again it
associated with the DPP AKM and no PMKID, which the AP rejected with
status 53. Require a PMKSA of the DPP AKM, under the AP MLD address as
well for an MLD, as patch 163 stores it.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau f40cea9640 hostapd: store the DPP Network Introduction PMKSA for the AP MLD too
wpa_supplicant adds the PMKSA it derives in Peer Discovery under the
address of the affiliated AP that answered, but an MLD association looks
it up by the AP MLD address. Whenever the two differ, the Association
Request carries the DPP AKM without a PMKID and the AP rejects it with
status 53. Add the entry under the AP MLD address as well, as the AP side
already does.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau a2f663371b hostapd: accept the Multi-AP backhaul netRoles in DPP Network Introduction
The Multi-AP specification (v6.1, Table 8) makes a mapBackhaulSta
Connector compatible with a mapBackhaulBss one, but
dpp_compatible_netrole() accepts only sta with ap, so a backhaul STA's
Peer Discovery ended with STATUS_NO_MATCH and the DPP AKM could not be
used on the backhaul.

Also report the features "dpp" and, from the patch itself, "dpp-map", so
that a Multi-AP Agent advertises the DPP AKM on a backhaul only where the
running wpad can complete the introduction.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 302dcebe89 hostapd: reset an MLD station when association follows a timed out SA Query
A non-AP MLD that lost its association without a Deauthentication comes
back with a new association. With PMF the AP answers status 30, runs an
SA Query, and allows the next association once the query times out.
The WPA state machine and the station entries of the earlier
association survive, and ieee80211_ml_process_link() then refuses the
partner links ("MLD: link: Station already exists", status 1 or 17), so
the station comes back with one link. IEEE Std 802.11be-2024 11.3.5.3 l)
has those states deleted.

Add a patch that deletes them in handle_assoc() once the SA Query has
timed out, before check_assoc_ies() creates a state machine for the link
that receives the association: it frees the state machine the way
handle_auth() does for a station without PMF, and then runs
ap_sta_re_add(). This covers Open System and SAE authentication on
drivers with full AP client state. Upstream hostap still refuses the
links as of 2026-09-22 (24c033de8775).

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 446d2384d8 hostapd: report received Action frames on ubus, and answer ANQP 272
Add the ubus notification "action-frame". It carries the body of a
received frame as hex, from the Category octet, for a subscriber that
forwards the frame rather than a reading of it. Raise it for a BSS
Transition Management Query, a WNM Notification Request and an ANQP
request for the Neighbor Report ANQP-element.

Answer the Neighbor Report ANQP-element (Info ID 272) from the neighbor
report database that the AP keeps for Radio Measurement, not only from
a configured anqp_elem, which still takes precedence. The payload is a
sequence of whole Neighbor Report elements. The element comes before
the elements of the extra requests, stops at 65535 octets, and carries
only the entries with the SSID of the BSS, as
hostapd_send_nei_report_resp() does. The Capability List advertises
Info ID 272 under the same condition.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau f7831b6ebd hostapd: name the interface of a Beacon Report on ubus
rrm_beacon_req takes its Dialog Token from the counter of the BSS whose
ubus object the call reached, and the links of an AP MLD share the one
object of their interface. The token alone therefore names the request a
report answers only per interface: two interfaces can hand out the same
token to requests for one station. The beacon-report notification now
carries "ifname", which is that interface for every link of an MLD.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau b920fc57c9 hostapd: pass the Measurement Token of a Beacon Report to ubus
The beacon-report notification carried the frame's Dialog Token under
"token" and dropped the Measurement Token of the Measurement Report
element, which hostapd_handle_beacon_report() skips before the report.
A subscriber that rebuilds the element put the Dialog Token in its
place. 802.11-2024 9.4.2.20 sets the Measurement Token to that of the
request's Measurement Request element, which hostapd sets to 1. The
notification now carries it as "measurement-token".

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 2c1ba806b4 hostapd: answer rrm_beacon_req with the Dialog Token of the request
A station answers a Beacon Request with a Radio Measurement Report that
carries the request's Dialog Token (802.11-2024 9.6.6.3), and the
beacon-report notification passes that token on. The caller of
rrm_beacon_req never learnt the token hostapd chose, so it could not
tell a report for its request from one for an earlier request to the
same station. rrm_beacon_req now replies with dialog_token.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 8381b0a352 hostapd: fix two -Wall warnings of the ubus code
__hostapd_bss_mgmt_enable_f() ended its switch without a return, so a
flag outside the enum returned an undefined value (-Wreturn-type). The
callers pass only the flags of the enum, which all return, so it now
returns false after the switch: such a flag changes nothing.

hostapd_notify_ubus() has no caller (-Wunused-function), and is
removed.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 9e0fb47cbe wifi-scripts, hostapd: handle a netdev that does not come up
Bringing a netdev up can fail, for example with ENOTUNIQ from
ieee80211_do_open() when another running interface of the wiphy has the
same address. wdev_set_up() ignored the result, and wpa_supplicant's
iface_start() and mld_add(), hostapd's bss_check_mld() and wdev.uc went
on as if the netdev ran. wpa_supplicant and wdev.uc also went on after a
failed netdev creation.

Return the error from wdev_set_up(). On a failure to create the netdev
or to bring it up, stop there; after a failure to bring it up, also
remove the netdev. wpa_supplicant leaves the interface not running for
the next start_pending() or mld_start(), hostapd skips the links of the
AP MLD, and wdev.uc drops it from its state for the next setup.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau b268edea7f hostapd: start a configuration again whose restart failed
iface_reload_config() returned true when the new BSS configuration
equalled the old one, before it checked that the interface existed.
After a setup whose netdev creation or hostapd.add_iface() failed, the
stored configuration has no interface. The next identical configuration
compared equal, and hostapd never started it, also after the cause went
away.

Take an equal configuration as up to date only if its interface runs or
its setup is pending.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 71611b11af hostapd: fix the shutdown handlers of the scripts
The global object of hostapd and wpa_supplicant has no ubus property,
so the shutdown handlers raised a reference error on the disconnect
call. The scripts store their connection in data.ubus.

wpa_supplicant's shutdown() called set_config() with two of its six
arguments. Pass the stored phy parameters. Also release its udebug
ubus binding before the disconnect, as hostapd does.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 8b4c110555 hostapd: ubus: count a reference only for a BSS object that was added
hostapd_ubus_add_bss() counted a reference to the ubus context whether
ubus_add_object() succeeded or not, and hostapd_ubus_free_bss() drops
one only for an object with an id. Every BSS whose object could not be
added, such as each further link of an AP MLD, which all ask for the
same name, kept the context referenced after it was freed. Count the
reference only where the object was added.

The context can now go away before such a BSS is freed, and
hostapd_ubus_free_bss() returned without a context before it freed the
name of the object. Free the name in every case.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 327f715b5b hostapd: keep a start_disabled BSS quiet when the interface comes back
On a radio shared with a STA, wifi-scripts generates every AP BSS with
start_disabled=1. The beacons start only when apsta_state goes up.

If the driver disables and enables the interface before that,
hostapd_iface_enable() starts the beacons of every started BSS, on a
channel that the STA possibly does not use.
053-AP-keep-a-start_disabled-BSS-quiet-on-interface-enable skips the
beacon update for a BSS with start_disabled set.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:37 +02:00
Felix Fietkau 22c42571ac hostapd: refresh patches
Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-08 09:50:36 +02:00
Karsten Sperling 8a6ae95526 hostapd: mbedtls EAP-TLS peer 3.6.3+ compatibility
Mbed TLS 3.6.3 (and 2.28.10) introduced the requirement to set or
explicitly clear the hostname when verifying a server certificate,
otherwise verification fails with
MBEDTLS_ERR_SSL_CERTIFICATE_VERIFICATION_WITHOUT_HOSTNAME. Clearing the
hostname allows EAP-TLS to work correctly in the peer / supplicant role
on those Mbed TLS versions.

https://mbed-tls.readthedocs.io/en/latest/kb/attacks/ssl_set_hostname/

Signed-off-by: Karsten Sperling <ksperling@apple.com>
Link: https://github.com/openwrt/openwrt/pull/25552
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-10-04 17:52:54 +02:00
Karsten Sperling 9e21f779cb hostapd: fix inverted test in tls_mbedtls_set_ciphers
tls_mbedtls_set_ciphers() contains logic intended to handle cipher name
mapping cases where the Mbed TLS name carries a trailing "-SHA256" that
the OpenSSL name omits, e.g. the OpenSSL name
"ECDHE-RSA-CHACHA20-POLY1305" corresponds to the Mbed TLS name
"TLS-ECDHE-RSA-WITH-CHACHA20-POLY1305-SHA256". The os_memcmp()
expression for this suffix check was missing a "== 0", i.e. the logic
was inverted.

This means intended cases like "ECDHE-ECDSA-CHACHA20-POLY1305" were
incorrectly rejected (an unmatched name makes tls_mbedtls_set_ciphers()
fail), while accepting bogus mappings like "ECDHE-RSA-AES256-GCM" ->
"ECDHE-RSA-AES256-GCM-SHA384" or "ECDHE-ECDSA-AES" ->
"ECDHE-ECDSA-AES256-CCM". (Fortunately no valid OpenSSL suite names were
accepted with a bogus mapping.)

Signed-off-by: Karsten Sperling <ksperling@apple.com>
Link: https://github.com/openwrt/openwrt/pull/25552
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-10-04 17:52:54 +02:00
Karsten Sperling 218dae245a hostapd: mbedtls check_cert_subject uninitialized read
tls_mbedtls_match_dn_field() populates one struct mlistoid per component
of the check_cert_subject string, but never initializes the prefix flag,
which is only assigned when the value ends in '*'. Whether a component
without a wildcard was matched as a prefix therefore depended on
leftover stack contents, e.g. check_cert_subject="O=w1.f" could be
accepted for an Organization of "w1.fi".

Found by ap_wpa2_eap_ttls_pap_check_cert_subject_neg, which happens to
check "C=FI1*" immediately before "O=w1.f".

Signed-off-by: Karsten Sperling <ksperling@apple.com>
Link: https://github.com/openwrt/openwrt/pull/25552
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-10-04 17:52:54 +02:00
Felix Fietkau c909828ca3 hostapd: ubus: count a reference only for a BSS object that was added
hostapd_ubus_add_bss() counted a reference to the ubus context whether
ubus_add_object() succeeded or not, and hostapd_ubus_free_bss() drops
one only for an object with an id. Every BSS whose object could not be
added, such as each further link of an AP MLD, which all ask for the
same name, kept the context referenced after it was freed. Count the
reference only where the object was added.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-01 22:29:55 +02:00
Felix Fietkau 909be7c9fa hostapd: build the DPP frame link choice without IEEE 802.11be
hostapd_dpp_freq_bss() reads hapd->conf->mld_ap, which exists only
with CONFIG_IEEE80211BE, so ucode.c did not build without it. Without
it there is no AP MLD, and the BSS the caller reached answers.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-10-01 22:29:55 +02:00
Felix Fietkau b833ad3511 hostapd: initialise the result of bss.rename()
bss.rename(ifname, true) skips the driver rename, and ret was then
read without a value. hostapd.uc passes no second argument today, so
no caller reaches this.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 9a81bf528d hostapd: move the control socket with a renamed BSS
With CONFIG_IEEE80211BE, hostapd names the control socket of a BSS
after hapd->ctrl_sock_iface, not after conf->iface. bss.rename()
changed conf->iface and opened the socket again, but kept the old
ctrl_sock_iface. The renamed BSS therefore kept the socket of its old
name.

A reload that renames phy0-ap0 to phy0-ap2 and then adds a new
phy0-ap0 failed for this reason. The new BSS found
/var/run/hostapd/phy0-ap0 bound by the renamed BSS ("ctrl_iface exists
and seems to be in use"), add_bss failed, and hostapd.uc restarted
the whole phy. Measured on slot-1-agent2, 2026-09-28 13:56:19 (phy0)
and 13:56:21 (phy1): each restart took all BSSes of the phy down for
about 2 s.

Set ctrl_sock_iface from the new name before the socket is opened
again, as start_ctrl_iface_bss() does for a new BSS.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 87623c1e67 hostapd: give the radio 0 link of an MLD the MLD address
bss_macaddr_next() gives the address of an MLD to its link on radio 0, and
the address generation relies on that. Two paths keep an old link address
instead, even after the MLD address changed:

- Step 2 of iface_reload_config() keeps the old BSSID of the first BSS when
  the new one is a default address, because the first BSS cannot change it
  in place.
- The keep loop in iface_restart() gives a surviving BSS with a default
  address its previous BSSID again.

The link then keeps its old address, and the MLD address is on no link.

In one case, a config change gave ap-mld0 a new MLD address and reloaded it
in place. Link 0 kept the previous MLD address as its link address, so the
new MLD address was on no link. From then on, about 30 % of the unicast
frames to the associated backhaul station were lost (68 of 100 echo requests
arrived), while the AP counted no failed transmission. A new association did
not clear it; a full wifi down/up did.

Move the decision of bss_macaddr_next() into bss_mld_addr(). The keep loop
of iface_restart() skips a link whose previous address is not the MLD
address, so the link gets the MLD address. Step 2 takes the restart path in
the same case. A link that already has the MLD address stays in place, and
plain BSSes, links on other radios and static addresses keep the old
behaviour.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 4c6992b13b hostapd: keep MLD interfaces and their BSSes across config changes
A config change below MLD level destroyed and recreated MLD netdevs and BSSes
that could have been kept. This restarted the PHY and dropped all connected
stations. Three separate issues caused this:

1. mld_find_matching_config() compared the whole vif config, so a change to an
   SSID created a new MLD. mld_set_config() then reloaded every interface
   without it, bss_check_mld() found no registry entry, iface_check_mld()
   spliced all MLD BSSes out of the config, and the interface was removed when
   no BSS was left. Replace it with mld_config_matches(), which compares only
   the fields that define the netdev: name, wiphy, radio mask and address.
   All other fields are BSS level and reach hostapd through the per PHY config
   file. The radio mask is compared instead of the radio list, so a new order
   of the device list keeps the MLD. mld_rename_bss() has no callers left and
   is removed.

2. bss_find_existing() accepted a hash match between MLD BSSes with different
   names, which happens when the BSS set shifts and BSSes share the same
   settings. Step 4 of iface_reload_config() then renamed one MLD netdev onto
   the name of another one, which fails with EEXIST. An MLD BSS is identified
   by its netdev name, so skip old MLD BSSes with a different name. The search
   continues, and an old BSS with the correct name is kept.

3. Step 2 preserved and renamed the first BSS even when the first BSS changes
   between plain BSS and MLD link. This cannot work, because
   hostapd_bss_setup_multi_link() only runs when a BSS is allocated. A rename
   of an MLD link also renames the netdev that the other radios share. Delete
   and recreate the first BSS instead. If no other BSS is kept, keep the old
   first BSS until the first new BSS exists, so that the PHY always has a BSS.
   Its address stays reserved until the delete. Delete it right after that,
   because its netdev and /var/run/hostapd/<name> keep its name, and a later
   new BSS can use that name. If another BSS is kept, delete the old first BSS
   in step 3 as before, since a kept BSS can be renamed onto its name.
   bss_remove() holds the delete sequence for both paths.

Tested on an AP MLD with 7 BSSes, going from 1 to 7 configured BSSes. The
messages "Skip MLD interface", "Remove interface", "Restart interface for phy"
and "Renaming device ... failed: Object exists" no longer appear, and a
4-address backhaul station stays associated across the change.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau c071e440ba hostapd: increase the netlink buffer of the dynamic VLAN socket
The dynamic VLAN code opens its own RTMGRP_LINK socket and takes the default
buffer, where netlink_init() sets one since 070-netlink-increase-buffer-size.
A reconfiguration that creates and destroys several interfaces bursts more
link events than the default holds, and hostapd answered "VLAN:
vlan_event_receive: recvfrom failed: No buffer space available" and lost the
events it needed.

Measured on a seven BSS AP MLD: a bring-up that restarts both PHYs produced
eighteen of those lines before and none after.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 400c79e6b6 hostapd: let a ubus subscriber answer BTM queries, and extend beacon_req
bss_transition_query_answer lets the subscriber that answers a BSS
Transition Management Query itself, stop hostapd from sending a BTM
Request of its own. The flag is cleared when the object loses its
last subscriber.

rrm_beacon_req takes channel_reports, written as AP Channel Report
subelements, and element_list, written as a Request subelement where
Reporting Detail is 1. A station rejects a request on channel 255 that
carries no channel report (802.11-2024 11.10.9.1.1). Values that do not
fit an octet, and a request longer than the Measurement Request element
can carry, are refused.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau aa99fb89a0 hostapd: add a ubus method to set MBO Association Disallowed
hostapd holds the MBO Association Disallowed attribute in its own
MBO-OCE element and sets it through the control interface command
"SET mbo_assoc_disallow", which no ubus method reaches. A daemon that
wants to advertise it has to add a second MBO-OCE element as a vendor
element, and wpa_supplicant reads the attribute only from the first
MBO-OCE element of a frame.

Add mbo_assoc_disallow { iface, reason } to the hostapd object. It sets
the reason on every BSS of that name, which covers each link of an AP
MLD; a reason of 0 allows association again.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 24723ba310 hostapd: name the DPP chirp listen frequency
The ubus field was called scan_interval, which names a duration.
wpa_supplicant reads it as "DPP_CHIRP listen=", a frequency in MHz. A
caller that passed a duration made the driver reject the
remain-on-channel request, and the enrollee listened on nothing between
chirp rounds.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 27b8aa25f8 hostapd: answer a DPP frame on the link that holds the channel
The ucode DPP API sends from the BSS the caller names. An AP MLD holds
one BSS per link, each with its own address and channel, and the BSS
that receives a frame is not always the one that holds the channel the
frame arrived on. The answer then leaves under the address of one link
on the channel of another, and the peer does not acknowledge it.

Send from the link whose channel the caller names. The GAS hook named no
channel at all, so pass the one the request arrived on.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau 79444dcc68 hostapd: let a preserved BSS keep the address it already has
iface_config_macaddr_list() marks every address of the running configuration
of a PHY, and every MLD address, with -1. The branch that carries a preserved
BSS's address into the new configuration read that as taken and was therefore
never reached, so the address stayed unclaimed in macaddr_list while the BSS
went on using it. The generator of step 6 then handed the same address to a
newly added BSS:

    Duplicate BSSID b6:ad:9f:70:fb:4a on interface 'ap-mld6' and 'ap-mld0'.
    Failed to add new bss ap-mld0 on phy phy0.0
    Restart interface for phy phy0.0

hostapd refused its own configuration and restarted the PHY under the stations
it served. A four address backhaul station survived that restart as associated
and stopped carrying data, which took a Multi-AP agent off the mesh until its
daemon was restarted.

The address is asked about by the BSS that already holds it, so a reservation
over it is its own. Measured on a seven BSS AP MLD: the reconfiguration that
produced the three lines above now produces none of them, and the backhaul
station keeps its association across it.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Felix Fietkau fcd9dbf534 hostapd: let the ucode DPP hook send a GAS comeback request
A GAS responder whose answer exceeds gas_frag_limit answers the initial
request with a comeback delay and holds the payload for GAS Comeback
Requests. hostapd does that for a DPP configuration response of any size
above 1400 octets, which a configuration object for several BSSs
exceeds, and an enrollee driven through this hook could not ask for it:
tx_gas_req builds an initial request around a payload, and tx_action
rebuilds whatever it is given as a DPP message, so neither carries the
plain public action frame a comeback needs.

dpp_send_gas_comeback_req exposes gas_build_comeback_req the same way
dpp_send_gas_req exposes its counterpart, and the channel takes it as
tx_gas_comeback_req.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-29 17:29:25 +02:00
Hauke Mehrtens 18a969c475 hostapd: restore Link Measurement Report handling
The ubus notification for link measurements was added by replacing the
call to hostapd_handle_link_mesr_report() instead of adding to it, so
upstream's handler lost its only caller and is compiled out as dead
code, which the build shows as:

  src/ap/rrm.c:354:13: warning: 'hostapd_handle_link_mesr_report'
  defined but not used [-Wunused-function]

As a result nothing clears hapd->link_mesr_req_active or cancels
hostapd_link_mesr_rep_timeout_handler() when a report arrives, so a
REQ_LINK_MEASUREMENT issued over the control interface always ends in a
timeout even though the report was received, and the LINK-MSR-RESP-RX
event carrying the report is never emitted.

Call both handlers, which is how the ubus hook for beacon reports a few
lines above is wired up too.

Fixes: f6445cfa1a ("hostapd: add ubus link-measurements notifications")
Assisted-by: Claude:claude-opus-5
Link: https://github.com/openwrt/openwrt/pull/25242
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-09-24 21:20:58 +02:00
Hauke Mehrtens 73e10e7432 hostapd: free the APuP peer when no AID is available
apup_process_beacon() adds the peer with ap_sta_add() and only asks for
an AID with hostapd_get_aid() further down. When that fails the function
returns without undoing anything, leaving the station in hapd->sta_list
with WLAN_STA_AUTH set but with no AID, no WLAN_STA_ASSOC and no WDS
interface. The driver entry added by the preceding hostapd_sta_add() and
the hapd->num_sta reference are leaked as well.

The leftover entry is then found by the ap_get_sta() check at the top of
the function, so every later beacon from that BSSID returns early and
the peer can never be set up again until hostapd is restarted.

Release the half-initialised station with ap_free_sta(), which removes
the driver entry, cancels the inactivity timer registered by
ap_sta_add(), unlinks it from the list and drops the counters.

Fixes: e80520197c ("hostapd: Add support for APuP")
Assisted-by: Claude:claude-opus-5
Link: https://github.com/openwrt/openwrt/pull/25242
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-09-24 21:20:58 +02:00
Hauke Mehrtens d6c5598e2f hostapd: fix NULL pointer dereference in APuP beacon handling
apup_process_beacon() dereferenced the return value of ap_sta_add()
without checking it. ap_sta_add() returns NULL when the station table is
full, when the allocation fails or when accounting_sta_get_id() fails,
so the following read of sta_ret->aid dereferences NULL.

The station table filling up is remotely triggerable. Beacons reach
handle_beacon() on every softmac driver, because hostapd subscribes to
them per wiphy with NL80211_CMD_REGISTER_BEACONS when it starts the AP.
An adjacent station can therefore beacon the target's SSID from
successive spoofed BSSIDs, each of which is added as an APuP peer, until
max_num_sta is reached and hostapd crashes.

CONFIG_APUP=y is set unconditionally in DRIVER_MAKEOPTS, so the code is
compiled into every wpad variant, but the call is gated on
hapd->conf->apup at runtime and wifi-scripts defaults apup to 0. Only
configurations that opt into APuP are affected.

Reported-by: JuliusBairaktaris
Fixes: e80520197c ("hostapd: Add support for APuP")
Assisted-by: Claude:claude-opus-5
Link: https://github.com/openwrt/openwrt/pull/25242
Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
2026-09-24 21:20:50 +02:00
John Crispin b029d56e0b hostapd: build the variants in parallel
The variants only write to their own build directories. The packages
without a variant are only built by the first variant.

With all packages selected, a clean build of the 31 variants takes 46 s
instead of 114 s, and the packages do not change. Together with the
commit that lists every build variant only once, a build that has
nothing to do takes 17 s instead of 33 s, as the variants were the
longest chain of it.

Signed-off-by: John Crispin <john@phrozen.org>
2026-09-22 18:18:58 +02:00
Felix Fietkau ce22f3ba6c hostapd: address a station on the link it associated over
Every link of an AP MLD can hold an entry for a station while only the
association link says where it is listening. Taking the first link that
has an entry sent the frame out on a link the station is not on, so a
BTM Request addressed to a station on the 5 GHz link went out on 2.4 GHz
and the station never saw it.

Prefer the link whose entry names it as the association link, and keep
any other entry only as a fallback for a station no link claims.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-02 09:55:51 +02:00
Felix Fietkau ca73f5be28 hostapd: stop building the MBO transition IE from uninitialised values
hostapd_bss_transition_request() left mbo_reason, cell_pref and
reassoc_delay uninitialised and assigned them only when the caller named
them, while hostapd_bss_tr_send() built the MBO attributes from all
three unconditionally.

A caller that names none of them, which is every caller that only wants
a plain BTM Request, therefore had whatever the stack held validated and
then written into the frame. Where the leftovers failed validation the
request was refused with an invalid argument and no BTM Request frame
was transmitted at all; where they passed, the frame carried a random
transition reason and cellular preference. Which of the two happened
varied between calls, so client steering failed intermittently with no
diagnostic.

Initialise the three, and build the attributes only when the caller
asked for MBO. Name the cellular preference constants while touching
the validation.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-02 09:55:51 +02:00
Felix Fietkau c2b36db684 hostapd: find a station on any link of an AP MLD in ubus methods
A station associates over one link of an AP MLD and its sta_info lives
on that link's BSS. A ubus method resolves its hostapd_data from the
object it arrived on, and only the BSS that registered the shared
interface name owns an object, so a method addressed to a station on any
other link looked it up on the wrong BSS.

bss_transition_request returned NOT_FOUND for those stations, so no BTM
Request frame was ever transmitted: hostapd's bss_transition_request_tx
counter stayed put across a whole run of client steering mandates.
del_client likewise failed to deauthenticate them, which left
association control unable to block a client of an AP MLD.

Walk the affiliated links, as hostapd_get_sta_ies_sta() already does for
the association frames, and act on the BSS that holds the station.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-02 09:55:51 +02:00
Felix Fietkau 7277008a59 hostapd: send every ubus notification of an AP MLD to the subscribed object
The BSSes of an AP MLD share one interface name, so only the first BSS
registers hostapd.<iface> and holds the subscribers. ubus_add_object()
fails for every other link and its return value is discarded, leaving
those links with an object that never has a subscriber.

hostapd_ubus_notify_obj() already resolves this, but only the generic
hostapd_ubus_notify() used it. Every other emitter still tested and
notified hapd->ubus.obj directly, so an event raised on a link other
than the first was dropped without a trace.

sta-authorized is the one that hurts: a station that associates over any
link but the first is never announced. umap-rrmd builds its station
table from that notification, so it never learns the station, and client
steering fails with "rrmd accepted 0 of 1 mandate STAs" for every client
of an AP MLD. bss-transition-response, beacon-report and the association
decision in hostapd_ubus_handle_event() are lost the same way.

Route them all through hostapd_ubus_notify_obj(). A BSS that is not an
MLD link, or one whose own object has subscribers, is unaffected.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-09-02 09:55:51 +02:00
Felix Fietkau 54f7b240b4 hostapd: fix authentication with non-AP MLDs that change link addresses
A non-AP MLD can pick new affiliated STA addresses for every attempt.
Replies addressed to the MLD MAC address then rely on a stale driver
mapping and never reach the peer.

Add three patches that address Authentication replies to the peer link
address and find MLD stations by link address in the TX status handler.
Refresh the offsets of the following patches.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-08-21 18:09:46 +02:00
Felix Fietkau e414d5a01e wpa_supplicant: reserve the configured addresses of the PHY
phy_dev_open() builds the address pool from the list of hostapd and from a
scan of the wdevs. A configured address appears in neither. macaddr_next()
never returns it, so nothing marks it, and the wdev of its interface exists
only while the interface runs.

start_pending() scans the wdevs once and then starts every interface of the
radio. An interface without a configured address could therefore take the
address of an interface that the same loop configures.

Add the configured addresses of the PHY to the pool. hostapd reserves its
own BSSIDs the same way, in iface_config_macaddr_list().

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-08-21 18:09:29 +02:00
Felix Fietkau 44519a8d4f hostapd: publish the addresses of the whole PHY to the supplicant
The supplicant builds its address pool in phy_dev_open() from the list that
hostapd publishes and from a scan of the wdevs. hostapd published the
addresses of the MLDs and the BSSIDs of one radio.

A link of an AP MLD has no wdev of its own, and the scan of the supplicant
drops a wdev of another radio. The link addresses of the other radios of the
PHY reached the supplicant through neither source. A STA interface could
take one of them, because the sequences of two radios of a PHY overlap.

Publish the list that phy_macaddr_list() builds. hostapd reserves the same
addresses for its own radios, and the supplicant needs them for the same
reason. Both callers run after hostapd assigned the BSSIDs of the
configuration, so the list is complete.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2026-08-21 18:09:29 +02:00