1 let libubus = require("ubus");
2 import { open, readfile } from "fs";
3 import { wdev_create, wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open } from "common";
5 let ubus = libubus.connect(null, 60);
7 hostapd.data.config = {};
8 hostapd.data.pending_config = {};
10 hostapd.data.file_fields = {
13 accept_mac_file: true,
25 function iface_remove(cfg)
27 if (!cfg || !cfg.bss || !cfg.bss[0] || !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";
44 let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
49 ${join("\n", bss.data)}
50 nas_identifier=${nasid}
61 function iface_freq_info(iface, config, params)
63 let freq = params.frequency;
67 let sec_offset = params.sec_chan_offset;
68 if (sec_offset != -1 && sec_offset != 1)
72 for (let line in config.radio.data) {
73 if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
74 sec_offset = null; // auto-detect
78 let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
90 return hostapd.freq_info(freq, sec_offset, width);
93 function iface_add(phy, config, phy_status)
95 let config_inline = iface_gen_config(phy, config, !!phy_status);
97 let bss = config.bss[0];
98 let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
105 let iface = hostapd.interfaces[phy];
109 let freq_info = iface_freq_info(iface, config, phy_status);
111 return iface.start(freq_info) >= 0;
114 function iface_config_macaddr_list(config)
116 let macaddr_list = {};
117 for (let i = 0; i < length(config.bss); i++) {
118 let bss = config.bss[i];
119 if (!bss.default_macaddr)
120 macaddr_list[bss.bssid] = i;
126 function __iface_pending_next(pending, state, ret, data)
128 let config = pending.config;
129 let phydev = pending.phydev;
130 let phy = pending.phy;
131 let bss = config.bss[0];
134 pending.defer.abort();
135 delete pending.defer;
138 let macaddr_list = [];
139 for (let i = 0; i < length(config.bss); i++)
140 push(macaddr_list, config.bss[i].bssid);
141 pending.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
144 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
146 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
150 pending.call("wpa_supplicant", "phy_status", { phy: phy });
153 let phy_status = data;
154 if (phy_status && phy_status.state == "COMPLETED") {
155 if (iface_add(phy, config, phy_status))
158 hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
160 pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
161 return "wpas_stopped";
163 if (!iface_add(phy, config))
164 hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
165 pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
169 delete hostapd.data.pending_config[phy];
174 function iface_pending_next(ret, data)
180 this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
181 if (!this.next_state) {
182 __iface_pending_next(cfg, "done");
185 pending = !this.defer;
189 function iface_pending_abort()
191 this.next_state = "done";
195 function iface_pending_ubus_call(obj, method, arg)
197 let ubus = hostapd.data.ubus;
199 this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
202 const iface_pending_proto = {
203 next: iface_pending_next,
204 call: iface_pending_ubus_call,
205 abort: iface_pending_abort,
208 function iface_pending_init(phydev, config)
210 let phy = phydev.name;
212 let pending = proto({
217 next: iface_pending_next,
218 }, iface_pending_proto);
220 hostapd.data.pending_config[phy] = pending;
224 function iface_restart(phydev, config, old_config)
226 let phy = phydev.name;
227 let pending = hostapd.data.pending_config[phy];
232 hostapd.remove_iface(phy);
233 iface_remove(old_config);
234 iface_remove(config);
236 if (!config.bss || !config.bss[0]) {
237 hostapd.printf(`No bss for phy ${phy}`);
241 phydev.macaddr_init(iface_config_macaddr_list(config));
242 for (let i = 0; i < length(config.bss); i++) {
243 let bss = config.bss[i];
244 if (bss.default_macaddr)
245 bss.bssid = phydev.macaddr_next();
248 iface_pending_init(phydev, config);
251 function array_to_obj(arr, key, start)
256 for (let i = start; i < length(arr); i++) {
264 function find_array_idx(arr, key, val)
266 for (let i = 0; i < length(arr); i++)
267 if (arr[i][key] == val)
273 function bss_reload_psk(bss, config, old_config)
275 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
278 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
279 if (!is_equal(old_config, config))
282 let ret = bss.ctrl("RELOAD_WPA_PSK");
285 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
288 function remove_file_fields(config)
290 return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
293 function bss_remove_file_fields(config)
297 for (let key in config)
298 new_cfg[key] = config[key];
299 new_cfg.data = remove_file_fields(new_cfg.data);
301 for (let key in config.hash)
302 new_cfg.hash[key] = config.hash[key];
303 delete new_cfg.hash.wpa_psk_file;
304 delete new_cfg.hash.vlan_file;
309 function bss_config_hash(config)
311 return hostapd.sha1(remove_file_fields(config) + "");
314 function bss_find_existing(config, prev_config, prev_hash)
316 let hash = bss_config_hash(config.data);
318 for (let i = 0; i < length(prev_config.bss); i++) {
319 if (!prev_hash[i] || hash != prev_hash[i])
329 function get_config_bss(config, idx)
331 if (!config.bss[idx]) {
332 hostapd.printf(`Invalid bss index ${idx}`);
336 let ifname = config.bss[idx].ifname;
338 hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
340 return hostapd.bss[ifname];
343 function iface_reload_config(phydev, config, old_config)
345 let phy = phydev.name;
347 if (!old_config || !is_equal(old_config.radio, config.radio))
350 if (is_equal(old_config.bss, config.bss))
353 if (hostapd.data.pending_config[phy])
356 if (!old_config.bss || !old_config.bss[0])
359 let iface = hostapd.interfaces[phy];
360 let iface_name = old_config.bss[0].ifname;
362 hostapd.printf(`Could not find previous interface ${iface_name}`);
366 let first_bss = hostapd.bss[iface_name];
368 hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
372 let macaddr_list = iface_config_macaddr_list(config);
374 let bss_list_cfg = [];
375 let prev_bss_hash = [];
377 for (let bss in old_config.bss) {
378 let hash = bss_config_hash(bss.data);
379 push(prev_bss_hash, bss_config_hash(bss.data));
382 // Step 1: find (possibly renamed) interfaces with the same config
383 // and store them in the new order (with gaps)
384 for (let i = 0; i < length(config.bss); i++) {
387 // For fullmac devices, the first interface needs to be preserved,
388 // since it's treated as the master
389 if (!i && phy_is_fullmac(phy)) {
391 prev_bss_hash[0] = null;
393 prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
398 let cur_config = config.bss[i];
399 let prev_config = old_config.bss[prev];
401 let prev_bss = get_config_bss(old_config, prev);
405 // try to preserve MAC address of this BSS by reassigning another
407 if (cur_config.default_macaddr &&
408 !macaddr_list[prev_config.bssid]) {
409 macaddr_list[prev_config.bssid] = i;
410 cur_config.bssid = prev_config.bssid;
413 bss_list[i] = prev_bss;
414 bss_list_cfg[i] = old_config.bss[prev];
417 if (config.mbssid && !bss_list_cfg[0]) {
418 hostapd.printf("First BSS changed with MBSSID enabled");
422 // Step 2: if none were found, rename and preserve the first one
423 if (length(bss_list) == 0) {
424 // can't change the bssid of the first bss
425 if (config.bss[0].bssid != old_config.bss[0].bssid) {
426 if (!config.bss[0].default_macaddr) {
427 hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
431 config.bss[0].bssid = old_config.bss[0].bssid;
434 let prev_bss = get_config_bss(old_config, 0);
438 macaddr_list[config.bss[0].bssid] = 0;
439 bss_list[0] = prev_bss;
440 bss_list_cfg[0] = old_config.bss[0];
441 prev_bss_hash[0] = null;
444 // Step 3: delete all unused old interfaces
445 for (let i = 0; i < length(prev_bss_hash); i++) {
446 if (!prev_bss_hash[i])
449 let prev_bss = get_config_bss(old_config, i);
453 let ifname = old_config.bss[i].ifname;
454 hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
459 // Step 4: rename preserved interfaces, use temporary name on duplicates
460 let rename_list = [];
461 for (let i = 0; i < length(bss_list); i++) {
465 let old_ifname = bss_list_cfg[i].ifname;
466 let new_ifname = config.bss[i].ifname;
467 if (old_ifname == new_ifname)
470 if (hostapd.bss[new_ifname]) {
471 new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
472 push(rename_list, i);
475 hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
476 if (!bss_list[i].rename(new_ifname)) {
477 hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
481 bss_list_cfg[i].ifname = new_ifname;
484 // Step 5: rename interfaces with temporary names
485 for (let i in rename_list) {
486 let new_ifname = config.bss[i].ifname;
487 if (!bss_list[i].rename(new_ifname)) {
488 hostapd.printf(`Failed to rename bss to ${new_ifname}`);
491 bss_list_cfg[i].ifname = new_ifname;
494 // Step 6: assign BSSID for newly created interfaces
496 num_global: config.num_global_macaddr ?? 1,
497 mbssid: config.mbssid ?? 0,
499 macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
500 for (let i = 0; i < length(config.bss); i++) {
503 let bsscfg = config.bss[i];
505 let mac_idx = macaddr_list[bsscfg.bssid];
507 macaddr_list[bsscfg.bssid] = i;
511 // statically assigned bssid of the new interface is in conflict
512 // with the bssid of a reused interface. reassign the reused interface
513 if (!bsscfg.default_macaddr) {
514 // can't update bssid of the first BSS, need to restart
518 bsscfg = config.bss[mac_idx];
521 let addr = phydev.macaddr_next(i);
523 hostapd.printf(`Failed to generate mac address for phy ${phy}`);
529 let config_inline = iface_gen_config(phy, config);
531 // Step 7: fill in the gaps with new interfaces
532 for (let i = 0; i < length(config.bss); i++) {
533 let ifname = config.bss[i].ifname;
534 let bss = bss_list[i];
539 hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
540 bss_list[i] = iface.add_bss(config_inline, i);
542 hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
547 // Step 8: update interface bss order
548 if (!iface.set_bss_order(bss_list)) {
549 hostapd.printf(`Failed to update BSS order on phy '${phy}'`);
553 // Step 9: update config
554 for (let i = 0; i < length(config.bss); i++) {
555 if (!bss_list_cfg[i])
558 let ifname = config.bss[i].ifname;
559 let bss = bss_list[i];
561 if (is_equal(config.bss[i], bss_list_cfg[i]))
564 if (is_equal(bss_remove_file_fields(config.bss[i]),
565 bss_remove_file_fields(bss_list_cfg[i]))) {
566 hostapd.printf(`Update config data files for bss ${ifname}`);
567 if (bss.set_config(config_inline, i, true) < 0) {
568 hostapd.printf(`Could not update config data files for bss ${ifname}`);
571 bss.ctrl("RELOAD_WPA_PSK");
576 bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
577 if (is_equal(config.bss[i], bss_list_cfg[i]))
580 hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
581 if (bss.set_config(config_inline, i) < 0) {
582 hostapd.printf(`Failed to set config for bss ${ifname}`);
590 function iface_set_config(phy, config)
592 let old_config = hostapd.data.config[phy];
594 hostapd.data.config[phy] = config;
597 hostapd.remove_iface(phy);
598 return iface_remove(old_config);
601 let phydev = phy_open(phy);
603 hostapd.printf(`Failed to open phy ${phy}`);
608 let ret = iface_reload_config(phydev, config, old_config);
610 iface_update_supplicant_macaddr(phy, config);
611 hostapd.printf(`Reloaded settings for phy ${phy}`);
615 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
618 hostapd.printf(`Restart interface for phy ${phy}`);
619 let ret = iface_restart(phydev, config, old_config);
624 function config_add_bss(config, name)
632 push(config.bss, bss);
637 function iface_load_config(filename)
639 let f = open(filename, "r");
653 while ((line = rtrim(f.read("line"), "\n")) != null) {
654 let val = split(line, "=", 2);
658 if (val[0] == "interface") {
659 bss = config_add_bss(config, val[1]);
663 if (val[0] == "channel") {
664 config.radio.channel = val[1];
668 if (val[0] == "#num_global_macaddr" ||
670 config[val[0]] = int(val[1]);
672 push(config.radio.data, line);
675 while ((line = rtrim(f.read("line"), "\n")) != null) {
676 if (line == "#default_macaddr")
677 bss.default_macaddr = true;
679 let val = split(line, "=", 2);
683 if (val[0] == "bssid") {
684 bss.bssid = lc(val[1]);
688 if (val[0] == "nas_identifier")
691 if (val[0] == "bss") {
692 bss = config_add_bss(config, val[1]);
696 if (hostapd.data.file_fields[val[0]])
697 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
699 push(bss.data, line);
706 function ex_wrap(func) {
712 hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
714 return libubus.STATUS_UNKNOWN_ERROR;
723 call: ex_wrap(function(req) {
724 let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
725 for (let phy_name in phy_list) {
726 let phy = hostapd.data.config[phy_name];
727 let config = iface_load_config(phy.orig_file);
728 iface_set_config(phy_name, config);
743 call: ex_wrap(function(req) {
744 if (req.args.up == null || !req.args.phy)
745 return libubus.STATUS_INVALID_ARGUMENT;
747 let phy = req.args.phy;
748 let config = hostapd.data.config[phy];
749 if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
752 let iface = hostapd.interfaces[phy];
761 if (!req.args.frequency)
762 return libubus.STATUS_INVALID_ARGUMENT;
764 let freq_info = iface_freq_info(iface, config, req.args);
766 return libubus.STATUS_UNKNOWN_ERROR;
770 freq_info.csa_count = req.args.csa_count ?? 10;
771 ret = iface.switch_channel(freq_info);
773 ret = iface.start(freq_info);
776 return libubus.STATUS_UNKNOWN_ERROR;
781 config_get_macaddr_list: {
785 call: ex_wrap(function(req) {
786 let phy = req.args.phy;
788 return libubus.STATUS_INVALID_ARGUMENT;
794 let config = hostapd.data.config[phy];
798 ret.macaddr = map(config.bss, (bss) => bss.bssid);
808 call: ex_wrap(function(req) {
809 let phy = req.args.phy;
810 let file = req.args.config;
811 let prev_file = req.args.prev_config;
814 return libubus.STATUS_INVALID_ARGUMENT;
816 if (prev_file && !hostapd.data.config[phy]) {
817 let config = iface_load_config(prev_file);
819 config.radio.data = [];
820 hostapd.data.config[phy] = config;
823 let config = iface_load_config(file);
825 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
826 iface_set_config(phy, config);
829 pid: hostapd.getpid()
838 call: ex_wrap(function(req) {
839 if (!req.args.iface || !req.args.config)
840 return libubus.STATUS_INVALID_ARGUMENT;
842 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
843 return libubus.STATUS_INVALID_ARGUMENT;
846 pid: hostapd.getpid()
854 call: ex_wrap(function(req) {
856 return libubus.STATUS_INVALID_ARGUMENT;
858 hostapd.remove_iface(req.args.iface);
864 hostapd.data.ubus = ubus;
865 hostapd.data.obj = ubus.publish("hostapd", main_obj);
866 hostapd.udebug_set("hostapd", hostapd.data.ubus);
868 function bss_event(type, name, data) {
869 let ubus = hostapd.data.ubus;
873 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
874 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
878 shutdown: function() {
879 for (let phy in hostapd.data.config)
880 iface_set_config(phy, null);
881 hostapd.udebug_set(null);
882 hostapd.ubus.disconnect();
884 bss_add: function(name, obj) {
885 bss_event("add", name);
887 bss_reload: function(name, obj, reconf) {
888 bss_event("reload", name, { reconf: reconf != 0 });
890 bss_remove: function(name, obj) {
891 bss_event("remove", name);