hostapd: allow adding initial AP without breaking STA interface connection

When switching from a STA-only configuration to AP+STA on the same phy, the
STA was previously restarted in order to notify hostapd of the new frequency,
which might not match the AP configuration.
Fix the STA restart by querying the operating frequency from within hostapd
when bringing up the AP.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
Felix Fietkau 2023-09-03 09:51:28 +02:00
parent d65354488d
commit 4a0b1af905
4 changed files with 112 additions and 33 deletions

View File

@ -889,7 +889,6 @@ wpa_supplicant_init_config() {
wpa_supplicant_add_interface() {
local ifname="$1"
local mode="$2"
local hostapd_ctrl="$3"
local prev
_wpa_supplicant_common "$ifname"
@ -903,7 +902,6 @@ wpa_supplicant_add_interface() {
json_add_string config "$_config"
json_add_string macaddr "$macaddr"
[ -n "$network_bridge" ] && json_add_string bridge "$network_bridge"
[ -n "$hostapd_ctrl" ] && json_add_string hostapd_ctrl "$hostapd_ctrl"
[ -n "$wds" ] && json_add_boolean 4addr "$wds"
json_add_boolean powersave "$powersave"
[ "$mode" = "mesh" ] && mac80211_add_mesh_params
@ -978,7 +976,7 @@ mac80211_setup_supplicant() {
wpa_supplicant_add_network "$ifname" "$freq" "$htmode" "$noscan"
fi
wpa_supplicant_add_interface "$ifname" "$mode" "$hostapd_ctrl"
wpa_supplicant_add_interface "$ifname" "$mode"
return 0
}

View File

@ -31,7 +31,7 @@ function iface_remove(cfg)
wdev_remove(bss.ifname);
}
function iface_gen_config(phy, config)
function iface_gen_config(phy, config, start_disabled)
{
let str = `data:
${join("\n", config.radio.data)}
@ -45,12 +45,69 @@ channel=${config.radio.channel}
str += `
${type}=${bss.ifname}
${join("\n", bss.data)}
`;
if (start_disabled)
str += `
start_disabled=1
`;
}
return str;
}
function iface_freq_info(iface, config, params)
{
let freq = params.frequency;
if (!freq)
return null;
let sec_offset = params.sec_chan_offset;
if (sec_offset != -1 && sec_offset != 1)
sec_offset = 0;
let width = 0;
for (let line in config.radio.data) {
if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
sec_offset = null; // auto-detect
continue;
}
let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
if (!val)
continue;
val = int(val[2]);
if (val > width)
width = val;
}
if (freq < 4000)
width = 0;
return hostapd.freq_info(freq, sec_offset, width);
}
function iface_add(phy, config, phy_status)
{
let config_inline = iface_gen_config(phy, config, !!phy_status);
let bss = config.bss[0];
let ret = hostapd.add_iface(`bss_config=${bss.ifname}:${config_inline}`);
if (ret < 0)
return false;
if (!phy_status)
return true;
let iface = hostapd.interfaces[bss.ifname];
if (!iface)
return false;
let freq_info = iface_freq_info(iface, config, phy_status);
return iface.start(freq_info) >= 0;
}
function iface_restart(phy, config, old_config)
{
iface_remove(old_config);
@ -65,11 +122,18 @@ function iface_restart(phy, config, old_config)
let err = wdev_create(phy, bss.ifname, { mode: "ap" });
if (err)
hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
let config_inline = iface_gen_config(phy, config);
let ubus = hostapd.data.ubus;
let phy_status = ubus.call("wpa_supplicant", "phy_status", { phy: phy });
if (phy_status && phy_status.state == "COMPLETED") {
if (iface_add(phy, config, phy_status))
return;
hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
}
ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
if (hostapd.add_iface(`bss_config=${bss.ifname}:${config_inline}`) < 0)
if (!iface_add(phy, config))
hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
}
@ -295,7 +359,6 @@ function iface_load_config(filename)
}
let main_obj = {
reload: {
args: {
@ -344,34 +407,10 @@ let main_obj = {
return 0;
}
let freq = req.args.frequency;
if (!freq)
if (!req.args.frequency)
return libubus.STATUS_INVALID_ARGUMENT;
let sec_offset = req.args.sec_chan_offset;
if (sec_offset != -1 && sec_offset != 1)
sec_offset = 0;
let width = 0;
for (let line in config.radio.data) {
if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
sec_offset = null; // auto-detect
continue;
}
let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
if (!val)
continue;
val = int(val[2]);
if (val > width)
width = val;
}
if (freq < 4000)
width = 0;
let freq_info = hostapd.freq_info(freq, sec_offset, width);
let freq_info = iface_freq_info(iface, config, req.args);
if (!freq_info)
return libubus.STATUS_UNKNOWN_ERROR;

View File

@ -43,6 +43,11 @@ function iface_cb(new_if, old_if)
return;
}
if (new_if && old_if)
wpas.printf(`Update configuration for interface ${old_if.config.iface}`);
else if (old_if)
wpas.printf(`Remove interface ${old_if.config.iface}`);
if (old_if)
iface_stop(old_if);
}
@ -106,6 +111,41 @@ let main_obj = {
return 0;
}
},
phy_status: {
args: {
phy: ""
},
call: function(req) {
if (!req.args.phy)
return libubus.STATUS_INVALID_ARGUMENT;
let phy = wpas.data.config[req.args.phy];
if (!phy)
return libubus.STATUS_NOT_FOUND;
for (let ifname in phy.data) {
try {
let iface = wpas.interfaces[ifname];
if (!iface)
continue;
let status = iface.status();
if (!status)
continue;
if (status.state == "INTERFACE_DISABLED")
continue;
status.ifname = ifname;
return status;
} catch (e) {
continue;
}
}
return libubus.STATUS_NOT_FOUND;
}
},
config_set: {
args: {
phy: "",
@ -116,6 +156,7 @@ let main_obj = {
if (!req.args.phy)
return libubus.STATUS_INVALID_ARGUMENT;
wpas.printf(`Set new config for phy ${req.args.phy}`);
try {
if (req.args.config)
set_config(req.args.phy, req.args.config);

View File

@ -362,6 +362,7 @@ out:
int ret;
hapd->started = 1;
hapd->conf->start_disabled = 0;
hostapd_set_freq(hapd, conf->hw_mode, iface->freq,
conf->channel,
conf->enable_edmg,