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>
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>
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>
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>
IEEE 802.11-2024 12.12.2 says "An AP shall require management frame
protection (MFPR = 1) when using RSN" in the 6 GHz band, and
hostapd_config_check_bss_6g() refuses a BSS with "Management frame
protection is required in 6 GHz".
sae-compat used set_default(), so an explicit ieee80211w 0 or 1 won. In
an AP MLD one configuration serves every link, so an ieee80211w value
that suits the 2.4 and 5 GHz links stopped the 6 GHz one.
In the 6 GHz band, set ieee80211w to 2 for every mode with WPA2 or later,
also over an explicit value, which cannot work there.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
On 6 GHz, generate() replaced the psk and psk-sae auth types with sae,
and eap and eap-eap2 with eap2, after parse_encryption() had run. A
replacement that must also change wpa or the pairwise cipher cannot work
that way, because parse_encryption() derives both from the encryption
string. For psk, psk-mixed, wpa and wpa-mixed, wpa kept the value 1 or
3, and hostapd_config_check_bss_6g() refuses such a BSS with "Pre-RSNA
security methods are not allowed in 6 GHz".
Replace the encryption mode before parse_encryption() runs, and keep any
cipher suffix:
- sae-mixed, psk3-mixed, psk, psk2 and psk-mixed become sae.
- wpa3-mixed, wpa, wpa2 and wpa-mixed become wpa3. WPA3 Specification
v3.5 11.2 item c forbids the 802.1X SHA-1 AKM in the 6 GHz band.
Each mode gets the same auth type as before. psk, psk-mixed, wpa and
wpa-mixed now also get wpa 2.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd_config_check_bss() refuses a BSS with "MBO: PMF needs to be
enabled whenever using WPA2 with MBO", and one with "OCV: PMF needs to be
enabled whenever using OCV". A refused BSS stops the whole phy.
Outside 6 GHz, several modes leave ieee80211w at 0 unless it is set:
psk2, psk-mixed, wpa2, wpa-mixed, and sae-compat, which sets 0 for its
base RSNE. With "option mbo 1" or "option ocv 1", each of them failed.
On a BSS with WPA2 or later, set ieee80211w to 1 (optional) when mbo or
ocv is set and ieee80211w is 0 or not set. An explicit 0 cannot work
with these options, so it is raised as well, as the dpp option does.
hostapd enables ieee80211w itself when it reads "ocv", but only if no
ieee80211w line came before it, and wifi-scripts writes "ieee80211w=0"
after "ocv". Write ocv beside ieee80211w in iface_mfp() instead. On a
BSS without WPA2 there is no PMF, so ocv is no longer written there.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
In WPA3-Personal Compatibility Mode outside 6 GHz, the base RSNE must
look like WPA2-Personal to legacy stations. wpa_key_mgmt() added
WPA-PSK-SHA256 to that RSNE whenever ieee80211w was set, which the dpp
option already does, and which an explicit ieee80211w does too.
hostapd's own example for this mode in hostapd/hostapd.conf ("Example
configuration for WPA2-Personal/PMF-optional in RSNE and
WPA3-Personal/PMF-required/MLO in override elements") uses ieee80211w=1
with wpa_key_mgmt=WPA-PSK alone. Commit 86b9eec8f0, which added the
mode, cites the WPA3 Deployment Guide for a base RSNE with WPA-PSK only.
Advertise WPA-PSK, and FT-PSK with ieee80211r, in that RSNE whatever the
PMF setting is.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A new station MLD took over the address of a removed one only where
the names were the same. netifd numbers the MLO interfaces in the order
of their sections, so a new section renames a station MLD (sta-mld0 to
sta-mld1), and the station MLD got a new address. Other devices then
take it for a new station MLD, although it continues the existing one.
mld_set_config() now uses the rule of the AP MLD (mld_prev_match()):
on one wiphy, the same SSID and radios, then the same SSID. For a
continued station MLD with another name, the MLD sync names the address
of the old one; the entry of the old name loses it, and the new name
claims it. A station MLD that continues none asks by its name, as
before.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
netifd numbers the MLO interfaces in the order of their sections
(mlo_vif_create()), so the name of an AP MLD is no identity: a new
section that sorts first renames the MLDs after it (ap-mld0 to ap-mld1).
hostapd took the renamed MLD for a new one and gave it a new address.
Its links got new addresses too, although the configuration of the MLD
did not change.
- mld_prev_match() in common.uc decides which removed MLD a new MLD
continues. On one wiphy, two passes in order: the same SSID and
radios, then the same SSID. The first pass separates two MLDs with one
SSID. A new MLD that matches none asks netifd by its name, as before:
it gets the address that its name had, unless another MLD continues
that one, and else a new address, as a new BSS does.
- The MLD sync names the address of a continued MLD with another name
for the new name. The entry of the old name loses the address to it,
so a new MLD with the old name finds no entry.
- The links of a removed MLD release their entries on every radio that
hostapd has a configuration for (macaddr_release). The reload of
mld_set_config() reaches only the links that run, so a link whose BSS
failed or was pending kept its entry, which shares the address of the
old MLD.
- The entry of a link of a continued MLD moves to the link of the new
MLD on that radio (macaddr_release with to), where that name has no
live entry. A stale entry on the target name is a leftover of an
earlier removal, unless the target is the old name of another MLD of
the same change; hostapd lets the move replace it only in the first
case (replace). Without that, the leftover kept the target name: the
continued MLD got the old link address of that removed MLD, and an
unrelated new MLD took the link address of the continued one.
In a chain of renames, the MLD whose new name is free moves
first and frees its old name for the next one. A cycle (two MLDs that
swap their names) has no free name: the entries stay stale with their
old names, and the links of the new MLDs claim them by name. A link
that carries the MLD address follows the MLD; each other link keeps
the address that its name had, not the one of the link it continues.
Each move happens within one netifd. The sync and the releases go out
before the requests of the new MLDs, and netifd takes the requests of
one connection in order.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
399 looks up the AP station by the AP MLD address, 400 matches a channel
switch action against the link that received it, and 401 reports the
BSSID of each link in debugfs. The vbed image of 2026-10-02 builds and
runs with them.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd, wpa_supplicant and wdev.uc now request the address of each
interface from netifd (network.wireless macaddr_get) and tell it which
names they keep (macaddr_sync). The used lists that they built from
their own configuration, from the lists of each other and from the
netdevs are gone, and with them the ubus methods that passed the lists:
config_get_macaddr_list of hostapd and wpa_supplicant,
wpas_set_macaddr_list of hostapd and phy_set_macaddr_list of
wpa_supplicant. The allocator of common.uc and the get_macaddr command
of wdev.uc are gone too.
Names:
- hostapd: <phy>/<ifname> per BSS, in the group of the PHY
configuration, and the AP MLD name in the group "mld". A link of an
AP MLD shares the address of the MLD where the configuration names
it as its BSSID; a link on radio 0 also follows a generated MLD
address.
- wpa_supplicant: the ifname, in the group of the PHY configuration,
and the station MLD name in the group "mld". A station MLD keeps its
address when its radios change.
- wdev.uc: the ifname, in the group of the radio.
A name keeps its address, so a reconfigured station, mesh or IBSS
interface keeps the address that its peers know. An empty configuration
of a radio releases the entries of its owner for that radio: in
hostapd (iface_set_config(), also when iface_check_mld() takes the last
BSS), in wpa_supplicant (set_config()) and in wdev.uc. An expired link
of an AP MLD (mld_link_bss_remove()) releases its entry too. netifd
keeps the released entries as stale entries, so the interfaces that
come back get their addresses by name, and a configured address takes
them.
A reload of hostapd moves the address of a reused BSS to its new name
in one macaddr_sync, so that a rename or a swap of names keeps each
address with its BSS. A reused first BSS of a fullmac device whose kind
of address changed needs a restart, since its netdev keeps its address.
A configured BSSID that another interface holds is refused, and the
interface does not start.
If netifd cannot be reached, each consumer uses the address that the
name had before, where it knows one, and a hostapd reload keeps the
addresses of the reused BSSes.
The configuration files carry no placeholder BSSID for an interface
without a configured address: hostapd replaced it and wpa_supplicant
and wdev.uc dropped it. hostapd keeps the BSSIDs of an unchanged BSS
list by the name of each BSS instead.
The allocation table lives in netifd only, so a new netifd has no
record of the addresses that the previous one gave. /etc/init.d/network
restart runs wifi down before netifd stops, but a crash of netifd tears
nothing down: the new netifd found its radios running, none of the
consumers asked for an address, and a new interface could get one of
the running addresses. After wifi down too, hostapd kept the data of an
AP MLD whose configuration did not change, and the netdev of that MLD
took its address from that data. wireless.uc therefore resets the
radios when netifd loads it, before config_init() sends mld_set:
- hostapd config_reset,
- wpa_supplicant config_reset, a new method: an empty configuration for
each radio, and an empty MLD configuration, as hostapd does,
- wdev.uc reset, a new command, for each /var/run/wdev-<phy>.json. It
removes the interfaces that the state file names and that this radio
created (drop_inactive()), and writes empty state and id files. It
opens no ubus connection, as network.wireless does not exist yet at
this point.
netifd waits for each of these. The two calls use a connection of their
own with a timeout of 3 s, not the global connection of netifd with
30 s, so a hostapd or wpa_supplicant that does not answer holds netifd
start for at most 3 s each. A result other than "not found" goes to the
log. netifd loads wireless.uc once, in netifd_ucode_init() from main(),
so the reset runs only when netifd starts, not at a wifi reload, a
network reload or a reconf.
The cost: a crash of netifd that procd answers with a respawn, and
"ubus call network restart", now restart all wifi, as
/etc/init.d/network restart already did through wifi down. Outside the
ETSI region, cfg80211 sets an available DFS channel back to usable 2 s
after no interface operates on it (cfg80211_dfs_channels_update_work()),
so a radio on a DFS channel runs a new CAC, and the connection of a
backhaul station drops.
Other parts:
- wdev_create() refuses a netdev other than a monitor without an
address, and hostapd creates its first BSS with its BSSID: without an
address, mac80211 takes the first free entry of the wiphy address
list, which netifd can have given to another interface.
- A failed allocation in iface_restart() starts no BSS of the radio and
restarts wpa_supplicant there, as the other early returns do.
- On a fullmac wiphy, wdev_create() reuses a netdev that is down, and
nl80211_set_interface() does not read NL80211_ATTR_MAC. The reused
netdev now gets the requested address through RTM_SETLINK. A driver
without ndo_set_mac_address, such as ath6kl, keeps the old address,
with a warning.
- iface_reload_config() checks the fullmac property of the wiphy. It
passed the configuration name, such as phy1.0, which has no sysfs
entry, so phy_is_fullmac() checked wiphy 0.
- wpa_supplicant reports the generated addresses in its status method.
- wdev.uc takes num_global=<n> and macaddr_base=<addr> among its
arguments again.
- wpad_acl.json allows the calls to network.wireless, and the
postinst of hostapd-common makes ubusd read the ACLs again.
hostapd.uc and wpa_supplicant.uc import common.uc of wifi-scripts, so
both packages must be updated together.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd, wpa_supplicant and wdev.uc each allocate addresses with the
allocator of common.uc, and each sees the addresses of the others only
through lists that they pass to each other or through the netdevs that
exist at that moment. An address that one of them keeps for an
interface without a netdev is free for the others.
Add one allocation table to the wireless code of netifd, with four
methods on network.wireless:
- macaddr_get gives an address to a name of an owner (hostapd,
wpa_supplicant or wdev). A name keeps its address while the wiphy,
the radio and the generator options stay the same; with any_radio,
also when the radio or the options change, as a station MLD needs.
A configured (static) address that
another name holds is refused. share lets a name use the address of
another name of the same owner, as an AP MLD link does with the MLD
address.
- macaddr_sync sets the names of an owner and group in one call: names
that it does not list are released, and an address can move to
another name of the group (from), for a renamed or swapped BSS.
- macaddr_release releases one name.
- macaddr_list shows the table.
Addresses are unique per wiphy. Generation skips the addresses of the
interfaces in the UCI configuration, and the addresses of netdevs
without an entry. The generator is a copy of the one in common.uc, with
the per-radio ranges; it reads the wiphy once per request and stops at
the first free address.
An entry that shares the address of another name holds that address
for the shared name only: a request for that name does not count the
entry as a holder. Such an entry accounts for no netdev, so the check
of the live netdevs still runs.
A released entry, and each entry of an owner whose ubus object goes
away, stays as a stale entry for the life of netifd, so that the
interface or the process that comes back gets its address by name:
- A request, or a sync that keeps the name, claims the entry again.
- A released entry with a configured address goes instead. The address
comes back with the configuration.
- A new name gets a stale address only where the range of the request
has no other free address.
- A stale entry yields to a request that names its address: a
configured address, a shared one, or a value of a sync. A request
that takes the address removes the stale entries of other names with
it. In a sync, the entries of the same owner lose the address; a
stale entry of another owner keeps it, and its name finds a holder
when it asks again.
The table lives in netifd only. A restart of netifd through its init
script tears the radios down first, so the new table starts empty.
The configured addresses are lower-cased again in update_config(),
because hostapd and wpa_supplicant compare them with the addresses that
netifd returns.
Nothing uses the methods yet.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
wnm_send_bss_tm_req() passes buf + sizeof(buf) - pos as the space left
for the MBO element, with buf a pointer. On a 64-bit target sizeof(buf)
is 8 and pos is at least buf + 31, so the value converts to a size_t
near SIZE_MAX and the check in mbo_add_ie() never fails. The allocation,
os_zalloc(1000 + nei_rep_len + mbo_len), leaves room for at most 299
octets of fixed content and the 6 octet MBO element header, so no write
goes past it.
Upstream hostap has the same line on main, pending, main-check, pqc and
eppke as of today, and no commit changes it, so 072 is a local patch
that passes the space left in the allocation.
The two lines that 072 adds move the last hunk of 590 by two; refresh
it so that the series applies without offsets.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd_bss_tr_send() allocates nr for the neighbour reports and freed
it only where hexstr2bin() failed. wnm_send_bss_tm_req() copies the
buffer and does not take it over, so every request with neighbors leaked
it, and so did the returns of the MBO checks after the allocation.
The MBO checks and attributes need nothing from nr, so they now run
before the allocation, and nr is freed after wnm_send_bss_tm_req(),
whatever it returns. The one return after the allocation that remains,
a failure of hexstr2bin(), frees nr with os_free(), which matches
os_zalloc().
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd_bss_transition_request() read each value as a u32 and then
narrowed it without a check. reassoc_delay, mbo_reason and cell_pref were
u8 locals, and hostapd_bss_tr_send() took da_timer as u16 and
valid_period and dialog_token as u8. A value that did not fit lost its
high bits:
- an Association Retry Delay of 256 to 65535 went out as its low octet,
although the attribute holds two octets, and the check against 65535
could never be true;
- a Transition Reason Code of 256 became 0 and passed the check against
MBO_TRANSITION_REASON_PREMIUM_AP;
- the same applied to the Cellular Data Connection Preference, the
Validity Interval, the Dialog Token and the Disassociation Timer.
The request values now stay u32, and the handler answers
UBUS_STATUS_INVALID_ARGUMENT for a value larger than its field:
- Dialog Token and Validity Interval: one octet; Disassociation Timer:
two octets (IEEE Std 802.11-2024, 9.6.13.9, Figure 9-1275);
- Transition Reason Code and Cellular Data Connection Preference: one
octet (MBO v0.0_r19, 4.2.6 and 4.2.5, as ieee802_11_defs.h names
them); Association Retry Delay: two octets, as hostapd_bss_tr_send()
writes it. hostapd's MBO code names no section for that attribute.
hostapd_bss_tr_send() takes reassoc_delay as u16 and keeps only the
check that a delay needs Disassociation Imminent.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
These locals are never read under any configuration:
- hostapd_notify_response(): elems, pos, len;
- hostapd_bss_wps_status(): rc;
- hostapd_rrm_print_nr(): data;
- hostapd_bss_mgmt_enable(): cur, neigh, beacon;
- hostapd_rrm_beacon_req(): mode, which was assigned only; the frame
reads the Measurement Mode from the request attribute;
- hostapd_bss_transition_request(): sta;
- hostapd_wired_get_clients(): sta_driver_data, r;
- hostapd_ubus_notify_bss_transition_response(): i;
- hostapd_ubus_notify_bss_transition_query(): cl_str, i.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd_bss_del_client() sets reason only when the request carries
"reason". A request without it passed an uninitialised int to
hostapd_drv_sta_deauth() or hostapd_drv_sta_disassoc() and to
ap_sta_deauthenticate() or ap_sta_disassociate(). The station then got
whatever value the stack held as the Reason Code of the Deauthentication
or Disassociation frame.
Start reason at WLAN_REASON_UNSPECIFIED (1). Reason code 0 is reserved
(IEEE Std 802.11-2024, Table 9-79).
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A subscriber that reports a failed connection on an RSNA BSS needs the
RSN Error, which names the frame in which the RSN procedure failed, and
the body of the last frame that the station sent. The key-mismatch
event carried the station address and the interface name only, so a
subscriber could fill in neither field.
For a mismatching SAE Confirm in handle_auth_sae(), the event now also
carries:
- rsn_error: 1 (Authentication frame);
- frame: the Authentication frame body, from the Authentication
Algorithm Number field to the end of the frame, as lowercase hex.
The event name and the address and ifname fields do not change, so a
subscriber that reads only those keeps working. For a non-AP MLD the
address is the STA MLD address: handle_auth() looks up or adds the
station by the MLD address of the Basic Multi-Link element.
The key-mismatch event of hostapd_wpa_auth_psk_failure_report() (EAPOL
message 2) does not change.
The hex encoding of a blobmsg field moves into blobmsg_add_hex(), which
the action-frame event and the neighbour report output use too.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
The station removal event of an MLO station carried the counters of
the default link alone. It now carries the sum over the station's links
and its removed links, as the station dump does.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
mac80211 books all TX of an MLO station on the association link, so a
per-link station dump showed every TX byte, packet and the TX rate on
that link and none on the others.
- 417 exports the conversion of a TX rate to a rate info, with the
documentation of its arguments.
- 418 makes mac80211 ask the driver for the counters of a link before
it removes the link from the driver, and add them to the station's
totals of removed links.
- 419 lets hwsim count TX per link and report it through
link_sta_statistics. It counts the length of a frame before
encryption and one packet per MSDU, only where the frame resolves to
a station link. It starts the record of a link again when the link
station changes, and clears the records of removed links.
Measured on the virtual bed: each link reports its own TX counts and
rate, and the station totals are the sum of the links.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
sta_set_link_sinfo() took the band of a legacy TX rate from
ieee80211_get_sband(), which warns on an MLD interface and does not
return the band of the link. 416 converts the rate with the band of the
channel context of the link.
The last TX rate of an MLO station comes from the TX status of a frame
on any link, so a legacy index can lie beyond the bitrate table of the
band it is converted with. The conversion checks the index against
that table.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
415 fixes 727afb05ef63 of the backport above: a station dump stopped
carrying NL80211_ATTR_MLD_ADDR and NL80211_ATTR_MLO_LINK_ID, so a reader
of the dump no longer saw that a station is an MLD or which link it
associated on. NL80211_CMD_GET_STATION for one station
still carried both. Mainline has the same gap.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
001 to 005 are mainline 5c660b243435, 4b40378ac203, 727afb05ef63,
dd21d1844aa0 and f9202a374ec3. NL80211_ATTR_STA_DUMP_LINK_STATS then
asks a station dump for one message per link of an MLD station after
the aggregated one, so userspace can read the counters and rates of
each affiliated station. Without the flag the dump is as before.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
414 fixes cfg80211_sta_set_mld_sinfo(). It took the TX and RX rate of an
MLO station from the link with the least inactive time, but compared
against a value it had just set to that link's own, so only the first
link could decide. mac80211 records the last TX rate on the association
link, so a station associated on any other link reported no TX bit rate
at all. The TX and RX durations had the same condition and counted the
first link only.
Measured on a mac80211_hwsim bed: an MLO backhaul station associated on
link 1 now reports its TX rate, and the backhaul rates derived from it
read 270 and 3120 Mbit/s instead of 65535.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A subscriber that reports each affiliated STA of an associated client
MLD needs the STA's link address and the BSSID of the affiliated AP of
its setup link. hostapd's ubus named the MLD address only, and the
kernel's station dump names neither, so a subscriber reported the MLD
address as the affiliated STA, and could not map the link of a backhaul
STA to its AP link.
For a client MLD, get_clients and sta-authorized now carry
ap_mld_address, assoc_link_id and links [{ link_id, address, bssid }],
one entry per accepted link, read from the station of the association
link. Stations that are not an MLD get no new field.
A new sta-links-changed event carries the same fields where the links
change without a new association:
- 604: when a Link Reconfiguration Response that accepted an addition or
a removal is acknowledged;
- 605: when an affiliated AP leaves the AP MLD and its link stations are
freed.
197 frees a link station through one helper, ap_free_link_sta(), which
also clears the link on the partner stations and updates the state
machine's link information. It undoes the links a request added on every
path that refuses or does not complete it, where before the partner
stations kept them as set up. 604 depends on 197, and 605 builds on
both.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Commit da4be02fcd ("hostapd: fix a race condition on adding AP mode
wds sta interfaces") made netifd the only process that adds WDS station
interfaces to the bridge. Its hostapd patch 711 copies bridge into
wds_bridge when it parses bridge= and wds_bridge is still empty, and
hostapd.sh writes an empty wds_bridge= after bridge=.
ap.uc does not write the empty wds_bridge=, so hostapd adds each WDS
station interface to the bridge, and netifd adds it again. The commit
above names the effects of the race: incomplete bridge vlan settings,
or a client that hostapd refuses.
Write an empty wds_bridge= for a BSS with a bridge, as hostapd.sh does.
The line must follow bridge=, because patch 711 fills an empty
wds_bridge at parse time; append_vars() writes bridge first. A
wds_bridge that the config sets for a wds BSS still applies, as in
mac80211.sh.
The previous change makes netifd link WDS station interfaces that
appeared while it had no data for their vif.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
netifd links a WDS station interface <vif>.staN from its hotplug event,
through the handler data of the vif. wdev_config_init() clears the vif
entries of the handler data, and the handler sets them again with
set_vif(). The handlers of all radios run one at a time, so this gap
can be long. hostapd keeps an unchanged BSS through a reload, and a
station can associate in the gap. Its hotplug event then finds no vif,
and wdev_mark_up() links only the vif itself, so the WDS station
interface never joins the bridge. Today hostapd adds it to the bridge
for the ucode scripts; with hostapd.sh, which turns that off, the port
is already lost.
When a radio is marked up, also link the existing WDS station
interfaces of each vif and vlan, with the pattern that hotplug() uses.
This matches MLD names such as ap-mld1.sta1 and leaves out VLAN
subinterfaces such as wlan0.sta1.100. A link that exists already does
not change.
Keeping the old vif entries until set_vif() replaces them would also
close the gap, but the old entries can name a vif that the new config
removed or networks that it changed, and they do not cover interfaces
that exist before netifd has any data for the radio. Mark-up is the
point where the handler data is complete, so the links follow it there.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
The generated station file named no ctrl_interface; the socket path came
from add_iface alone. RECONFIGURE then read a configuration without it,
deinitialised the control interface and set up none, so the reload method
left /var/run/wpa_supplicant/<ifname> gone. The file now names the same
directory that add_iface passes, which keeps the socket, and the reload
method applies a station MLD's freq_list again.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A station whose configuration changed only in its credentials was removed
and added again, or with RECONFIGURE deauthenticated, although the new
settings could still allow the association in use. For example, a change
from PSK and SAE to SAE only, while the station is associated with SAE,
made the station associate a second time.
The iface method network_update() reads the regenerated file with
wpa_config_read() and, where its one network is the current one and the
association in use (SSID, AKM, protocol, ciphers, PMF, the effective
password and SAE PWE) still fits it, moves the credential fields into the
current network without the PMKSA flush that SET_NETWORK does. Otherwise
it refuses, and the caller removes and adds the interface as before.
wpa_supplicant.uc takes this path for a plain station and a station MLD
whose configuration differs only in the credential lines, and applies the
MLD's freq_list again after it.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Add the max_interfaces module parameter. A radio combines up to 2048
interfaces, and userspace that derives the number of BSSes of a radio
from the interface combinations reports that value. The default stays
2048.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
601-ucode_support.patch adds the first link of an AP MLD on the
existing netdev with no address, and hostapd_setup_bss() then takes the
netdev address, the MLD address, as the link address. A link on a
radio other than radio 0, whose configured BSSID hostapd.uc keeps apart
from the MLD address (bss_mld_addr()), beaconed with the MLD address,
while ubus get_status reported the configured BSSID. A subscriber that
built its BSS Transition candidates from get_status named a BSSID that
nothing transmitted.
Add a patch that keeps the configured BSSID as the link address and
takes the netdev address as the MLD address, as hostapd_driver_init()
does for the first interface. Without a configured BSSID, the link
takes a random address with the OUI of the netdev, as
hostapd_driver_init() does (pre_setup_mld), so that the link address
cannot be the MLD address: a later link that is given the MLD address
as its BSSID would fail ADD_LINK with -EALREADY. hostapd.uc always sets
a BSSID, so OpenWrt configurations take the configured address.
Have get_status and the channel-switch event report hapd->own_addr,
the address the BSS transmits with, instead of conf->bssid, which is
all zeros where the configuration names none.
Fixes: 816c2d86e7 ("hostapd: add support for MLO interfaces in ucode")
Signed-off-by: Felix Fietkau <nbd@nbd.name>
The nl80211 driver gave up the wiphy's Beacon event subscription
whenever one link stopped, and the other links of an AP MLD lost the
OBSS Beacon events that feed OLBC detection and the AP list. Add a
patch that gives it up with the last link.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Patch 186 counts the AP Removal Timer and left the BSS running at 0, so
the removed link kept beaconing until the caller's configuration
removed it, 1.64 s late on MT7996. Add a patch that stops the
affiliated AP at the expiry of its timer and hands the BSS to the ucode
bss_link_removed hook.
hostapd knows the TBTT to one beacon interval only: nl80211 reports
neither a Beacon transmission nor the TSF of an AP. The count runs from
the return of the first Beacon update with the count, and the link
stops after the Beacon with the count 1 and before the TBTT the count
names, so no Beacon carries the reserved value 0.
At the expiry, the other links drop the Reconfiguration element. Each
station associated over the link gets a Disassociation, or a
Deauthentication on a remaining link where it is a non-AP MLD, and the
link waits for their TX status, for at most a quarter of a beacon
interval. The patch message lists the limits of the timing. A link in
an MBSSID set with other BSSes is refused, as mac80211 stops the whole
set with it.
The bss_link_removed hook removes the BSS the way a configuration
without it would: bss.delete(), or the removal of the PHY's interface
where it was the only BSS. It has no fallback to a PHY restart, which
would take the PHY's other BSSes down. The configuration that follows
then finds nothing to do. The hook also takes the radio of the link out
of the MLD's configuration, so that a configuration that brings the
link back creates the MLD netdev again, as mld_config_matches() does
for any radio that joins.
The patch also refuses to announce the removal of a link where no other
link of the AP MLD beacons, and checks again at the expiry, as the other
links can stop during the countdown. IEEE 802.11be-2024 35.3.6.3
removes an affiliated AP from an AP MLD that goes on. Such a link keeps
running with its Beacon rewritten without the Reconfiguration element,
and the caller's configuration decides whether it goes. The
mld_link_remove ubus method answers a refusal with
UBUS_STATUS_NOT_SUPPORTED, and no event reports a link kept at the
expiry.
The mld_link_remove ubus method now answers expiry_ms, the time until
the expiry, rounded down, and removal_ms, the time until the link stops
at the latest, with that wait, rounded up.
A BSS of an AP MLD that goes through bss.delete() or remove_iface
without the timer now has the Beacons of the links that stay rewritten,
as they named it in their Reduced Neighbor Report, the number of
simultaneous links and a Reconfiguration element whose count stopped.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
ieee80211_stop_ap() stops one link, but ieee80211_stop_mbssid() walks
every active link of the interface. The STOP_AP of one link of an AP
MLD thus took apart the MBSSID set of each other link and stopped the
interfaces of those sets, on radios that keep running.
Add a patch that takes apart only the MBSSID set of the link that
stops. The stop of the whole interface still walks all its links.
With it, hostapd can remove an affiliated AP of an AP MLD whose other
links carry an MBSSID set. A link that is itself in an MBSSID set still
takes that set apart, and cfg80211_stop_iface() stops each member
interface with all its links.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A link STOP_AP cleared state that belongs to the interface, while the
other links of the AP MLD kept beaconing: cfg80211 the SSID, the owner
and the QoS map, mac80211 the SSID, the S1G and beacon rate flags and
the carrier of every AP_VLAN. The kept links then ran without a DSCP
map, reported no SSID, and their 4-address stations lost their bridge
port carrier.
Add two patches that clear this state only with the last link that
beacons.
With the SSID kept, cfg80211 refuses a START_AP that changes the SSID
of one link while another link beacons with the old one ("require
identical SSID for MLO"). hostapd.uc stops every link of an AP MLD
before an SSID change (the previous commit).
Signed-off-by: Felix Fietkau <nbd@nbd.name>
All links of an AP MLD carry one SSID. With cfg80211 keeping the SSID of
an interface while another link beacons, nl80211_start_ap() refuses a
link whose SSID differs from it ("require identical SSID for MLO"). An
SSID change applied PHY by PHY then leaves the first PHY's link down:
its new SSID is refused while the other links still beacon with the
old one.
Treat an SSID change as a new AP MLD in mld_config_matches(), so every
link stops before any starts with the new SSID.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
mld_config_matches() kept an MLD netdev only where its radio mask was
unchanged. An AP MLD that lost one affiliated AP was therefore removed
and created again, and every link stopped beaconing while it was.
Keep the MLD where its radios are a subset of the radios it runs now.
The removed radio's configuration drops the BSS, and bss.delete() takes
the link off the netdev (NL80211_CMD_REMOVE_LINK) while the other links
keep running. The netdev keeps the radio mask it was created with,
which cannot change while it is up, and which may name a radio without
a link. A radio that joins, the one of a removed link included, still
gets a new netdev: mt7996 keeps the state of a removed link until the
netdev goes, and adding the link again beside it is not known to work.
uc_hostapd_bss_delete() frees the link stations of an MLD BSS before its
STOP_AP, which flushes every station that holds the link: a non-AP MLD
that also holds another link keeps its association only where its link
station goes first.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
The links of an AP MLD share one interface name. Each asked for the
object of that name, the first link got it, and the others failed. The
object, its subscribers, and the state a subscriber set through it
belonged to that one link:
- the object went away with that link, although other links remained,
and a subscriber took the removal for the end of the AP MLD and of
every station on it;
- notify_response, the BSS Transition Management Query answer flag and
the bans applied to that link only, while each link read its own;
- bss_mgmt_enable and the rrm_nr methods changed the capabilities and
the neighbor report database of that link only.
Keep one object per AP MLD, from its first link to its last. A method
runs on a link that is there: the AP MLD's first link once its setup
has added it to the object, or, once the last link has left the AP MLD
and before its BSS frees the object, a link the object still counts. A
link is counted once. Notifications of every link go through it.
notify_response, the BTM query answer and the bans are the AP MLD's,
and every link reads them. bss_mgmt_enable, the implicit neighbor
report enable of the rrm_nr methods and rrm_nr_set apply to every
link, and a link that joins later takes the flags and the neighbor list
set before.
The methods that name a station act on the link that holds it:
get_clients lists the stations of every link, a non-AP MLD once with
the station of its association link, and rrm_beacon_req, rrm_lm_req
and update_airtime find the station on any link, as del_client and
bss_transition_request already did. They answered for the first link
only, and after that link left the AP MLD, for another one.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
The teardown of a BSS of an AP MLD freed the stations of that BSS and
left the partner link stations of each non-AP MLD associated over it
on the other links, with no state machine. Add a patch that frees them
in hostapd_bss_deinit_no_free(), which every teardown path of a BSS
runs.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A link that leaves the AP MLD frees the link stations of the non-AP
MLDs associated over another link, for example patch 186 at the count
of 0. Each one reported AP-STA-DISCONNECTED and a ubus "disassoc" event
with the MLD address, although the non-AP MLD stays associated over its
other links, and a subscriber took the event for the station's
departure. Add a patch that frees such a link station without the
event.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
hostapd_drv_sta_disassoc() gave a link ID only for a non-AP MLD. The
Disassociation to any other station of an AP MLD went out with the
frequency of the interface's first link and the link address of its
BSS, and where that BSS was not the first link, mac80211 refused the
frame with -EBUSY. Add a patch that passes the link ID for every station
of an AP MLD, as hostapd_drv_sta_deauth() does.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
A ban set through del_client or the ban call arms a timeout that names
the BSS, and hostapd_ubus_free_bss() neither cancelled it nor freed the
ban. A BSS freed while a ban was pending left a timeout that deleted
from the ban list of freed memory. The name and the bans also leaked
where the ubus context was already gone.
Name the ban list in the timeout rather than the BSS, and drop every
ban of a BSS when it is freed, whether the context is still there or
not.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
__hostapd_bss_mgmt_enable_f() tested
bss->radio_measurements[0] & flags == flags, which C parses as
bss->radio_measurements[0] & (flags == flags), that is, the Link
Measurement bit (bit 0).
For Beacon Report, the test read the wrong bit: Beacon Report was left
off wherever Link Measurement was already on, and was reported as
changed where its bits were already set and Link Measurement was off.
For Link Measurement, whose flag is bit 0, the test gave the right
answer.
Compare the masked bits with the set in both cases.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
`status` says whether a BSS runs, but not why one does not. A BSS in
HT_SCAN starts within seconds, one in DFS waits for the channel
availability check, and one that is DISABLED may not start at all. A
caller that waits for BSSs to come up needs to tell these apart.
Add `state` to each link and each non-MLO entry, as iface.state() names
it: UNINITIALIZED, DISABLED, COUNTRY_UPDATE, ACS, HT_SCAN, DFS, NO_IR or
ENABLED.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
bss.rename() closed the control socket of the BSS and opened a new one
under the new name. This dropped every attached monitor, for example
hostapd_cli -a or a wpa_cli in interactive mode. Keeping the list of
monitors does not help: the socket of a monitor is connected to the old
control socket, and the kernel refuses datagrams to it from any other
socket (unix_may_send()).
Keep the socket and give it the new name: link() the new path to the
socket file, then unlink() the old one. connect() finds a socket by
the inode of its path, so new clients reach the same socket. link()
does not replace an existing file, so a name that is in use stays
intact. If the move fails, for example on a leftover file of that
name, close the socket and open a new one as before.
The ucode code needs the path of the socket, which only
hostapd/ctrl_iface.c builds. Export it through hapd_interfaces, as
ctrl_iface_recv is.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
bss.rename() ignored the results of hostapd_set_ctrl_sock_iface() and
ctrl_iface_init(). A BSS that could not open the control socket under
its new name was reported as renamed, and ran on without a control
socket.
Return null in that case, as add_bss() does when ctrl_iface_init()
fails. hostapd.uc then restarts the phy, which opens the control
sockets again.
Signed-off-by: Felix Fietkau <nbd@nbd.name>