hostapd: allow adding initial AP without breaking STA interface connection
authorFelix Fietkau <nbd@nbd.name>
Sun, 3 Sep 2023 07:51:28 +0000 (09:51 +0200)
committerFelix Fietkau <nbd@nbd.name>
Sun, 3 Sep 2023 07:51:28 +0000 (09:51 +0200)
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>
package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh
package/network/services/hostapd/files/hostapd.uc
package/network/services/hostapd/files/wpa_supplicant.uc
package/network/services/hostapd/src/src/ap/ucode.c

index db1ba231f3a33a2f9782fbe36d37d67113d74842..3b88af46795970633acd14c1434c1c3e44380183 100644 (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
 }
index f9f0c31babc21c8faec8f3cbbc1289d19c13e528..384c5c2eb08d51bc9eaeb47f594a0651ece1dc9e 100644 (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;
 
index 50da7f14ffe4f68f0619afcd9c836d0c3bec4e66..f8a3fcb52531c0b87800e36b1cfef0976d6e288b 100644 (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);
index f0b4ce4eb82b0bdae78de6306998af530fd9f74f..0326f6fc82dc9f8aa2f74ae695f7c4605247e87d 100644 (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,