openwrt/package/network/services/hostapd/files/hostapd.uc
Sybil127 7306ae401c hostapd: add support for rxkh_file
Initial support for dynamic reload of RxKHs.

In order to check if RxKHs need reloading.
RxKHs defined in the rxkh_file first has to be parsed and formated,
the same way as hostapd will read from the file and also output,
with the command GET_RXKHS.
Then each list of RxKHs can be hashed and compared.

Ucode implementation of hostapds rkh_derive_key() function.
Hostapd converts hex keys with 128-bits or more when less than 256-bits
to 256-bits, and truncates those that are more than 256-bits.
See: https://w1.fi/cgit/hostap/commit/hostapd/config_file.c?id=245fc96e5f4b1c566b7eaa19180c774307ebed79

Signed-off-by: Sybil127 <sybil127@outlook.com>
Signed-off-by: Felix Fietkau <nbd@nbd.name>
2024-12-12 19:19:33 +01:00

1135 lines
25 KiB
Ucode

let libubus = require("ubus");
import { open, readfile } from "fs";
import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask } from "common";
let ubus = libubus.connect(null, 60);
hostapd.data.config = {};
hostapd.data.pending_config = {};
hostapd.data.file_fields = {
vlan_file: true,
wpa_psk_file: true,
sae_password_file: true,
rxkh_file: true,
accept_mac_file: true,
deny_mac_file: true,
eap_user_file: true,
ca_cert: true,
server_cert: true,
server_cert2: true,
private_key: true,
private_key2: true,
dh_file: true,
eap_sim_db: true,
};
hostapd.data.iface_fields = {
ft_iface: true,
upnp_iface: true,
snoop_iface: true,
bridge: true,
iapp_interface: true,
};
hostapd.data.bss_info_fields = {
// radio
hw_mode: true,
channel: true,
ieee80211ac: true,
ieee80211ax: true,
// bss
bssid: true,
ssid: true,
wpa: true,
wpa_key_mgmt: true,
wpa_pairwise: true,
auth_algs: true,
ieee80211w: true,
owe_transition_ifname: true,
};
function iface_remove(cfg)
{
if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
return;
for (let bss in cfg.bss)
wdev_remove(bss.ifname);
}
function iface_gen_config(config, start_disabled)
{
let str = `data:
${join("\n", config.radio.data)}
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 += `
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(config, !!phy_status);
let bss = config.bss[0];
let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
if (ret < 0)
return false;
if (!phy_status)
return true;
let iface = hostapd.interfaces[phy];
if (!iface)
return false;
let freq_info = iface_freq_info(iface, config, phy_status);
return iface.start(freq_info) >= 0;
}
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_update_supplicant_macaddr(phydev, config)
{
let macaddr_list = [];
for (let i = 0; i < length(config.bss); i++)
push(macaddr_list, config.bss[i].bssid);
ubus.defer("wpa_supplicant", "phy_set_macaddr_list", {
phy: phydev.name,
radio: phydev.radio ?? -1,
macaddr: macaddr_list
});
}
function __iface_pending_next(pending, state, ret, data)
{
let config = pending.config;
let phydev = pending.phydev;
let phy = pending.phy;
let bss = config.bss[0];
if (pending.defer)
pending.defer.abort();
delete pending.defer;
switch (state) {
case "init":
iface_update_supplicant_macaddr(phydev, config);
return "create_bss";
case "create_bss":
let err = phydev.wdev_add(bss.ifname, {
mode: "ap",
radio: phydev.radio,
});
if (err) {
hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
return null;
}
pending.call("wpa_supplicant", "phy_status", {
phy: phydev.phy,
radio: phydev.radio,
});
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: phydev.phy,
radio: phydev.radio,
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: phydev.phy,
radio: phydev.radio,
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) {
try {
this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
if (!this.next_state) {
__iface_pending_next(cfg, "done");
return;
}
} catch(e) {
hostapd.printf(`Exception: ${e}\n${e.stacktrace[0].context}`);
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_macaddr_init(phydev, config, macaddr_list)
{
let macaddr_data = {
num_global: config.num_global_macaddr ?? 1,
macaddr_base: config.macaddr_base,
mbssid: config.mbssid ?? 0,
};
return phydev.macaddr_init(macaddr_list, macaddr_data);
}
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);
iface_remove(config);
if (!config.bss || !config.bss[0]) {
hostapd.printf(`No bss for phy ${phy}`);
return;
}
iface_macaddr_init(phydev, config, 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();
}
iface_pending_init(phydev, config);
}
function array_to_obj(arr, key, start)
{
let obj = {};
start ??= 0;
for (let i = start; i < length(arr); i++) {
let cur = arr[i];
obj[cur[key]] = cur;
}
return obj;
}
function find_array_idx(arr, key, val)
{
for (let i = 0; i < length(arr); i++)
if (arr[i][key] == val)
return i;
return -1;
}
function bss_reload_psk(bss, config, old_config)
{
if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
return;
old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
if (!is_equal(old_config, config))
return;
let ret = bss.ctrl("RELOAD_WPA_PSK");
ret ??= "failed";
hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
}
function normalize_rxkhs(txt)
{
const pat = {
sep: "\x20",
mac: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}",
r0kh_id: "[\x21-\x7e]{1,48}",
r1kh_id: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}",
key: "[[:xdigit:]]{32,}",
r0kh: function() {
return "r0kh=" + this.mac + this.sep + this.r0kh_id;
},
r1kh: function() {
return "r1kh=" + this.mac + this.sep + this.r1kh_id;
},
rxkh: function() {
return "(" + this.r0kh() + "|" + this.r1kh() + ")" + this.sep + this.key;
},
};
let rxkhs = filter(
split(txt, "\n"), (line) => match(line, regexp("^" + pat.rxkh() + "$"))
) ?? [];
rxkhs = map(rxkhs, function(k) {
k = split(k, " ", 3);
k[0] = lc(k[0]);
if(match(k[0], /^r1kh/)) {
k[1] = lc(k[1]);
}
if(!k[2] = hostapd.rkh_derive_key(k[2])) {
return;
}
return join(" ", k);
});
return join("\n", sort(filter(rxkhs, length)));
}
function bss_reload_rxkhs(bss, config, old_config)
{
let bss_rxkhs = join("\n", sort(split(bss.ctrl("GET_RXKHS"), "\n")));
let bss_rxkhs_hash = hostapd.sha1(bss_rxkhs);
if (is_equal(config.hash.rxkh_file, bss_rxkhs_hash)) {
if (is_equal(old_config.hash.rxkh_file, config.hash.rxkh_file))
return;
}
old_config.hash.rxkh_file = config.hash.rxkh_file;
if (!is_equal(old_config, config))
return;
let ret = bss.ctrl("RELOAD_RXKHS");
ret ??= "failed";
hostapd.printf(`Reload RxKH file for bss ${config.ifname}: ${ret}`);
}
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;
delete new_cfg.hash.sae_password_file;
delete new_cfg.hash.vlan_file;
return new_cfg;
}
function bss_ifindex_list(config)
{
config = filter(config, (line) => !!hostapd.data.iface_fields[split(line, "=")[0]]);
return join(",", map(config, (line) => {
try {
let file = "/sys/class/net/" + split(line, "=")[1] + "/ifindex";
let val = trim(readfile(file));
return val;
} catch (e) {
return "";
}
}));
}
function bss_config_hash(config)
{
return hostapd.sha1(remove_file_fields(config) + bss_ifindex_list(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(name, phydev, config, old_config)
{
let phy = phydev.name;
if (!old_config || !is_equal(old_config.radio, config.radio))
return false;
if (is_equal(old_config.bss, config.bss))
return true;
if (hostapd.data.pending_config[name])
return false;
if (!old_config.bss || !old_config.bss[0])
return false;
let iface = hostapd.interfaces[name];
let iface_name = old_config.bss[0].ifname;
if (!iface) {
hostapd.printf(`Could not find previous interface ${iface_name}`);
return false;
}
let first_bss = hostapd.bss[iface_name];
if (!first_bss) {
hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
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 '${name}'`);
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
macaddr_list = iface_macaddr_init(phydev, config, macaddr_list);
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 ${name}`);
return false;
}
bsscfg.bssid = addr;
}
let config_inline = iface_gen_config(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;
hostapd.printf(`Add bss ${ifname} on phy ${name}`);
bss_list[i] = iface.add_bss(config_inline, i);
if (!bss_list[i]) {
hostapd.printf(`Failed to add new bss ${ifname} on phy ${name}`);
return false;
}
}
// Step 8: update interface bss order
if (!iface.set_bss_order(bss_list)) {
hostapd.printf(`Failed to update BSS order on phy '${name}'`);
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(`Could not update config data files for bss ${ifname}`);
return false;
} else {
bss.ctrl("RELOAD_WPA_PSK");
continue;
}
}
bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
bss_reload_rxkhs(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 '${name}'`);
if (bss.set_config(config_inline, i) < 0) {
hostapd.printf(`Failed to set config for bss ${ifname}`);
return false;
}
}
return true;
}
function iface_set_config(name, config)
{
let old_config = hostapd.data.config[name];
hostapd.data.config[name] = config;
if (!config) {
hostapd.remove_iface(name);
return iface_remove(old_config);
}
let phy = config.phy;
let phydev = phy_open(phy, config.radio_idx);
if (!phydev) {
hostapd.printf(`Failed to open phy ${phy}`);
return false;
}
try {
let ret = iface_reload_config(name, phydev, config, old_config);
if (ret) {
iface_update_supplicant_macaddr(phydev, config);
hostapd.printf(`Reloaded settings for phy ${name}`);
return 0;
}
} catch (e) {
hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
}
hostapd.printf(`Restart interface for phy ${name}`);
let ret = iface_restart(phydev, config, old_config);
return ret;
}
function config_add_bss(config, name)
{
let bss = {
ifname: name,
data: [],
hash: {}
};
push(config.bss, bss);
return bss;
}
function iface_load_config(phy, radio, filename)
{
let f = open(filename, "r");
if (!f)
return null;
if (radio < 0)
radio = null;
let config = {
phy,
radio_idx: radio,
radio: {
data: []
},
bss: [],
orig_file: filename,
};
let bss;
let line;
while ((line = rtrim(f.read("line"), "\n")) != null) {
let val = split(line, "=", 2);
if (!val[0])
continue;
if (val[0] == "interface") {
bss = config_add_bss(config, val[1]);
break;
}
if (val[0] == "channel") {
config.radio.channel = val[1];
continue;
}
if (val[0] == "#num_global_macaddr")
config[substr(val[0], 1)] = int(val[1]);
else if (val[0] == "#macaddr_base")
config[substr(val[0], 1)] = val[1];
else if (val[0] == "mbssid")
config[val[0]] = int(val[1]);
push(config.radio.data, line);
}
while ((line = rtrim(f.read("line"), "\n")) != null) {
if (line == "#default_macaddr")
bss.default_macaddr = true;
let val = split(line, "=", 2);
if (!val[0])
continue;
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]);
continue;
}
if (hostapd.data.file_fields[val[0]]) {
if (val[0] == "rxkh_file") {
bss.hash[val[0]] = hostapd.sha1(normalize_rxkhs(readfile(val[1])));
} else {
bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
}
}
push(bss.data, line);
}
f.close();
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;
};
}
function phy_name(phy, radio)
{
if (!phy)
return null;
if (radio != null && radio >= 0)
phy += "." + radio;
return phy;
}
function bss_config(bss_name) {
for (let phy, config in hostapd.data.config) {
if (!config)
continue;
for (let bss in config.bss)
if (bss.ifname == bss_name)
return [ config, bss ];
}
}
let main_obj = {
reload: {
args: {
phy: "",
radio: 0,
},
call: ex_wrap(function(req) {
let phy_list = req.args.phy ? [ phy_name(req.args.phy, req.args.radio) ] : keys(hostapd.data.config);
for (let phy_name in phy_list) {
let phy = hostapd.data.config[phy_name];
let config = iface_load_config(phy.phy, radio, phy.orig_file);
iface_set_config(phy_name, config);
}
return 0;
})
},
apsta_state: {
args: {
phy: "",
radio: 0,
up: true,
frequency: 0,
sec_chan_offset: 0,
csa: true,
csa_count: 0,
},
call: ex_wrap(function(req) {
let phy = phy_name(req.args.phy, req.args.radio);
if (req.args.up == null || !phy)
return libubus.STATUS_INVALID_ARGUMENT;
let config = hostapd.data.config[phy];
if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
return 0;
let iface = hostapd.interfaces[phy];
if (!iface)
return 0;
if (!req.args.up) {
iface.stop();
return 0;
}
if (!req.args.frequency)
return libubus.STATUS_INVALID_ARGUMENT;
let freq_info = iface_freq_info(iface, config, req.args);
if (!freq_info)
return libubus.STATUS_UNKNOWN_ERROR;
let ret;
if (req.args.csa) {
freq_info.csa_count = req.args.csa_count ?? 10;
ret = iface.switch_channel(freq_info);
} else {
ret = iface.start(freq_info);
}
if (!ret)
return libubus.STATUS_UNKNOWN_ERROR;
return 0;
})
},
config_get_macaddr_list: {
args: {
phy: "",
radio: 0,
},
call: ex_wrap(function(req) {
let phy = phy_name(req.args.phy, req.args.radio);
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: {
phy: "",
radio: 0,
config: "",
prev_config: "",
},
call: ex_wrap(function(req) {
let phy = req.args.phy;
let radio = req.args.radio;
let name = phy_name(phy, radio);
let file = req.args.config;
let prev_file = req.args.prev_config;
if (!phy)
return libubus.STATUS_INVALID_ARGUMENT;
if (prev_file && !hostapd.data.config[name]) {
let config = iface_load_config(phy, radio, prev_file);
if (config)
config.radio.data = [];
hostapd.data.config[name] = config;
}
let config = iface_load_config(phy, radio, file);
hostapd.printf(`Set new config for phy ${name}: ${file}`);
iface_set_config(name, config);
if (hostapd.data.auth_obj)
hostapd.data.auth_obj.notify("reload", { phy, radio });
return {
pid: hostapd.getpid()
};
})
},
config_add: {
args: {
iface: "",
config: "",
},
call: ex_wrap(function(req) {
if (!req.args.iface || !req.args.config)
return libubus.STATUS_INVALID_ARGUMENT;
if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
return libubus.STATUS_INVALID_ARGUMENT;
return {
pid: hostapd.getpid()
};
})
},
config_remove: {
args: {
iface: ""
},
call: ex_wrap(function(req) {
if (!req.args.iface)
return libubus.STATUS_INVALID_ARGUMENT;
hostapd.remove_iface(req.args.iface);
return 0;
})
},
bss_info: {
args: {
iface: ""
},
call: ex_wrap(function(req) {
if (!req.args.iface)
return libubus.STATUS_INVALID_ARGUMENT;
let config = bss_config(req.args.iface);
if (!config)
return libubus.STATUS_NOT_FOUND;
let bss = config[1];
config = config[0];
let ret = {};
for (let line in [ ...config.radio.data, ...bss.data ]) {
let fields = split(line, "=", 2);
let name = fields[0];
if (hostapd.data.bss_info_fields[name])
ret[name] = fields[1];
}
return ret;
})
},
};
hostapd.data.ubus = ubus;
hostapd.data.obj = ubus.publish("hostapd", main_obj);
let auth_obj = {};
hostapd.data.auth_obj = ubus.publish("hostapd-auth", auth_obj);
hostapd.udebug_set("hostapd", hostapd.data.ubus);
function bss_event(type, name, data) {
let ubus = hostapd.data.ubus;
data ??= {};
data.name = name;
hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
}
return {
shutdown: function() {
for (let phy in hostapd.data.config)
iface_set_config(phy);
hostapd.udebug_set(null);
hostapd.ubus.disconnect();
},
bss_create: function(phy, name, obj) {
phy = hostapd.data.config[phy];
if (!phy)
return;
if (phy.radio_idx != null && phy.radio_idx >= 0)
wdev_set_radio_mask(name, 1 << phy.radio_idx);
},
bss_add: function(phy, name, obj) {
bss_event("add", name);
},
bss_reload: function(phy, name, obj, reconf) {
bss_event("reload", name, { reconf: reconf != 0 });
},
bss_remove: function(phy, name, obj) {
bss_event("remove", name);
},
sta_auth: function(iface, sta) {
let msg = { iface, sta };
let ret = {};
let data_cb = (type, data) => {
ret = { ...ret, ...data };
};
if (hostapd.data.auth_obj)
hostapd.data.auth_obj.notify("sta_auth", msg, data_cb, null, null, 1000);
return ret;
},
sta_connected: function(iface, sta, data) {
let msg = { iface, sta, ...data };
let ret = {};
let data_cb = (type, data) => {
ret = { ...ret, ...data };
};
if (hostapd.data.auth_obj)
hostapd.data.auth_obj.notify("sta_connected", msg, data_cb, null, null, 1000);
return ret;
},
};