hostapd.data.config = {};
hostapd.data.pending_config = {};
+hostapd.data.apsta_freq = {};
hostapd.data.file_fields = {
vlan_file: true,
* interface (STA/mesh/adhoc) via apsta_state; the AP never picks its
* own channel here. Adopt the new fallback channel without touching the
* running BSSes.
+ *
+ * A station MLD marks every radio it spans as channel following, yet it
+ * only drives the channel of the radios it holds a link on. Radios
+ * without a link have no channel to follow, so let them apply their own
+ * configuration instead of deferring to a station that never reports one.
*/
- if (radio.channel_follow)
+ if (radio.channel_follow && hostapd.data.apsta_freq[name])
return true;
/*
function iface_config_remove(name, old_config)
{
+ delete hostapd.data.apsta_freq[name];
hostapd.remove_iface(name);
return iface_remove(old_config);
}
sec_chan_offset: 0,
csa: true,
csa_count: 0,
+ no_link: true,
},
call: function(req) {
let phy = phy_name(req.args.phy, req.args.radio);
return 0;
if (!req.args.up) {
+ delete hostapd.data.apsta_freq[phy];
iface.stop();
return 0;
}
+ if (req.args.frequency)
+ hostapd.data.apsta_freq[phy] = req.args.frequency;
+ else if (req.args.no_link && hostapd.data.apsta_freq[phy]) {
+ /*
+ * The station gave up the link that dictated this radio's
+ * channel. Hand the radio back to its own configuration
+ * rather than leaving the AP on the channel the station
+ * happened to use last.
+ */
+ delete hostapd.data.apsta_freq[phy];
+
+ if (iface_channel_switch(phy, config))
+ return 0;
+
+ let phydev = phy_open(config.phy, config.radio_idx);
+ if (phydev)
+ iface_restart(phydev, config, config);
+
+ return 0;
+ }
+
let freq_info;
if (req.args.frequency) {
freq_info = iface_freq_info(iface, config, req.args);
radio: i,
};
- if (state == "COMPLETED")
+ if (state == "COMPLETED") {
iface_status_fill_radio(mld, i, radio_msg, status);
+ // A station MLD spans every radio it was configured for, but only
+ // drives the channel of the radios it holds a link on. Tell the AP
+ // side which radios are left without one, so they can fall back to
+ // their own channel configuration.
+ if (!radio_msg.frequency)
+ radio_msg.no_link = true;
+ }
+
ubus.call("hostapd", "apsta_state", radio_msg);
}
}
},
ctrl_event: function(name, iface, ev) {
iface_ubus_notify(name, ev);
+
+ // The set of links of an associated MLD can change without a state
+ // transition, which would leave the AP side following a link that no
+ // longer exists, or ignoring one that just appeared.
+ if (index(ev, "CTRL-EVENT-LINK-RECONFIG") != 0)
+ return;
+
+ try {
+ iface_hostapd_notify(name, iface, "COMPLETED");
+ } catch (e) {
+ wpas.printf(`Error handling link reconfiguration: ${e}`);
+ }
},
state: function(ifname, iface, state) {
let event_data = iface.status();