hostapd: rework reload support and MAC address handling
authorFelix Fietkau <nbd@nbd.name>
Sat, 9 Sep 2023 15:07:09 +0000 (17:07 +0200)
committerFelix Fietkau <nbd@nbd.name>
Wed, 13 Sep 2023 10:37:44 +0000 (12:37 +0200)
MAC address and interface name assigned by mac80211.sh depend on the order in
which interfaces are brought up. This order changes when interfaces get added
or removed, which can cause unnecessary reload churn.

One part of the fix it making MAC address allocation more dynamic in both
wpa_supplicant and hostapd, by ignoring the provided MAC address using
the next available one, whenever the config does not explicitly specify one.

The other part is making use of support for renaming netdevs at runtime and
preserving the MAC address for renamed netdevs.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
package/kernel/mac80211/files/lib/netifd/wireless/mac80211.sh
package/network/services/hostapd/files/common.uc
package/network/services/hostapd/files/hostapd.sh
package/network/services/hostapd/files/hostapd.uc
package/network/services/hostapd/files/wdev.uc
package/network/services/hostapd/files/wpa_supplicant.uc

index 357277fbf7134db832597230cea2dbdcf0156286..7d3ab4dc01bd12bcd86405700abc60ed1599a622 100644 (file)
@@ -489,6 +489,7 @@ ${channel_list:+chanlist=$channel_list}
 ${hostapd_noscan:+noscan=1}
 ${tx_burst:+tx_queue_data2_burst=$tx_burst}
 mbssid=$multiple_bssid
+#num_global_macaddr=$num_global_macaddr
 $base_cfg
 
 EOF
@@ -519,6 +520,7 @@ mac80211_hostapd_setup_bss() {
        cat >> /var/run/hostapd-$phy.conf <<EOF
 $hostapd_cfg
 bssid=$macaddr
+${default_macaddr:+#default_macaddr}
 ${dtim_period:+dtim_period=$dtim_period}
 ${max_listen_int:+max_listen_interval=$max_listen_int}
 EOF
@@ -646,13 +648,16 @@ mac80211_prepare_vif() {
        set_default powersave 0
        json_add_string _ifname "$ifname"
 
+       default_macaddr=
        if [ -z "$macaddr" ]; then
                macaddr="$(mac80211_generate_mac $phy)"
                macidx="$(($macidx + 1))"
+               default_macaddr=1
        elif [ "$macaddr" = 'random' ]; then
                macaddr="$(macaddr_random)"
        fi
        json_add_string _macaddr "$macaddr"
+       json_add_string _default_macaddr "$default_macaddr"
        json_select ..
 
 
@@ -776,7 +781,7 @@ mac80211_setup_adhoc() {
 
        json_add_object "$ifname"
        json_add_string mode adhoc
-       json_add_string macaddr "$macaddr"
+       [ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
        json_add_string ssid "$ssid"
        json_add_string freq "$freq"
        json_add_string htmode "$iw_htmode"
@@ -802,7 +807,7 @@ mac80211_setup_mesh() {
 
        json_add_object "$ifname"
        json_add_string mode mesh
-       json_add_string macaddr "$macaddr"
+       [ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
        json_add_string ssid "$ssid"
        json_add_string freq "$freq"
        json_add_string htmode "$iw_htmode"
@@ -862,7 +867,7 @@ wpa_supplicant_add_interface() {
        json_add_string iface "$ifname"
        json_add_string mode "$mode"
        json_add_string config "$_config"
-       json_add_string macaddr "$macaddr"
+       [ -n "$default_macaddr" ] || json_add_string macaddr "$macaddr"
        [ -n "$network_bridge" ] && json_add_string bridge "$network_bridge"
        [ -n "$wds" ] && json_add_boolean 4addr "$wds"
        json_add_boolean powersave "$powersave"
@@ -950,6 +955,7 @@ mac80211_setup_vif() {
        json_select config
        json_get_var ifname _ifname
        json_get_var macaddr _macaddr
+       json_get_var default_macaddr _default_macaddr
        json_get_vars mode wds powersave
 
        set_default powersave 0
index f37804c8c6ec5678dbc8d50a3a572cd0999ed79e..ccffe3eb4362199aabdb5655a5e85e9d54be746d 100644 (file)
@@ -1,6 +1,6 @@
 import * as nl80211 from "nl80211";
 import * as rtnl from "rtnl";
-import { readfile } from "fs";
+import { readfile, glob, basename, readlink } from "fs";
 
 const iftypes = {
        ap: nl80211.const.NL80211_IFTYPE_AP,
@@ -109,71 +109,139 @@ function macaddr_join(addr)
        return join(":", map(addr, (val) => sprintf("%02x", val)));
 }
 
-function wdev_generate_macaddr(phy, data)
+function wdev_macaddr(wdev)
 {
-       let idx = int(data.id ?? 0);
-       let mbssid = int(data.mbssid ?? 0) > 0;
-       let num_global = int(data.num_global ?? 1);
-       let use_global = !mbssid && idx < num_global;
+       return trim(readfile(`/sys/class/net/${wdev}/address`));
+}
 
-       let base_addr = phy_sysfs_file(phy, "macaddress");
-       if (!base_addr)
-               return null;
+const phy_proto = {
+       macaddr_init: function(used, options) {
+               this.macaddr_options = options ?? {};
+               this.macaddr_list = {};
 
-       if (!idx && !mbssid)
-               return base_addr;
+               if (type(used) == "object")
+                       for (let addr in used)
+                               this.macaddr_list[addr] = used[addr];
+               else
+                       for (let addr in used)
+                               this.macaddr_list[addr] = -1;
 
-       let base_mask = phy_sysfs_file(phy, "address_mask");
-       if (!base_mask)
-               return null;
+               this.for_each_wdev((wdev) => {
+                       let macaddr = wdev_macaddr(wdev);
+                       this.macaddr_list[macaddr] ??= -1;
+               });
 
-       if (base_mask == "00:00:00:00:00:00" && idx >= num_global) {
-               let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
+               return this.macaddr_list;
+       },
 
-               if (idx < length(addrs))
-                       return addrs[idx];
+       macaddr_generate: function(data) {
+               let phy = this.name;
+               let idx = int(data.id ?? 0);
+               let mbssid = int(data.mbssid ?? 0) > 0;
+               let num_global = int(data.num_global ?? 1);
+               let use_global = !mbssid && idx < num_global;
 
-               base_mask = "ff:ff:ff:ff:ff:ff";
-       }
+               let base_addr = phy_sysfs_file(phy, "macaddress");
+               if (!base_addr)
+                       return null;
+
+               if (!idx && !mbssid)
+                       return base_addr;
+
+               let base_mask = phy_sysfs_file(phy, "address_mask");
+               if (!base_mask)
+                       return null;
+
+               if (base_mask == "00:00:00:00:00:00" && idx >= num_global) {
+                       let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
+
+                       if (idx < length(addrs))
+                               return addrs[idx];
+
+                       base_mask = "ff:ff:ff:ff:ff:ff";
+               }
+
+               let addr = macaddr_split(base_addr);
+               let mask = macaddr_split(base_mask);
+               let type;
 
-       let addr = macaddr_split(base_addr);
-       let mask = macaddr_split(base_mask);
-       let type;
-
-       if (mbssid)
-               type = "b5";
-       else if (use_global)
-               type = "add";
-       else if (mask[0] > 0)
-               type = "b1";
-       else if (mask[5] < 0xff)
-               type = "b5";
-       else
-               type = "add";
-
-       switch (type) {
-       case "b1":
-               if (!(addr[0] & 2))
-                       idx--;
-               addr[0] |= 2;
-               addr[0] ^= idx << 2;
-               break;
-       case "b5":
                if (mbssid)
+                       type = "b5";
+               else if (use_global)
+                       type = "add";
+               else if (mask[0] > 0)
+                       type = "b1";
+               else if (mask[5] < 0xff)
+                       type = "b5";
+               else
+                       type = "add";
+
+               switch (type) {
+               case "b1":
+                       if (!(addr[0] & 2))
+                               idx--;
                        addr[0] |= 2;
-               addr[5] ^= idx;
-               break;
-       default:
-               for (let i = 5; i > 0; i--) {
-                       addr[i] += idx;
-                       if (addr[i] < 256)
-                               break;
-                       addr[i] %= 256;
+                       addr[0] ^= idx << 2;
+                       break;
+               case "b5":
+                       if (mbssid)
+                               addr[0] |= 2;
+                       addr[5] ^= idx;
+                       break;
+               default:
+                       for (let i = 5; i > 0; i--) {
+                               addr[i] += idx;
+                               if (addr[i] < 256)
+                                       break;
+                               addr[i] %= 256;
+                       }
+                       break;
+               }
+
+               return macaddr_join(addr);
+       },
+
+       macaddr_next: function(val) {
+               let data = this.macaddr_options ?? {};
+               let list = this.macaddr_list;
+
+               for (let i = 0; i < 32; i++) {
+                       data.id = i;
+
+                       let mac = this.macaddr_generate(data);
+                       if (!mac)
+                               return null;
+
+                       if (list[mac] != null)
+                               continue;
+
+                       list[mac] = val != null ? val : -1;
+                       return mac;
+               }
+       },
+
+       for_each_wdev: function(cb) {
+               let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`);
+               wdevs = map(wdevs, (arg) => basename(arg));
+               for (let wdev in wdevs) {
+                       if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name)
+                               continue;
+
+                       cb(wdev);
                }
-               break;
        }
+};
 
-       return macaddr_join(addr);
+function phy_open(phy)
+{
+       let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
+       if (!phyidx)
+               return null;
+
+       return proto({
+               name: phy,
+               idx: int(phyidx)
+       }, phy_proto);
 }
 
 const vlist_proto = {
@@ -247,4 +315,4 @@ function vlist_new(cb) {
                }, vlist_proto);
 }
 
-export { wdev_remove, wdev_create, wdev_generate_macaddr, is_equal, vlist_new, phy_is_fullmac };
+export { wdev_remove, wdev_create, is_equal, vlist_new, phy_is_fullmac, phy_open };
index a0945fdc53ea11fc4296d9873b7a84ffe9557679..271c1f7becd27064f3e244298e6058abb5f06177 100644 (file)
@@ -632,8 +632,7 @@ hostapd_set_bss_options() {
                [ -n "$wpa_strict_rekey" ] && append bss_conf "wpa_strict_rekey=$wpa_strict_rekey" "$N"
        }
 
-       set_default nasid "${macaddr//\:}"
-       append bss_conf "nas_identifier=$nasid" "$N"
+       [ -n "$nasid" ] && append bss_conf "nas_identifier=$nasid" "$N"
 
        [ -n "$acct_interval" ] && \
                append bss_conf "radius_acct_interim_interval=$acct_interval" "$N"
index 384c5c2eb08d51bc9eaeb47f594a0651ece1dc9e..9b356dcc309ebc7d9876db81b611ae5ab545ea2e 100644 (file)
@@ -1,6 +1,6 @@
 let libubus = require("ubus");
 import { open, readfile } from "fs";
-import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac } from "common";
+import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
 
 let ubus = libubus.connect();
 
@@ -41,10 +41,13 @@ channel=${config.radio.channel}
        for (let i = 0; i < length(config.bss); i++) {
                let bss = config.bss[i];
                let type = i > 0 ? "bss" : "interface";
+               let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
 
                str += `
 ${type}=${bss.ifname}
+bssid=${bss.bssid}
 ${join("\n", bss.data)}
+nas_identifier=${nasid}
 `;
                if (start_disabled)
                        str += `
@@ -108,8 +111,22 @@ function iface_add(phy, config, phy_status)
        return iface.start(freq_info) >= 0;
 }
 
-function iface_restart(phy, config, old_config)
+function iface_config_macaddr_list(config)
 {
+       let macaddr_list = {};
+       for (let i = 0; i < length(config.bss); i++) {
+               let bss = config.bss[i];
+               if (!bss.default_macaddr)
+                       macaddr_list[bss.bssid] = i;
+       }
+
+       return macaddr_list;
+}
+
+function iface_restart(phydev, config, old_config)
+{
+       let phy = phydev.name;
+
        iface_remove(old_config);
        iface_remove(config);
 
@@ -118,6 +135,13 @@ function iface_restart(phy, config, old_config)
                return;
        }
 
+       phydev.macaddr_init(iface_config_macaddr_list(config));
+       for (let i = 0; i < length(config.bss); i++) {
+               let bss = config.bss[i];
+               if (bss.default_macaddr)
+                       bss.bssid = phydev.macaddr_next();
+       }
+
        let bss = config.bss[0];
        let err = wdev_create(phy, bss.ifname, { mode: "ap" });
        if (err)
@@ -175,8 +199,64 @@ function bss_reload_psk(bss, config, old_config)
        hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
 }
 
-function iface_reload_config(phy, config, old_config)
+function remove_file_fields(config)
+{
+       return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
+}
+
+function bss_remove_file_fields(config)
 {
+       let new_cfg = {};
+
+       for (let key in config)
+               new_cfg[key] = config[key];
+       new_cfg.data = remove_file_fields(new_cfg.data);
+       new_cfg.hash = {};
+       for (let key in config.hash)
+               new_cfg.hash[key] = config.hash[key];
+       delete new_cfg.hash.wpa_psk_file;
+
+       return new_cfg;
+}
+
+function bss_config_hash(config)
+{
+       return hostapd.sha1(remove_file_fields(config) + "");
+}
+
+function bss_find_existing(config, prev_config, prev_hash)
+{
+       let hash = bss_config_hash(config.data);
+
+       for (let i = 0; i < length(prev_config.bss); i++) {
+               if (!prev_hash[i] || hash != prev_hash[i])
+                       continue;
+
+               prev_hash[i] = null;
+               return i;
+       }
+
+       return -1;
+}
+
+function get_config_bss(config, idx)
+{
+       if (!config.bss[idx]) {
+               hostapd.printf(`Invalid bss index ${idx}`);
+               return null;
+       }
+
+       let ifname = config.bss[idx].ifname;
+       if (!ifname)
+               hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
+
+       return hostapd.bss[ifname];
+}
+
+function iface_reload_config(phydev, config, old_config)
+{
+       let phy = phydev.name;
+
        if (!old_config || !is_equal(old_config.radio, config.radio))
                return false;
 
@@ -186,82 +266,231 @@ function iface_reload_config(phy, config, old_config)
        if (!old_config.bss || !old_config.bss[0])
                return false;
 
-       if (config.bss[0].ifname != old_config.bss[0].ifname)
-               return false;
-
-       let iface_name = config.bss[0].ifname;
+       let iface_name = old_config.bss[0].ifname;
        let iface = hostapd.interfaces[iface_name];
-       if (!iface)
+       if (!iface) {
+               hostapd.printf(`Could not find previous interface ${iface_name}`);
                return false;
+       }
 
        let first_bss = hostapd.bss[iface_name];
-       if (!first_bss)
+       if (!first_bss) {
+               hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
                return false;
+       }
 
-       let config_inline = iface_gen_config(phy, config);
+       let macaddr_list = iface_config_macaddr_list(config);
+       let bss_list = [];
+       let bss_list_cfg = [];
+       let prev_bss_hash = [];
 
-       bss_reload_psk(first_bss, config.bss[0], old_config.bss[0]);
-       if (!is_equal(config.bss[0], old_config.bss[0])) {
-               if (phy_is_fullmac(phy))
+       for (let bss in old_config.bss) {
+               let hash = bss_config_hash(bss.data);
+               push(prev_bss_hash, bss_config_hash(bss.data));
+       }
+
+       // Step 1: find (possibly renamed) interfaces with the same config
+       // and store them in the new order (with gaps)
+       for (let i = 0; i < length(config.bss); i++) {
+               let prev;
+
+               // For fullmac devices, the first interface needs to be preserved,
+               // since it's treated as the master
+               if (!i && phy_is_fullmac(phy)) {
+                       prev = 0;
+                       prev_bss_hash[0] = null;
+               } else {
+                       prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
+               }
+               if (prev < 0)
+                       continue;
+
+               let cur_config = config.bss[i];
+               let prev_config = old_config.bss[prev];
+
+               let prev_bss = get_config_bss(old_config, prev);
+               if (!prev_bss)
                        return false;
 
-               if (config.bss[0].bssid != old_config.bss[0].bssid)
+               // try to preserve MAC address of this BSS by reassigning another
+               // BSS if necessary
+               if (cur_config.default_macaddr &&
+                   !macaddr_list[prev_config.bssid]) {
+                       macaddr_list[prev_config.bssid] = i;
+                       cur_config.bssid = prev_config.bssid;
+               }
+
+               bss_list[i] = prev_bss;
+               bss_list_cfg[i] = old_config.bss[prev];
+       }
+
+       if (config.mbssid && !bss_list_cfg[0]) {
+               hostapd.printf("First BSS changed with MBSSID enabled");
+               return false;
+       }
+
+       // Step 2: if none were found, rename and preserve the first one
+       if (length(bss_list) == 0) {
+               // can't change the bssid of the first bss
+               if (config.bss[0].bssid != old_config.bss[0].bssid) {
+                       if (!config.bss[0].default_macaddr) {
+                               hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
+                               return false;
+                       }
+
+                       config.bss[0].bssid = old_config.bss[0].bssid;
+               }
+
+               let prev_bss = get_config_bss(old_config, 0);
+               if (!prev_bss)
                        return false;
 
-               hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
-               if (first_bss.set_config(config_inline, 0) < 0) {
-                       hostapd.printf(`Failed to set config`);
+               macaddr_list[config.bss[0].bssid] = 0;
+               bss_list[0] = prev_bss;
+               bss_list_cfg[0] = old_config.bss[0];
+               prev_bss_hash[0] = null;
+       }
+
+       // Step 3: delete all unused old interfaces
+       for (let i = 0; i < length(prev_bss_hash); i++) {
+               if (!prev_bss_hash[i])
+                       continue;
+
+               let prev_bss = get_config_bss(old_config, i);
+               if (!prev_bss)
                        return false;
-               }
+
+               let ifname = old_config.bss[i].ifname;
+               hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
+               prev_bss.delete();
+               wdev_remove(ifname);
        }
 
-       let new_cfg = array_to_obj(config.bss, "ifname", 1);
-       let old_cfg = array_to_obj(old_config.bss, "ifname", 1);
+       // Step 4: rename preserved interfaces, use temporary name on duplicates
+       let rename_list = [];
+       for (let i = 0; i < length(bss_list); i++) {
+               if (!bss_list[i])
+                       continue;
 
-       for (let name in old_cfg) {
-               let bss = hostapd.bss[name];
-               if (!bss) {
-                       hostapd.printf(`bss '${name}' not found`);
+               let old_ifname = bss_list_cfg[i].ifname;
+               let new_ifname = config.bss[i].ifname;
+               if (old_ifname == new_ifname)
+                       continue;
+
+               if (hostapd.bss[new_ifname]) {
+                       new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
+                       push(rename_list, i);
+               }
+
+               hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
+               if (!bss_list[i].rename(new_ifname)) {
+                       hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
                        return false;
                }
 
-               if (!new_cfg[name]) {
-                       hostapd.printf(`Remove bss '${name}' on phy '${phy}'`);
-                       bss.delete();
-                       wdev_remove(name);
-                       continue;
+               bss_list_cfg[i].ifname = new_ifname;
+       }
+
+       // Step 5: rename interfaces with temporary names
+       for (let i in rename_list) {
+               let new_ifname = config.bss[i].ifname;
+               if (!bss_list[i].rename(new_ifname)) {
+                       hostapd.printf(`Failed to rename bss to ${new_ifname}`);
+                       return false;
                }
+               bss_list_cfg[i].ifname = new_ifname;
+       }
 
-               let new_cfg_data = new_cfg[name];
-               delete new_cfg[name];
+       // Step 6: assign BSSID for newly created interfaces
+       let macaddr_data = {
+               num_global: config.num_global_macaddr ?? 1,
+               mbssid: config.mbssid ?? 0,
+       };
+       macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
+       for (let i = 0; i < length(config.bss); i++) {
+               if (bss_list[i])
+                       continue;
+               let bsscfg = config.bss[i];
 
-               if (is_equal(old_cfg[name], new_cfg_data))
+               let mac_idx = macaddr_list[bsscfg.bssid];
+               if (mac_idx < 0)
+                       macaddr_list[bsscfg.bssid] = i;
+               if (mac_idx == i)
                        continue;
 
-               hostapd.printf(`Reload config for bss '${name}' on phy '${phy}'`);
-               let idx = find_array_idx(config.bss, "ifname", name);
-               if (idx < 0) {
-                       hostapd.printf(`bss index not found`);
-                       return false;
+               // statically assigned bssid of the new interface is in conflict
+               // with the bssid of a reused interface. reassign the reused interface
+               if (!bsscfg.default_macaddr) {
+                       // can't update bssid of the first BSS, need to restart
+                       if (!mac_idx < 0)
+                               return false;
+
+                       bsscfg = config.bss[mac_idx];
                }
 
-               if (bss.set_config(config_inline, idx) < 0) {
-                       hostapd.printf(`Failed to set config`);
+               let addr = phydev.macaddr_next(i);
+               if (!addr) {
+                       hostapd.printf(`Failed to generate mac address for phy ${phy}`);
                        return false;
                }
+               bsscfg.bssid = addr;
        }
 
-       for (let name in new_cfg) {
-               hostapd.printf(`Add bss '${name}' on phy '${phy}'`);
+       let config_inline = iface_gen_config(phy, config);
+
+       // Step 7: fill in the gaps with new interfaces
+       for (let i = 0; i < length(config.bss); i++) {
+               let ifname = config.bss[i].ifname;
+               let bss = bss_list[i];
+
+               if (bss)
+                       continue;
 
-               let idx = find_array_idx(config.bss, "ifname", name);
-               if (idx < 0) {
-                       hostapd.printf(`bss index not found`);
+               hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
+               bss_list[i] = iface.add_bss(config_inline, i);
+               if (!bss_list[i]) {
+                       hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
                        return false;
                }
+       }
+
+       // Step 8: update interface bss order
+       if (!iface.set_bss_order(bss_list)) {
+               hostapd.printf(`Failed to update BSS order on phy '${phy}'`);
+               return false;
+       }
+
+       // Step 9: update config
+       for (let i = 0; i < length(config.bss); i++) {
+               if (!bss_list_cfg[i])
+                       continue;
+
+               let ifname = config.bss[i].ifname;
+               let bss = bss_list[i];
+
+               if (is_equal(config.bss[i], bss_list_cfg[i]))
+                       continue;
+
+               if (is_equal(bss_remove_file_fields(config.bss[i]),
+                            bss_remove_file_fields(bss_list_cfg[i]))) {
+                       hostapd.printf(`Update config data files for bss ${ifname}`);
+                       if (bss.set_config(config_inline, i, true) < 0) {
+                               hostapd.printf(`Failed to update config data files for bss ${ifname}`);
+                               return false;
+                       }
+                       bss.ctrl("RELOAD_WPA_PSK");
+                       continue;
+               }
 
-               if (iface.add_bss(config_inline, idx) < 0) {
-                       hostapd.printf(`Failed to add bss`);
+               bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
+               if (is_equal(config.bss[i], bss_list_cfg[i]))
+                       continue;
+
+               hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
+               hostapd.printf(`old: ${bss_remove_file_fields(bss_list_cfg[i])}`);
+               hostapd.printf(`new: ${bss_remove_file_fields(config.bss[i])}`);
+               if (bss.set_config(config_inline, i) < 0) {
+                       hostapd.printf(`Failed to set config for bss ${ifname}`);
                        return false;
                }
        }
@@ -269,6 +498,14 @@ function iface_reload_config(phy, config, old_config)
        return true;
 }
 
+function iface_update_supplicant_macaddr(phy, config)
+{
+       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 });
+}
+
 function iface_set_config(phy, config)
 {
        let old_config = hostapd.data.config[phy];
@@ -278,14 +515,28 @@ function iface_set_config(phy, config)
        if (!config)
                return iface_remove(old_config);
 
-       let ret = iface_reload_config(phy, config, old_config);
-       if (ret) {
-               hostapd.printf(`Reloaded settings for phy ${phy}`);
-               return 0;
+       let phydev = phy_open(phy);
+       if (!phydev) {
+               hostapd.printf(`Failed to open phy ${phy}`);
+               return false;
+       }
+
+       try {
+               let ret = iface_reload_config(phydev, config, old_config);
+               if (ret) {
+                       iface_update_supplicant_macaddr(phy, config);
+                       hostapd.printf(`Reloaded settings for phy ${phy}`);
+                       return 0;
+               }
+       } catch (e) {
+                       hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
        }
 
        hostapd.printf(`Restart interface for phy ${phy}`);
-       return iface_restart(phy, config, old_config);
+       let ret = iface_restart(phydev, config, old_config);
+       iface_update_supplicant_macaddr(phy, config);
+
+       return ret;
 }
 
 function config_add_bss(config, name)
@@ -332,16 +583,28 @@ function iface_load_config(filename)
                        continue;
                }
 
+               if (val[0] == "#num_global_macaddr" ||
+                   val[0] == "mbssid")
+                       config[val[0]] = int(val[1]);
+
                push(config.radio.data, line);
        }
 
        while ((line = trim(f.read("line"))) != null) {
+               if (line == "#default_macaddr")
+                       bss.default_macaddr = true;
+
                let val = split(line, "=", 2);
                if (!val[0])
                        continue;
 
-               if (val[0] == "bssid")
-                       bss.bssid = val[1];
+               if (val[0] == "bssid") {
+                       bss.bssid = lc(val[1]);
+                       continue;
+               }
+
+               if (val[0] == "nas_identifier")
+                       bss.nasid = val[1];
 
                if (val[0] == "bss") {
                        bss = config_add_bss(config, val[1]);
@@ -358,27 +621,33 @@ function iface_load_config(filename)
        return config;
 }
 
+function ex_wrap(func) {
+       return (req) => {
+               try {
+                       let ret = func(req);
+                       return ret;
+               } catch(e) {
+                       hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
+               }
+               return libubus.STATUS_UNKNOWN_ERROR;
+       };
+}
 
 let main_obj = {
        reload: {
                args: {
                        phy: "",
                },
-               call: function(req) {
-                       try {
-                               let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
-                               for (let phy_name in phy_list) {
-                                       let phy = hostapd.data.config[phy_name];
-                                       let config = iface_load_config(phy.orig_file);
-                                       iface_set_config(phy_name, config);
-                               }
-                       } catch(e) {
-                               hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
-                               return libubus.STATUS_INVALID_ARGUMENT;
+               call: ex_wrap(function(req) {
+                       let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
+                       for (let phy_name in phy_list) {
+                               let phy = hostapd.data.config[phy_name];
+                               let config = iface_load_config(phy.orig_file);
+                               iface_set_config(phy_name, config);
                        }
 
                        return 0;
-               }
+               })
        },
        apsta_state: {
                args: {
@@ -389,7 +658,7 @@ let main_obj = {
                        csa: true,
                        csa_count: 0,
                },
-               call: function(req) {
+               call: ex_wrap(function(req) {
                        if (req.args.up == null || !req.args.phy)
                                return libubus.STATUS_INVALID_ARGUMENT;
 
@@ -426,7 +695,28 @@ let main_obj = {
                                return libubus.STATUS_UNKNOWN_ERROR;
 
                        return 0;
-               }
+               })
+       },
+       config_get_macaddr_list: {
+               args: {
+                       phy: ""
+               },
+               call: ex_wrap(function(req) {
+                       let phy = req.args.phy;
+                       if (!phy)
+                               return libubus.STATUS_INVALID_ARGUMENT;
+
+                       let ret = {
+                               macaddr: [],
+                       };
+
+                       let config = hostapd.data.config[phy];
+                       if (!config)
+                               return ret;
+
+                       ret.macaddr = map(config.bss, (bss) => bss.bssid);
+                       return ret;
+               })
        },
        config_set: {
                args: {
@@ -434,7 +724,7 @@ let main_obj = {
                        config: "",
                        prev_config: "",
                },
-               call: function(req) {
+               call: ex_wrap(function(req) {
                        let phy = req.args.phy;
                        let file = req.args.config;
                        let prev_file = req.args.prev_config;
@@ -442,34 +732,29 @@ let main_obj = {
                        if (!phy)
                                return libubus.STATUS_INVALID_ARGUMENT;
 
-                       try {
-                               if (prev_file && !hostapd.data.config[phy]) {
-                                       let config = iface_load_config(prev_file);
-                                       if (config)
-                                               config.radio.data = [];
-                                       hostapd.data.config[phy] = config;
-                               }
+                       if (prev_file && !hostapd.data.config[phy]) {
+                               let config = iface_load_config(prev_file);
+                               if (config)
+                                       config.radio.data = [];
+                               hostapd.data.config[phy] = config;
+                       }
 
-                               let config = iface_load_config(file);
+                       let config = iface_load_config(file);
 
-                               hostapd.printf(`Set new config for phy ${phy}: ${file}`);
-                               iface_set_config(phy, config);
-                       } catch(e) {
-                               hostapd.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
-                               return libubus.STATUS_INVALID_ARGUMENT;
-                       }
+                       hostapd.printf(`Set new config for phy ${phy}: ${file}`);
+                       iface_set_config(phy, config);
 
                        return {
                                pid: hostapd.getpid()
                        };
-               }
+               })
        },
        config_add: {
                args: {
                        iface: "",
                        config: "",
                },
-               call: function(req) {
+               call: ex_wrap(function(req) {
                        if (!req.args.iface || !req.args.config)
                                return libubus.STATUS_INVALID_ARGUMENT;
 
@@ -479,19 +764,19 @@ let main_obj = {
                        return {
                                pid: hostapd.getpid()
                        };
-               }
+               })
        },
        config_remove: {
                args: {
                        iface: ""
                },
-               call: function(req) {
+               call: ex_wrap(function(req) {
                        if (!req.args.iface)
                                return libubus.STATUS_INVALID_ARGUMENT;
 
                        hostapd.remove_iface(req.args.iface);
                        return 0;
-               }
+               })
        },
 };
 
index 8db245cdb7330bed2f5a5052bc836d8b49a1a0cd..8a031b40b9ee2dde85b6251c42c38c4935057dec 100644 (file)
@@ -1,10 +1,13 @@
 #!/usr/bin/env ucode
 'use strict';
-import { vlist_new, is_equal, wdev_create, wdev_remove, wdev_generate_macaddr } from "/usr/share/hostap/common.uc";
+import { vlist_new, is_equal, wdev_create, wdev_remove, phy_open } from "/usr/share/hostap/common.uc";
 import { readfile, writefile, basename, readlink, glob } from "fs";
+let libubus = require("ubus");
 
 let keep_devices = {};
 let phy = shift(ARGV);
+let command = shift(ARGV);
+let phydev;
 
 const mesh_params = [
        "mesh_retry_timeout", "mesh_confirm_timeout", "mesh_holding_timeout", "mesh_max_peer_links",
@@ -33,6 +36,11 @@ function iface_start(wdev)
                system([ "ip", "link", "set", "dev", ifname, "down" ]);
                wdev_remove(ifname);
        }
+       let wdev_config = {};
+       for (let key in wdev)
+               wdev_config[key] = wdev[key];
+       if (!wdev_config.macaddr && wdev.mode != "monitor")
+               wdev_config.macaddr = phydev.macaddr_next();
        wdev_create(phy, ifname, wdev);
        system([ "ip", "link", "set", "dev", ifname, "up" ]);
        if (wdev.freq)
@@ -154,6 +162,14 @@ const commands = {
                add_ifname(config.data);
                drop_inactive(config.data);
 
+               let ubus = libubus.connect();
+               let data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phy });
+               let macaddr_list = [];
+               if (type(data) == "object" && data.macaddr)
+                       macaddr_list = data.macaddr;
+               ubus.disconnect();
+               phydev.macaddr_init(macaddr_list);
+
                add_ifname(new_config);
                config.update(new_config);
 
@@ -169,7 +185,7 @@ const commands = {
                        data[arg[0]] = arg[1];
                }
 
-               let macaddr = wdev_generate_macaddr(phy, data);
+               let macaddr = phydev.macaddr_generate(data);
                if (!macaddr) {
                        warn(`Could not get MAC address for phy ${phy}\n`);
                        exit(1);
@@ -179,12 +195,11 @@ const commands = {
        },
 };
 
-let command = shift(ARGV);
-
 if (!phy || !command | !commands[command])
        usage();
 
-if (!readfile(`/sys/class/ieee80211/${phy}/index`)) {
+phydev = phy_open(phy);
+if (!phydev) {
        warn(`PHY ${phy} does not exist\n`);
        exit(1);
 }
index f8a3fcb52531c0b87800e36b1cfef0976d6e288b..cb5f41b1af3ac0f18e2028ff83ff9257806d9888 100644 (file)
@@ -1,11 +1,12 @@
 let libubus = require("ubus");
 import { open, readfile } from "fs";
-import { wdev_create, wdev_remove, is_equal, vlist_new } from "common";
+import { wdev_create, wdev_remove, is_equal, vlist_new, phy_open } from "common";
 
 let ubus = libubus.connect();
 
 wpas.data.config = {};
 wpas.data.iface_phy = {};
+wpas.data.macaddr_list = {};
 
 function iface_stop(iface)
 {
@@ -20,16 +21,23 @@ function iface_stop(iface)
        iface.running = false;
 }
 
-function iface_start(phy, iface)
+function iface_start(phydev, iface, macaddr_list)
 {
+       let phy = phydev.name;
+
        if (iface.running)
                return;
 
        let ifname = iface.config.iface;
+       let wdev_config = {};
+       for (let field in iface.config)
+               wdev_config[field] = iface.config[field];
+       if (!wdev_config.macaddr)
+               wdev_config.macaddr = phydev.macaddr_next();
 
        wpas.data.iface_phy[ifname] = phy;
        wdev_remove(ifname);
-       let ret = wdev_create(phy, ifname, iface.config);
+       let ret = wdev_create(phy, ifname, wdev_config);
        if (ret)
                wpas.printf(`Failed to create device ${ifname}: ${ret}`);
        wpas.add_iface(iface.config);
@@ -78,9 +86,22 @@ function set_config(phy_name, config_list)
 function start_pending(phy_name)
 {
        let phy = wpas.data.config[phy_name];
+       let ubus = wpas.data.ubus;
+
+       if (!phy || !phy.data)
+               return;
+
+       let phydev = phy_open(phy_name);
+       if (!phydev) {
+               wpas.printf(`Could not open phy ${phy_name}`);
+               return;
+       }
+
+       let macaddr_list = wpas.data.macaddr_list[phy_name];
+       phydev.macaddr_init(macaddr_list);
 
        for (let ifname in phy.data)
-               iface_start(phy_name, phy.data[ifname]);
+               iface_start(phydev, phy.data[ifname]);
 }
 
 let main_obj = {
@@ -111,6 +132,20 @@ let main_obj = {
                        return 0;
                }
        },
+       phy_set_macaddr_list: {
+               args: {
+                       phy: "",
+                       macaddr: [],
+               },
+               call: function(req) {
+                       let phy = req.args.phy;
+                       if (!phy)
+                               return libubus.STATUS_INVALID_ARGUMENT;
+
+                       wpas.data.macaddr_list[phy] = req.args.macaddr;
+                       return 0;
+               }
+       },
        phy_status: {
                args: {
                        phy: ""