2023-05-26 08:23:59 +00:00
|
|
|
let libubus = require("ubus");
|
|
|
|
import { open, readfile } from "fs";
|
2023-09-09 15:07:09 +00:00
|
|
|
import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2024-01-11 08:14:59 +00:00
|
|
|
let ubus = libubus.connect(null, 60);
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
hostapd.data.config = {};
|
2024-01-11 08:14:59 +00:00
|
|
|
hostapd.data.pending_config = {};
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
hostapd.data.file_fields = {
|
|
|
|
vlan_file: true,
|
|
|
|
wpa_psk_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,
|
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-09-03 07:51:28 +00:00
|
|
|
function iface_gen_config(phy, config, start_disabled)
|
2023-05-26 08:23:59 +00:00
|
|
|
{
|
|
|
|
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";
|
2023-09-09 15:07:09 +00:00
|
|
|
let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
str += `
|
|
|
|
${type}=${bss.ifname}
|
2023-09-09 15:07:09 +00:00
|
|
|
bssid=${bss.bssid}
|
2023-05-26 08:23:59 +00:00
|
|
|
${join("\n", bss.data)}
|
2023-09-09 15:07:09 +00:00
|
|
|
nas_identifier=${nasid}
|
2023-09-03 07:51:28 +00:00
|
|
|
`;
|
|
|
|
if (start_disabled)
|
|
|
|
str += `
|
|
|
|
start_disabled=1
|
2023-05-26 08:23:59 +00:00
|
|
|
`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2023-09-03 07:51:28 +00:00
|
|
|
function iface_freq_info(iface, config, params)
|
|
|
|
{
|
|
|
|
let freq = params.frequency;
|
|
|
|
if (!freq)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let sec_offset = params.sec_chan_offset;
|
|
|
|
if (sec_offset != -1 && sec_offset != 1)
|
|
|
|
sec_offset = 0;
|
|
|
|
|
|
|
|
let width = 0;
|
|
|
|
for (let line in config.radio.data) {
|
|
|
|
if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
|
|
|
|
sec_offset = null; // auto-detect
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
|
|
|
|
if (!val)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
val = int(val[2]);
|
|
|
|
if (val > width)
|
|
|
|
width = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (freq < 4000)
|
|
|
|
width = 0;
|
|
|
|
|
|
|
|
return hostapd.freq_info(freq, sec_offset, width);
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_add(phy, config, phy_status)
|
|
|
|
{
|
|
|
|
let config_inline = iface_gen_config(phy, config, !!phy_status);
|
|
|
|
|
|
|
|
let bss = config.bss[0];
|
2023-09-19 09:02:54 +00:00
|
|
|
let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
|
2023-09-03 07:51:28 +00:00
|
|
|
if (ret < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!phy_status)
|
|
|
|
return true;
|
|
|
|
|
2023-09-19 09:02:54 +00:00
|
|
|
let iface = hostapd.interfaces[phy];
|
2023-09-03 07:51:28 +00:00
|
|
|
if (!iface)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let freq_info = iface_freq_info(iface, config, phy_status);
|
|
|
|
|
|
|
|
return iface.start(freq_info) >= 0;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
function iface_config_macaddr_list(config)
|
2023-05-26 08:23:59 +00:00
|
|
|
{
|
2023-09-09 15:07:09 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-01-13 15:11:41 +00:00
|
|
|
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.defer("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
|
|
|
|
}
|
|
|
|
|
2024-01-11 08:14:59 +00:00
|
|
|
function __iface_pending_next(pending, state, ret, data)
|
2023-09-28 08:28:43 +00:00
|
|
|
{
|
2024-01-11 08:14:59 +00:00
|
|
|
let config = pending.config;
|
|
|
|
let phydev = pending.phydev;
|
|
|
|
let phy = pending.phy;
|
2024-01-13 14:22:27 +00:00
|
|
|
let bss = config.bss[0];
|
2024-01-11 08:14:59 +00:00
|
|
|
|
|
|
|
if (pending.defer)
|
|
|
|
pending.defer.abort();
|
|
|
|
delete pending.defer;
|
|
|
|
switch (state) {
|
|
|
|
case "init":
|
|
|
|
let macaddr_list = [];
|
|
|
|
for (let i = 0; i < length(config.bss); i++)
|
|
|
|
push(macaddr_list, config.bss[i].bssid);
|
|
|
|
pending.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
|
|
|
|
return "create_bss";
|
|
|
|
case "create_bss":
|
|
|
|
let err = wdev_create(phy, bss.ifname, { mode: "ap" });
|
|
|
|
if (err) {
|
|
|
|
hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
pending.call("wpa_supplicant", "phy_status", { phy: phy });
|
|
|
|
return "check_phy";
|
|
|
|
case "check_phy":
|
|
|
|
let phy_status = data;
|
|
|
|
if (phy_status && phy_status.state == "COMPLETED") {
|
|
|
|
if (iface_add(phy, config, phy_status))
|
|
|
|
return "done";
|
|
|
|
|
|
|
|
hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
|
|
|
|
}
|
|
|
|
pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
|
|
|
|
return "wpas_stopped";
|
|
|
|
case "wpas_stopped":
|
|
|
|
if (!iface_add(phy, config))
|
|
|
|
hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
|
|
|
|
pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
|
|
|
|
return null;
|
|
|
|
case "done":
|
|
|
|
default:
|
|
|
|
delete hostapd.data.pending_config[phy];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_pending_next(ret, data)
|
|
|
|
{
|
|
|
|
let pending = true;
|
|
|
|
let cfg = this;
|
|
|
|
|
|
|
|
while (pending) {
|
|
|
|
this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
|
|
|
|
if (!this.next_state) {
|
|
|
|
__iface_pending_next(cfg, "done");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
pending = !this.defer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_pending_abort()
|
|
|
|
{
|
|
|
|
this.next_state = "done";
|
|
|
|
this.next();
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_pending_ubus_call(obj, method, arg)
|
|
|
|
{
|
|
|
|
let ubus = hostapd.data.ubus;
|
|
|
|
let pending = this;
|
|
|
|
this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
|
|
|
|
}
|
|
|
|
|
|
|
|
const iface_pending_proto = {
|
|
|
|
next: iface_pending_next,
|
|
|
|
call: iface_pending_ubus_call,
|
|
|
|
abort: iface_pending_abort,
|
|
|
|
};
|
|
|
|
|
|
|
|
function iface_pending_init(phydev, config)
|
|
|
|
{
|
|
|
|
let phy = phydev.name;
|
|
|
|
|
|
|
|
let pending = proto({
|
|
|
|
next_state: "init",
|
|
|
|
phydev: phydev,
|
|
|
|
phy: phy,
|
|
|
|
config: config,
|
|
|
|
next: iface_pending_next,
|
|
|
|
}, iface_pending_proto);
|
|
|
|
|
|
|
|
hostapd.data.pending_config[phy] = pending;
|
|
|
|
pending.next();
|
2023-09-28 08:28:43 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
function iface_restart(phydev, config, old_config)
|
|
|
|
{
|
|
|
|
let phy = phydev.name;
|
2024-01-11 08:14:59 +00:00
|
|
|
let pending = hostapd.data.pending_config[phy];
|
|
|
|
|
|
|
|
if (pending)
|
|
|
|
pending.abort();
|
2023-09-09 15:07:09 +00:00
|
|
|
|
2023-09-19 09:02:54 +00:00
|
|
|
hostapd.remove_iface(phy);
|
2023-05-26 08:23:59 +00:00
|
|
|
iface_remove(old_config);
|
|
|
|
iface_remove(config);
|
|
|
|
|
|
|
|
if (!config.bss || !config.bss[0]) {
|
|
|
|
hostapd.printf(`No bss for phy ${phy}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2024-01-11 08:14:59 +00:00
|
|
|
iface_pending_init(phydev, config);
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
function remove_file_fields(config)
|
|
|
|
{
|
|
|
|
return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
|
|
|
|
}
|
|
|
|
|
|
|
|
function bss_remove_file_fields(config)
|
2023-05-26 08:23:59 +00:00
|
|
|
{
|
2023-09-09 15:07:09 +00:00
|
|
|
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;
|
2023-09-15 12:25:23 +00:00
|
|
|
delete new_cfg.hash.vlan_file;
|
2023-09-09 15:07:09 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2023-05-26 08:23:59 +00:00
|
|
|
if (!old_config || !is_equal(old_config.radio, config.radio))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (is_equal(old_config.bss, config.bss))
|
|
|
|
return true;
|
2024-01-11 08:14:59 +00:00
|
|
|
|
|
|
|
if (hostapd.data.pending_config[phy])
|
|
|
|
return false;
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-08-08 18:11:42 +00:00
|
|
|
if (!old_config.bss || !old_config.bss[0])
|
|
|
|
return false;
|
|
|
|
|
2023-09-19 09:02:54 +00:00
|
|
|
let iface = hostapd.interfaces[phy];
|
2023-11-14 03:49:28 +00:00
|
|
|
let iface_name = old_config.bss[0].ifname;
|
2023-09-09 15:07:09 +00:00
|
|
|
if (!iface) {
|
|
|
|
hostapd.printf(`Could not find previous interface ${iface_name}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
2023-09-09 15:07:09 +00:00
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-08-11 22:36:13 +00:00
|
|
|
let first_bss = hostapd.bss[iface_name];
|
2023-09-09 15:07:09 +00:00
|
|
|
if (!first_bss) {
|
|
|
|
hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
|
2023-08-11 22:36:13 +00:00
|
|
|
return false;
|
2023-09-09 15:07:09 +00:00
|
|
|
}
|
2023-08-11 22:36:13 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
let macaddr_list = iface_config_macaddr_list(config);
|
|
|
|
let bss_list = [];
|
|
|
|
let bss_list_cfg = [];
|
|
|
|
let prev_bss_hash = [];
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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)
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
// 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)
|
2023-08-11 21:08:56 +00:00
|
|
|
return false;
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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)
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
2023-09-09 15:07:09 +00:00
|
|
|
|
|
|
|
let ifname = old_config.bss[i].ifname;
|
|
|
|
hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
|
|
|
|
prev_bss.delete();
|
|
|
|
wdev_remove(ifname);
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
// 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;
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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;
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
2023-09-09 15:07:09 +00:00
|
|
|
bss_list_cfg[i].ifname = new_ifname;
|
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
// 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];
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
let mac_idx = macaddr_list[bsscfg.bssid];
|
|
|
|
if (mac_idx < 0)
|
|
|
|
macaddr_list[bsscfg.bssid] = i;
|
|
|
|
if (mac_idx == i)
|
2023-05-26 08:23:59 +00:00
|
|
|
continue;
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
// 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];
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
let addr = phydev.macaddr_next(i);
|
|
|
|
if (!addr) {
|
|
|
|
hostapd.printf(`Failed to generate mac address for phy ${phy}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-09-09 15:07:09 +00:00
|
|
|
bsscfg.bssid = addr;
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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;
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
2023-09-09 15:07:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2023-09-15 12:25:23 +00:00
|
|
|
hostapd.printf(`Could not update config data files for bss ${ifname}`);
|
2023-09-09 15:07:09 +00:00
|
|
|
return false;
|
2023-09-15 12:25:23 +00:00
|
|
|
} else {
|
|
|
|
bss.ctrl("RELOAD_WPA_PSK");
|
|
|
|
continue;
|
2023-09-09 15:07:09 +00:00
|
|
|
}
|
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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}'`);
|
|
|
|
if (bss.set_config(config_inline, i) < 0) {
|
|
|
|
hostapd.printf(`Failed to set config for bss ${ifname}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_set_config(phy, config)
|
|
|
|
{
|
|
|
|
let old_config = hostapd.data.config[phy];
|
|
|
|
|
|
|
|
hostapd.data.config[phy] = config;
|
|
|
|
|
2023-09-19 09:02:54 +00:00
|
|
|
if (!config) {
|
|
|
|
hostapd.remove_iface(phy);
|
2023-05-26 08:23:59 +00:00
|
|
|
return iface_remove(old_config);
|
2023-09-19 09:02:54 +00:00
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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}`);
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hostapd.printf(`Restart interface for phy ${phy}`);
|
2023-09-09 15:07:09 +00:00
|
|
|
let ret = iface_restart(phydev, config, old_config);
|
|
|
|
|
|
|
|
return ret;
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function config_add_bss(config, name)
|
|
|
|
{
|
|
|
|
let bss = {
|
|
|
|
ifname: name,
|
|
|
|
data: [],
|
|
|
|
hash: {}
|
|
|
|
};
|
|
|
|
|
|
|
|
push(config.bss, bss);
|
|
|
|
|
|
|
|
return bss;
|
|
|
|
}
|
|
|
|
|
|
|
|
function iface_load_config(filename)
|
|
|
|
{
|
|
|
|
let f = open(filename, "r");
|
|
|
|
if (!f)
|
|
|
|
return null;
|
|
|
|
|
|
|
|
let config = {
|
|
|
|
radio: {
|
|
|
|
data: []
|
|
|
|
},
|
|
|
|
bss: [],
|
|
|
|
orig_file: filename,
|
|
|
|
};
|
|
|
|
|
|
|
|
let bss;
|
|
|
|
let line;
|
2023-10-30 17:32:24 +00:00
|
|
|
while ((line = rtrim(f.read("line"), "\n")) != null) {
|
2023-05-26 08:23:59 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
if (val[0] == "#num_global_macaddr" ||
|
|
|
|
val[0] == "mbssid")
|
|
|
|
config[val[0]] = int(val[1]);
|
|
|
|
|
2023-05-26 08:23:59 +00:00
|
|
|
push(config.radio.data, line);
|
|
|
|
}
|
|
|
|
|
2023-10-30 17:32:24 +00:00
|
|
|
while ((line = rtrim(f.read("line"), "\n")) != null) {
|
2023-09-09 15:07:09 +00:00
|
|
|
if (line == "#default_macaddr")
|
|
|
|
bss.default_macaddr = true;
|
|
|
|
|
2023-05-26 08:23:59 +00:00
|
|
|
let val = split(line, "=", 2);
|
|
|
|
if (!val[0])
|
|
|
|
continue;
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
if (val[0] == "bssid") {
|
|
|
|
bss.bssid = lc(val[1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (val[0] == "nas_identifier")
|
|
|
|
bss.nasid = val[1];
|
2023-08-11 21:08:56 +00:00
|
|
|
|
2023-05-26 08:23:59 +00:00
|
|
|
if (val[0] == "bss") {
|
|
|
|
bss = config_add_bss(config, val[1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hostapd.data.file_fields[val[0]])
|
|
|
|
bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
|
|
|
|
|
|
|
|
push(bss.data, line);
|
|
|
|
}
|
|
|
|
f.close();
|
|
|
|
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
let main_obj = {
|
|
|
|
reload: {
|
|
|
|
args: {
|
|
|
|
phy: "",
|
|
|
|
},
|
2023-09-09 15:07:09 +00:00
|
|
|
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);
|
2023-05-26 08:23:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2023-09-09 15:07:09 +00:00
|
|
|
})
|
2023-05-26 08:23:59 +00:00
|
|
|
},
|
2023-08-10 16:33:04 +00:00
|
|
|
apsta_state: {
|
|
|
|
args: {
|
|
|
|
phy: "",
|
|
|
|
up: true,
|
|
|
|
frequency: 0,
|
|
|
|
sec_chan_offset: 0,
|
|
|
|
csa: true,
|
|
|
|
csa_count: 0,
|
|
|
|
},
|
2023-09-09 15:07:09 +00:00
|
|
|
call: ex_wrap(function(req) {
|
2023-08-10 16:33:04 +00:00
|
|
|
if (req.args.up == null || !req.args.phy)
|
|
|
|
return libubus.STATUS_INVALID_ARGUMENT;
|
|
|
|
|
|
|
|
let phy = req.args.phy;
|
|
|
|
let config = hostapd.data.config[phy];
|
|
|
|
if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
|
|
|
|
return 0;
|
|
|
|
|
2023-09-19 09:02:54 +00:00
|
|
|
let iface = hostapd.interfaces[phy];
|
2023-08-10 16:33:04 +00:00
|
|
|
if (!iface)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!req.args.up) {
|
|
|
|
iface.stop();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-09-03 07:51:28 +00:00
|
|
|
if (!req.args.frequency)
|
2023-08-10 16:33:04 +00:00
|
|
|
return libubus.STATUS_INVALID_ARGUMENT;
|
|
|
|
|
2023-09-03 07:51:28 +00:00
|
|
|
let freq_info = iface_freq_info(iface, config, req.args);
|
2023-08-10 16:33:04 +00:00
|
|
|
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;
|
2023-09-09 15:07:09 +00:00
|
|
|
})
|
|
|
|
},
|
|
|
|
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;
|
|
|
|
})
|
2023-08-10 16:33:04 +00:00
|
|
|
},
|
2023-05-26 08:23:59 +00:00
|
|
|
config_set: {
|
|
|
|
args: {
|
|
|
|
phy: "",
|
|
|
|
config: "",
|
|
|
|
prev_config: "",
|
|
|
|
},
|
2023-09-09 15:07:09 +00:00
|
|
|
call: ex_wrap(function(req) {
|
2023-05-26 08:23:59 +00:00
|
|
|
let phy = req.args.phy;
|
|
|
|
let file = req.args.config;
|
|
|
|
let prev_file = req.args.prev_config;
|
|
|
|
|
|
|
|
if (!phy)
|
|
|
|
return libubus.STATUS_INVALID_ARGUMENT;
|
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
if (prev_file && !hostapd.data.config[phy]) {
|
|
|
|
let config = iface_load_config(prev_file);
|
|
|
|
if (config)
|
|
|
|
config.radio.data = [];
|
|
|
|
hostapd.data.config[phy] = config;
|
|
|
|
}
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
let config = iface_load_config(file);
|
2023-05-26 08:23:59 +00:00
|
|
|
|
2023-09-09 15:07:09 +00:00
|
|
|
hostapd.printf(`Set new config for phy ${phy}: ${file}`);
|
|
|
|
iface_set_config(phy, config);
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
pid: hostapd.getpid()
|
|
|
|
};
|
2023-09-09 15:07:09 +00:00
|
|
|
})
|
2023-05-26 08:23:59 +00:00
|
|
|
},
|
|
|
|
config_add: {
|
|
|
|
args: {
|
|
|
|
iface: "",
|
|
|
|
config: "",
|
|
|
|
},
|
2023-09-09 15:07:09 +00:00
|
|
|
call: ex_wrap(function(req) {
|
2023-05-26 08:23:59 +00:00
|
|
|
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()
|
|
|
|
};
|
2023-09-09 15:07:09 +00:00
|
|
|
})
|
2023-05-26 08:23:59 +00:00
|
|
|
},
|
|
|
|
config_remove: {
|
|
|
|
args: {
|
|
|
|
iface: ""
|
|
|
|
},
|
2023-09-09 15:07:09 +00:00
|
|
|
call: ex_wrap(function(req) {
|
2023-05-26 08:23:59 +00:00
|
|
|
if (!req.args.iface)
|
|
|
|
return libubus.STATUS_INVALID_ARGUMENT;
|
|
|
|
|
|
|
|
hostapd.remove_iface(req.args.iface);
|
|
|
|
return 0;
|
2023-09-09 15:07:09 +00:00
|
|
|
})
|
2023-05-26 08:23:59 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
hostapd.data.ubus = ubus;
|
|
|
|
hostapd.data.obj = ubus.publish("hostapd", main_obj);
|
2023-11-30 19:08:21 +00:00
|
|
|
hostapd.udebug_set("hostapd", hostapd.data.ubus);
|
2023-05-26 08:23:59 +00:00
|
|
|
|
|
|
|
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, null);
|
2023-11-30 19:08:21 +00:00
|
|
|
hostapd.udebug_set(null);
|
2023-05-26 08:23:59 +00:00
|
|
|
hostapd.ubus.disconnect();
|
|
|
|
},
|
|
|
|
bss_add: function(name, obj) {
|
|
|
|
bss_event("add", name);
|
|
|
|
},
|
|
|
|
bss_reload: function(name, obj, reconf) {
|
|
|
|
bss_event("reload", name, { reconf: reconf != 0 });
|
|
|
|
},
|
|
|
|
bss_remove: function(name, obj) {
|
|
|
|
bss_event("remove", name);
|
|
|
|
}
|
|
|
|
};
|