1 let libubus = require("ubus");
2 import { open, readfile } from "fs";
3 import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac } from "common";
5 let ubus = libubus.connect();
7 hostapd.data.config = {};
9 hostapd.data.file_fields = {
12 accept_mac_file: true,
24 function iface_remove(cfg)
26 if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
29 hostapd.remove_iface(cfg.bss[0].ifname);
30 for (let bss in cfg.bss)
31 wdev_remove(bss.ifname);
34 function iface_gen_config(phy, config, start_disabled)
37 ${join("\n", config.radio.data)}
38 channel=${config.radio.channel}
41 for (let i = 0; i < length(config.bss); i++) {
42 let bss = config.bss[i];
43 let type = i > 0 ? "bss" : "interface";
47 ${join("\n", bss.data)}
58 function iface_freq_info(iface, config, params)
60 let freq = params.frequency;
64 let sec_offset = params.sec_chan_offset;
65 if (sec_offset != -1 && sec_offset != 1)
69 for (let line in config.radio.data) {
70 if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
71 sec_offset = null; // auto-detect
75 let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
87 return hostapd.freq_info(freq, sec_offset, width);
90 function iface_add(phy, config, phy_status)
92 let config_inline = iface_gen_config(phy, config, !!phy_status);
94 let bss = config.bss[0];
95 let ret = hostapd.add_iface(`bss_config=${bss.ifname}:${config_inline}`);
102 let iface = hostapd.interfaces[bss.ifname];
106 let freq_info = iface_freq_info(iface, config, phy_status);
108 return iface.start(freq_info) >= 0;
111 function iface_restart(phy, config, old_config)
113 iface_remove(old_config);
114 iface_remove(config);
116 if (!config.bss || !config.bss[0]) {
117 hostapd.printf(`No bss for phy ${phy}`);
121 let bss = config.bss[0];
122 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
124 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
126 let ubus = hostapd.data.ubus;
127 let phy_status = ubus.call("wpa_supplicant", "phy_status", { phy: phy });
128 if (phy_status && phy_status.state == "COMPLETED") {
129 if (iface_add(phy, config, phy_status))
132 hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
135 ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
136 if (!iface_add(phy, config))
137 hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
138 ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
141 function array_to_obj(arr, key, start)
146 for (let i = start; i < length(arr); i++) {
154 function find_array_idx(arr, key, val)
156 for (let i = 0; i < length(arr); i++)
157 if (arr[i][key] == val)
163 function bss_reload_psk(bss, config, old_config)
165 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
168 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
169 if (!is_equal(old_config, config))
172 let ret = bss.ctrl("RELOAD_WPA_PSK");
175 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
178 function iface_reload_config(phy, config, old_config)
180 if (!old_config || !is_equal(old_config.radio, config.radio))
183 if (is_equal(old_config.bss, config.bss))
186 if (!old_config.bss || !old_config.bss[0])
189 if (config.bss[0].ifname != old_config.bss[0].ifname)
192 let iface_name = config.bss[0].ifname;
193 let iface = hostapd.interfaces[iface_name];
197 let first_bss = hostapd.bss[iface_name];
201 let config_inline = iface_gen_config(phy, config);
203 bss_reload_psk(first_bss, config.bss[0], old_config.bss[0]);
204 if (!is_equal(config.bss[0], old_config.bss[0])) {
205 if (phy_is_fullmac(phy))
208 if (config.bss[0].bssid != old_config.bss[0].bssid)
211 hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
212 if (first_bss.set_config(config_inline, 0) < 0) {
213 hostapd.printf(`Failed to set config`);
218 let new_cfg = array_to_obj(config.bss, "ifname", 1);
219 let old_cfg = array_to_obj(old_config.bss, "ifname", 1);
221 for (let name in old_cfg) {
222 let bss = hostapd.bss[name];
224 hostapd.printf(`bss '${name}' not found`);
228 if (!new_cfg[name]) {
229 hostapd.printf(`Remove bss '${name}' on phy '${phy}'`);
235 let new_cfg_data = new_cfg[name];
236 delete new_cfg[name];
238 if (is_equal(old_cfg[name], new_cfg_data))
241 hostapd.printf(`Reload config for bss '${name}' on phy '${phy}'`);
242 let idx = find_array_idx(config.bss, "ifname", name);
244 hostapd.printf(`bss index not found`);
248 if (bss.set_config(config_inline, idx) < 0) {
249 hostapd.printf(`Failed to set config`);
254 for (let name in new_cfg) {
255 hostapd.printf(`Add bss '${name}' on phy '${phy}'`);
257 let idx = find_array_idx(config.bss, "ifname", name);
259 hostapd.printf(`bss index not found`);
263 if (iface.add_bss(config_inline, idx) < 0) {
264 hostapd.printf(`Failed to add bss`);
272 function iface_set_config(phy, config)
274 let old_config = hostapd.data.config[phy];
276 hostapd.data.config[phy] = config;
279 return iface_remove(old_config);
281 let ret = iface_reload_config(phy, config, old_config);
283 hostapd.printf(`Reloaded settings for phy ${phy}`);
287 hostapd.printf(`Restart interface for phy ${phy}`);
288 return iface_restart(phy, config, old_config);
291 function config_add_bss(config, name)
299 push(config.bss, bss);
304 function iface_load_config(filename)
306 let f = open(filename, "r");
320 while ((line = trim(f.read("line"))) != null) {
321 let val = split(line, "=", 2);
325 if (val[0] == "interface") {
326 bss = config_add_bss(config, val[1]);
330 if (val[0] == "channel") {
331 config.radio.channel = val[1];
335 push(config.radio.data, line);
338 while ((line = trim(f.read("line"))) != null) {
339 let val = split(line, "=", 2);
343 if (val[0] == "bssid")
346 if (val[0] == "bss") {
347 bss = config_add_bss(config, val[1]);
351 if (hostapd.data.file_fields[val[0]])
352 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
354 push(bss.data, line);
367 call: function(req) {
369 let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
370 for (let phy_name in phy_list) {
371 let phy = hostapd.data.config[phy_name];
372 let config = iface_load_config(phy.orig_file);
373 iface_set_config(phy_name, config);
376 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
377 return libubus.STATUS_INVALID_ARGUMENT;
392 call: function(req) {
393 if (req.args.up == null || !req.args.phy)
394 return libubus.STATUS_INVALID_ARGUMENT;
396 let phy = req.args.phy;
397 let config = hostapd.data.config[phy];
398 if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
401 let iface = hostapd.interfaces[config.bss[0].ifname];
410 if (!req.args.frequency)
411 return libubus.STATUS_INVALID_ARGUMENT;
413 let freq_info = iface_freq_info(iface, config, req.args);
415 return libubus.STATUS_UNKNOWN_ERROR;
419 freq_info.csa_count = req.args.csa_count ?? 10;
420 ret = iface.switch_channel(freq_info);
423 ret = iface.start(freq_info);
426 return libubus.STATUS_UNKNOWN_ERROR;
437 call: function(req) {
438 let phy = req.args.phy;
439 let file = req.args.config;
440 let prev_file = req.args.prev_config;
443 return libubus.STATUS_INVALID_ARGUMENT;
446 if (prev_file && !hostapd.data.config[phy]) {
447 let config = iface_load_config(prev_file);
449 config.radio.data = [];
450 hostapd.data.config[phy] = config;
453 let config = iface_load_config(file);
455 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
456 iface_set_config(phy, config);
458 hostapd.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
459 return libubus.STATUS_INVALID_ARGUMENT;
463 pid: hostapd.getpid()
472 call: function(req) {
473 if (!req.args.iface || !req.args.config)
474 return libubus.STATUS_INVALID_ARGUMENT;
476 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
477 return libubus.STATUS_INVALID_ARGUMENT;
480 pid: hostapd.getpid()
488 call: function(req) {
490 return libubus.STATUS_INVALID_ARGUMENT;
492 hostapd.remove_iface(req.args.iface);
498 hostapd.data.ubus = ubus;
499 hostapd.data.obj = ubus.publish("hostapd", main_obj);
501 function bss_event(type, name, data) {
502 let ubus = hostapd.data.ubus;
506 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
507 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
511 shutdown: function() {
512 for (let phy in hostapd.data.config)
513 iface_set_config(phy, null);
514 hostapd.ubus.disconnect();
516 bss_add: function(name, obj) {
517 bss_event("add", name);
519 bss_reload: function(name, obj, reconf) {
520 bss_event("reload", name, { reconf: reconf != 0 });
522 bss_remove: function(name, obj) {
523 bss_event("remove", name);