hostapd: rework reload support and MAC address handling

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>
This commit is contained in:
Felix Fietkau 2023-09-09 17:07:09 +02:00
parent 651cb1422e
commit 20c667cc88
6 changed files with 586 additions and 178 deletions

View file

@ -489,6 +489,7 @@ ${channel_list:+chanlist=$channel_list}
${hostapd_noscan:+noscan=1} ${hostapd_noscan:+noscan=1}
${tx_burst:+tx_queue_data2_burst=$tx_burst} ${tx_burst:+tx_queue_data2_burst=$tx_burst}
mbssid=$multiple_bssid mbssid=$multiple_bssid
#num_global_macaddr=$num_global_macaddr
$base_cfg $base_cfg
EOF EOF
@ -519,6 +520,7 @@ mac80211_hostapd_setup_bss() {
cat >> /var/run/hostapd-$phy.conf <<EOF cat >> /var/run/hostapd-$phy.conf <<EOF
$hostapd_cfg $hostapd_cfg
bssid=$macaddr bssid=$macaddr
${default_macaddr:+#default_macaddr}
${dtim_period:+dtim_period=$dtim_period} ${dtim_period:+dtim_period=$dtim_period}
${max_listen_int:+max_listen_interval=$max_listen_int} ${max_listen_int:+max_listen_interval=$max_listen_int}
EOF EOF
@ -646,13 +648,16 @@ mac80211_prepare_vif() {
set_default powersave 0 set_default powersave 0
json_add_string _ifname "$ifname" json_add_string _ifname "$ifname"
default_macaddr=
if [ -z "$macaddr" ]; then if [ -z "$macaddr" ]; then
macaddr="$(mac80211_generate_mac $phy)" macaddr="$(mac80211_generate_mac $phy)"
macidx="$(($macidx + 1))" macidx="$(($macidx + 1))"
default_macaddr=1
elif [ "$macaddr" = 'random' ]; then elif [ "$macaddr" = 'random' ]; then
macaddr="$(macaddr_random)" macaddr="$(macaddr_random)"
fi fi
json_add_string _macaddr "$macaddr" json_add_string _macaddr "$macaddr"
json_add_string _default_macaddr "$default_macaddr"
json_select .. json_select ..
@ -776,7 +781,7 @@ mac80211_setup_adhoc() {
json_add_object "$ifname" json_add_object "$ifname"
json_add_string mode adhoc 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 ssid "$ssid"
json_add_string freq "$freq" json_add_string freq "$freq"
json_add_string htmode "$iw_htmode" json_add_string htmode "$iw_htmode"
@ -802,7 +807,7 @@ mac80211_setup_mesh() {
json_add_object "$ifname" json_add_object "$ifname"
json_add_string mode mesh 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 ssid "$ssid"
json_add_string freq "$freq" json_add_string freq "$freq"
json_add_string htmode "$iw_htmode" json_add_string htmode "$iw_htmode"
@ -862,7 +867,7 @@ wpa_supplicant_add_interface() {
json_add_string iface "$ifname" json_add_string iface "$ifname"
json_add_string mode "$mode" json_add_string mode "$mode"
json_add_string config "$_config" 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 "$network_bridge" ] && json_add_string bridge "$network_bridge"
[ -n "$wds" ] && json_add_boolean 4addr "$wds" [ -n "$wds" ] && json_add_boolean 4addr "$wds"
json_add_boolean powersave "$powersave" json_add_boolean powersave "$powersave"
@ -950,6 +955,7 @@ mac80211_setup_vif() {
json_select config json_select config
json_get_var ifname _ifname json_get_var ifname _ifname
json_get_var macaddr _macaddr json_get_var macaddr _macaddr
json_get_var default_macaddr _default_macaddr
json_get_vars mode wds powersave json_get_vars mode wds powersave
set_default powersave 0 set_default powersave 0

View file

@ -1,6 +1,6 @@
import * as nl80211 from "nl80211"; import * as nl80211 from "nl80211";
import * as rtnl from "rtnl"; import * as rtnl from "rtnl";
import { readfile } from "fs"; import { readfile, glob, basename, readlink } from "fs";
const iftypes = { const iftypes = {
ap: nl80211.const.NL80211_IFTYPE_AP, ap: nl80211.const.NL80211_IFTYPE_AP,
@ -109,8 +109,33 @@ function macaddr_join(addr)
return join(":", map(addr, (val) => sprintf("%02x", val))); return join(":", map(addr, (val) => sprintf("%02x", val)));
} }
function wdev_generate_macaddr(phy, data) function wdev_macaddr(wdev)
{ {
return trim(readfile(`/sys/class/net/${wdev}/address`));
}
const phy_proto = {
macaddr_init: function(used, options) {
this.macaddr_options = options ?? {};
this.macaddr_list = {};
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;
this.for_each_wdev((wdev) => {
let macaddr = wdev_macaddr(wdev);
this.macaddr_list[macaddr] ??= -1;
});
return this.macaddr_list;
},
macaddr_generate: function(data) {
let phy = this.name;
let idx = int(data.id ?? 0); let idx = int(data.id ?? 0);
let mbssid = int(data.mbssid ?? 0) > 0; let mbssid = int(data.mbssid ?? 0) > 0;
let num_global = int(data.num_global ?? 1); let num_global = int(data.num_global ?? 1);
@ -174,6 +199,49 @@ function wdev_generate_macaddr(phy, data)
} }
return macaddr_join(addr); 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);
}
}
};
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 = { const vlist_proto = {
@ -247,4 +315,4 @@ function vlist_new(cb) {
}, vlist_proto); }, 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 };

View file

@ -632,8 +632,7 @@ hostapd_set_bss_options() {
[ -n "$wpa_strict_rekey" ] && append bss_conf "wpa_strict_rekey=$wpa_strict_rekey" "$N" [ -n "$wpa_strict_rekey" ] && append bss_conf "wpa_strict_rekey=$wpa_strict_rekey" "$N"
} }
set_default nasid "${macaddr//\:}" [ -n "$nasid" ] && append bss_conf "nas_identifier=$nasid" "$N"
append bss_conf "nas_identifier=$nasid" "$N"
[ -n "$acct_interval" ] && \ [ -n "$acct_interval" ] && \
append bss_conf "radius_acct_interim_interval=$acct_interval" "$N" append bss_conf "radius_acct_interim_interval=$acct_interval" "$N"

View file

@ -1,6 +1,6 @@
let libubus = require("ubus"); let libubus = require("ubus");
import { open, readfile } from "fs"; 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(); let ubus = libubus.connect();
@ -41,10 +41,13 @@ channel=${config.radio.channel}
for (let i = 0; i < length(config.bss); i++) { for (let i = 0; i < length(config.bss); i++) {
let bss = config.bss[i]; let bss = config.bss[i];
let type = i > 0 ? "bss" : "interface"; let type = i > 0 ? "bss" : "interface";
let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
str += ` str += `
${type}=${bss.ifname} ${type}=${bss.ifname}
bssid=${bss.bssid}
${join("\n", bss.data)} ${join("\n", bss.data)}
nas_identifier=${nasid}
`; `;
if (start_disabled) if (start_disabled)
str += ` str += `
@ -108,8 +111,22 @@ function iface_add(phy, config, phy_status)
return iface.start(freq_info) >= 0; 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(old_config);
iface_remove(config); iface_remove(config);
@ -118,6 +135,13 @@ function iface_restart(phy, config, old_config)
return; 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 bss = config.bss[0];
let err = wdev_create(phy, bss.ifname, { mode: "ap" }); let err = wdev_create(phy, bss.ifname, { mode: "ap" });
if (err) 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}`); 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)) if (!old_config || !is_equal(old_config.radio, config.radio))
return false; return false;
@ -186,82 +266,231 @@ function iface_reload_config(phy, config, old_config)
if (!old_config.bss || !old_config.bss[0]) if (!old_config.bss || !old_config.bss[0])
return false; return false;
if (config.bss[0].ifname != old_config.bss[0].ifname) let iface_name = old_config.bss[0].ifname;
return false;
let iface_name = config.bss[0].ifname;
let iface = hostapd.interfaces[iface_name]; let iface = hostapd.interfaces[iface_name];
if (!iface) if (!iface) {
hostapd.printf(`Could not find previous interface ${iface_name}`);
return false; return false;
}
let first_bss = hostapd.bss[iface_name]; 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; return false;
}
let macaddr_list = iface_config_macaddr_list(config);
let bss_list = [];
let bss_list_cfg = [];
let prev_bss_hash = [];
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;
// 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;
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);
}
// 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;
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;
}
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;
}
// 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];
let mac_idx = macaddr_list[bsscfg.bssid];
if (mac_idx < 0)
macaddr_list[bsscfg.bssid] = i;
if (mac_idx == i)
continue;
// 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];
}
let addr = phydev.macaddr_next(i);
if (!addr) {
hostapd.printf(`Failed to generate mac address for phy ${phy}`);
return false;
}
bsscfg.bssid = addr;
}
let config_inline = iface_gen_config(phy, config); let config_inline = iface_gen_config(phy, config);
bss_reload_psk(first_bss, config.bss[0], old_config.bss[0]); // Step 7: fill in the gaps with new interfaces
if (!is_equal(config.bss[0], old_config.bss[0])) { for (let i = 0; i < length(config.bss); i++) {
if (phy_is_fullmac(phy)) let ifname = config.bss[i].ifname;
return false; let bss = bss_list[i];
if (config.bss[0].bssid != old_config.bss[0].bssid) if (bss)
continue;
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; 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;
}
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(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
if (first_bss.set_config(config_inline, 0) < 0) { hostapd.printf(`old: ${bss_remove_file_fields(bss_list_cfg[i])}`);
hostapd.printf(`Failed to set config`); hostapd.printf(`new: ${bss_remove_file_fields(config.bss[i])}`);
return false; if (bss.set_config(config_inline, i) < 0) {
} hostapd.printf(`Failed to set config for bss ${ifname}`);
}
let new_cfg = array_to_obj(config.bss, "ifname", 1);
let old_cfg = array_to_obj(old_config.bss, "ifname", 1);
for (let name in old_cfg) {
let bss = hostapd.bss[name];
if (!bss) {
hostapd.printf(`bss '${name}' not found`);
return false;
}
if (!new_cfg[name]) {
hostapd.printf(`Remove bss '${name}' on phy '${phy}'`);
bss.delete();
wdev_remove(name);
continue;
}
let new_cfg_data = new_cfg[name];
delete new_cfg[name];
if (is_equal(old_cfg[name], new_cfg_data))
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;
}
if (bss.set_config(config_inline, idx) < 0) {
hostapd.printf(`Failed to set config`);
return false;
}
}
for (let name in new_cfg) {
hostapd.printf(`Add 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;
}
if (iface.add_bss(config_inline, idx) < 0) {
hostapd.printf(`Failed to add bss`);
return false; return false;
} }
} }
@ -269,6 +498,14 @@ function iface_reload_config(phy, config, old_config)
return true; 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) function iface_set_config(phy, config)
{ {
let old_config = hostapd.data.config[phy]; let old_config = hostapd.data.config[phy];
@ -278,14 +515,28 @@ function iface_set_config(phy, config)
if (!config) if (!config)
return iface_remove(old_config); return iface_remove(old_config);
let ret = iface_reload_config(phy, config, old_config); 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) { if (ret) {
iface_update_supplicant_macaddr(phy, config);
hostapd.printf(`Reloaded settings for phy ${phy}`); hostapd.printf(`Reloaded settings for phy ${phy}`);
return 0; return 0;
} }
} catch (e) {
hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
}
hostapd.printf(`Restart interface for phy ${phy}`); 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) function config_add_bss(config, name)
@ -332,16 +583,28 @@ function iface_load_config(filename)
continue; continue;
} }
if (val[0] == "#num_global_macaddr" ||
val[0] == "mbssid")
config[val[0]] = int(val[1]);
push(config.radio.data, line); push(config.radio.data, line);
} }
while ((line = trim(f.read("line"))) != null) { while ((line = trim(f.read("line"))) != null) {
if (line == "#default_macaddr")
bss.default_macaddr = true;
let val = split(line, "=", 2); let val = split(line, "=", 2);
if (!val[0]) if (!val[0])
continue; continue;
if (val[0] == "bssid") if (val[0] == "bssid") {
bss.bssid = val[1]; bss.bssid = lc(val[1]);
continue;
}
if (val[0] == "nas_identifier")
bss.nasid = val[1];
if (val[0] == "bss") { if (val[0] == "bss") {
bss = config_add_bss(config, val[1]); bss = config_add_bss(config, val[1]);
@ -358,27 +621,33 @@ function iface_load_config(filename)
return config; 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 = { let main_obj = {
reload: { reload: {
args: { args: {
phy: "", phy: "",
}, },
call: function(req) { call: ex_wrap(function(req) {
try {
let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config); let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
for (let phy_name in phy_list) { for (let phy_name in phy_list) {
let phy = hostapd.data.config[phy_name]; let phy = hostapd.data.config[phy_name];
let config = iface_load_config(phy.orig_file); let config = iface_load_config(phy.orig_file);
iface_set_config(phy_name, config); iface_set_config(phy_name, config);
} }
} catch(e) {
hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
return libubus.STATUS_INVALID_ARGUMENT;
}
return 0; return 0;
} })
}, },
apsta_state: { apsta_state: {
args: { args: {
@ -389,7 +658,7 @@ let main_obj = {
csa: true, csa: true,
csa_count: 0, csa_count: 0,
}, },
call: function(req) { call: ex_wrap(function(req) {
if (req.args.up == null || !req.args.phy) if (req.args.up == null || !req.args.phy)
return libubus.STATUS_INVALID_ARGUMENT; return libubus.STATUS_INVALID_ARGUMENT;
@ -426,7 +695,28 @@ let main_obj = {
return libubus.STATUS_UNKNOWN_ERROR; return libubus.STATUS_UNKNOWN_ERROR;
return 0; 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: { config_set: {
args: { args: {
@ -434,7 +724,7 @@ let main_obj = {
config: "", config: "",
prev_config: "", prev_config: "",
}, },
call: function(req) { call: ex_wrap(function(req) {
let phy = req.args.phy; let phy = req.args.phy;
let file = req.args.config; let file = req.args.config;
let prev_file = req.args.prev_config; let prev_file = req.args.prev_config;
@ -442,7 +732,6 @@ let main_obj = {
if (!phy) if (!phy)
return libubus.STATUS_INVALID_ARGUMENT; return libubus.STATUS_INVALID_ARGUMENT;
try {
if (prev_file && !hostapd.data.config[phy]) { if (prev_file && !hostapd.data.config[phy]) {
let config = iface_load_config(prev_file); let config = iface_load_config(prev_file);
if (config) if (config)
@ -454,22 +743,18 @@ let main_obj = {
hostapd.printf(`Set new config for phy ${phy}: ${file}`); hostapd.printf(`Set new config for phy ${phy}: ${file}`);
iface_set_config(phy, config); iface_set_config(phy, config);
} catch(e) {
hostapd.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
return libubus.STATUS_INVALID_ARGUMENT;
}
return { return {
pid: hostapd.getpid() pid: hostapd.getpid()
}; };
} })
}, },
config_add: { config_add: {
args: { args: {
iface: "", iface: "",
config: "", config: "",
}, },
call: function(req) { call: ex_wrap(function(req) {
if (!req.args.iface || !req.args.config) if (!req.args.iface || !req.args.config)
return libubus.STATUS_INVALID_ARGUMENT; return libubus.STATUS_INVALID_ARGUMENT;
@ -479,19 +764,19 @@ let main_obj = {
return { return {
pid: hostapd.getpid() pid: hostapd.getpid()
}; };
} })
}, },
config_remove: { config_remove: {
args: { args: {
iface: "" iface: ""
}, },
call: function(req) { call: ex_wrap(function(req) {
if (!req.args.iface) if (!req.args.iface)
return libubus.STATUS_INVALID_ARGUMENT; return libubus.STATUS_INVALID_ARGUMENT;
hostapd.remove_iface(req.args.iface); hostapd.remove_iface(req.args.iface);
return 0; return 0;
} })
}, },
}; };

View file

@ -1,10 +1,13 @@
#!/usr/bin/env ucode #!/usr/bin/env ucode
'use strict'; '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"; import { readfile, writefile, basename, readlink, glob } from "fs";
let libubus = require("ubus");
let keep_devices = {}; let keep_devices = {};
let phy = shift(ARGV); let phy = shift(ARGV);
let command = shift(ARGV);
let phydev;
const mesh_params = [ const mesh_params = [
"mesh_retry_timeout", "mesh_confirm_timeout", "mesh_holding_timeout", "mesh_max_peer_links", "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" ]); system([ "ip", "link", "set", "dev", ifname, "down" ]);
wdev_remove(ifname); 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); wdev_create(phy, ifname, wdev);
system([ "ip", "link", "set", "dev", ifname, "up" ]); system([ "ip", "link", "set", "dev", ifname, "up" ]);
if (wdev.freq) if (wdev.freq)
@ -154,6 +162,14 @@ const commands = {
add_ifname(config.data); add_ifname(config.data);
drop_inactive(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); add_ifname(new_config);
config.update(new_config); config.update(new_config);
@ -169,7 +185,7 @@ const commands = {
data[arg[0]] = arg[1]; data[arg[0]] = arg[1];
} }
let macaddr = wdev_generate_macaddr(phy, data); let macaddr = phydev.macaddr_generate(data);
if (!macaddr) { if (!macaddr) {
warn(`Could not get MAC address for phy ${phy}\n`); warn(`Could not get MAC address for phy ${phy}\n`);
exit(1); exit(1);
@ -179,12 +195,11 @@ const commands = {
}, },
}; };
let command = shift(ARGV);
if (!phy || !command | !commands[command]) if (!phy || !command | !commands[command])
usage(); usage();
if (!readfile(`/sys/class/ieee80211/${phy}/index`)) { phydev = phy_open(phy);
if (!phydev) {
warn(`PHY ${phy} does not exist\n`); warn(`PHY ${phy} does not exist\n`);
exit(1); exit(1);
} }

View file

@ -1,11 +1,12 @@
let libubus = require("ubus"); let libubus = require("ubus");
import { open, readfile } from "fs"; 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(); let ubus = libubus.connect();
wpas.data.config = {}; wpas.data.config = {};
wpas.data.iface_phy = {}; wpas.data.iface_phy = {};
wpas.data.macaddr_list = {};
function iface_stop(iface) function iface_stop(iface)
{ {
@ -20,16 +21,23 @@ function iface_stop(iface)
iface.running = false; iface.running = false;
} }
function iface_start(phy, iface) function iface_start(phydev, iface, macaddr_list)
{ {
let phy = phydev.name;
if (iface.running) if (iface.running)
return; return;
let ifname = iface.config.iface; 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; wpas.data.iface_phy[ifname] = phy;
wdev_remove(ifname); wdev_remove(ifname);
let ret = wdev_create(phy, ifname, iface.config); let ret = wdev_create(phy, ifname, wdev_config);
if (ret) if (ret)
wpas.printf(`Failed to create device ${ifname}: ${ret}`); wpas.printf(`Failed to create device ${ifname}: ${ret}`);
wpas.add_iface(iface.config); wpas.add_iface(iface.config);
@ -78,9 +86,22 @@ function set_config(phy_name, config_list)
function start_pending(phy_name) function start_pending(phy_name)
{ {
let phy = wpas.data.config[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) for (let ifname in phy.data)
iface_start(phy_name, phy.data[ifname]); iface_start(phydev, phy.data[ifname]);
} }
let main_obj = { let main_obj = {
@ -111,6 +132,20 @@ let main_obj = {
return 0; 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: { phy_status: {
args: { args: {
phy: "" phy: ""