hostapd: make ubus calls to wpa_supplicant asynchronous
authorFelix Fietkau <nbd@nbd.name>
Thu, 11 Jan 2024 08:14:59 +0000 (09:14 +0100)
committerFelix Fietkau <nbd@nbd.name>
Thu, 11 Jan 2024 08:15:54 +0000 (09:15 +0100)
This fixes a deadlock issue where depending on the setup order, hostapd and
wpa_supplicant could end up waiting for each other

Reported-by: Michael-cy Lee (李峻宇) <Michael-cy.Lee@mediatek.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/network/services/hostapd/files/hostapd.uc

index b85f523b352fec118a3b8a614ae77db42db8b882..0c89cd71cc47bed1a69693b0c4858795a9dc9bf0 100644 (file)
@@ -2,9 +2,10 @@ let libubus = require("ubus");
 import { open, readfile } from "fs";
 import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
 
-let ubus = libubus.connect();
+let ubus = libubus.connect(null, 60);
 
 hostapd.data.config = {};
+hostapd.data.pending_config = {};
 
 hostapd.data.file_fields = {
        vlan_file: true,
@@ -122,17 +123,111 @@ function iface_config_macaddr_list(config)
        return macaddr_list;
 }
 
-function iface_update_supplicant_macaddr(phy, config)
+function __iface_pending_next(pending, state, ret, data)
 {
-       let macaddr_list = [];
-       for (let i = 0; i < length(config.bss); i++)
-               push(macaddr_list, config.bss[i].bssid);
-       ubus.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
+       let config = pending.config;
+       let phydev = pending.phydev;
+       let phy = pending.phy;
+
+       if (pending.defer)
+               pending.defer.abort();
+       delete pending.defer;
+       switch (state) {
+       case "init":
+               let macaddr_list = [];
+               for (let i = 0; i < length(config.bss); i++)
+                       push(macaddr_list, config.bss[i].bssid);
+               pending.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
+               return "create_bss";
+       case "create_bss":
+               let bss = config.bss[0];
+               let err = wdev_create(phy, bss.ifname, { mode: "ap" });
+               if (err) {
+                       hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
+                       return null;
+               }
+
+               pending.call("wpa_supplicant", "phy_status", { phy: phy });
+               return "check_phy";
+       case "check_phy":
+               let phy_status = data;
+               if (phy_status && phy_status.state == "COMPLETED") {
+                       if (iface_add(phy, config, phy_status))
+                               return "done";
+
+                       hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
+               }
+               pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
+               return "wpas_stopped";
+       case "wpas_stopped":
+               if (!iface_add(phy, config))
+                       hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
+               pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
+               return null;
+       case "done":
+       default:
+               delete hostapd.data.pending_config[phy];
+               break;
+       }
+}
+
+function iface_pending_next(ret, data)
+{
+       let pending = true;
+       let cfg = this;
+
+       while (pending) {
+               this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
+               if (!this.next_state) {
+                       __iface_pending_next(cfg, "done");
+                       return;
+               }
+               pending = !this.defer;
+       }
+}
+
+function iface_pending_abort()
+{
+       this.next_state = "done";
+       this.next();
+}
+
+function iface_pending_ubus_call(obj, method, arg)
+{
+       let ubus = hostapd.data.ubus;
+       let pending = this;
+       this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
+}
+
+const iface_pending_proto = {
+       next: iface_pending_next,
+       call: iface_pending_ubus_call,
+       abort: iface_pending_abort,
+};
+
+function iface_pending_init(phydev, config)
+{
+       let phy = phydev.name;
+
+       let pending = proto({
+               next_state: "init",
+               phydev: phydev,
+               phy: phy,
+               config: config,
+               next: iface_pending_next,
+       }, iface_pending_proto);
+
+       hostapd.data.pending_config[phy] = pending;
+       pending.next();
 }
 
 function iface_restart(phydev, config, old_config)
 {
        let phy = phydev.name;
+       let pending = hostapd.data.pending_config[phy];
+
+       if (pending)
+               pending.abort();
 
        hostapd.remove_iface(phy);
        iface_remove(old_config);
@@ -150,26 +245,7 @@ function iface_restart(phydev, config, old_config)
                        bss.bssid = phydev.macaddr_next();
        }
 
-       iface_update_supplicant_macaddr(phy, config);
-
-       let bss = config.bss[0];
-       let err = wdev_create(phy, bss.ifname, { mode: "ap" });
-       if (err)
-               hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
-
-       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 (!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 });
+       iface_pending_init(phydev, config);
 }
 
 function array_to_obj(arr, key, start)
@@ -274,6 +350,9 @@ function iface_reload_config(phydev, config, old_config)
        if (is_equal(old_config.bss, config.bss))
                return true;
 
+       if (hostapd.data.pending_config[phy])
+               return false;
+
        if (!old_config.bss || !old_config.bss[0])
                return false;