hostapd: make ubus calls to wpa_supplicant asynchronous
[openwrt/staging/981213.git] / package / network / services / hostapd / files / hostapd.uc
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";
4
5 let ubus = libubus.connect(null, 60);
6
7 hostapd.data.config = {};
8 hostapd.data.pending_config = {};
9
10 hostapd.data.file_fields = {
11 vlan_file: true,
12 wpa_psk_file: true,
13 accept_mac_file: true,
14 deny_mac_file: true,
15 eap_user_file: true,
16 ca_cert: true,
17 server_cert: true,
18 server_cert2: true,
19 private_key: true,
20 private_key2: true,
21 dh_file: true,
22 eap_sim_db: true,
23 };
24
25 function iface_remove(cfg)
26 {
27 if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
28 return;
29
30 for (let bss in cfg.bss)
31 wdev_remove(bss.ifname);
32 }
33
34 function iface_gen_config(phy, config, start_disabled)
35 {
36 let str = `data:
37 ${join("\n", config.radio.data)}
38 channel=${config.radio.channel}
39 `;
40
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, ":", "");
45
46 str += `
47 ${type}=${bss.ifname}
48 bssid=${bss.bssid}
49 ${join("\n", bss.data)}
50 nas_identifier=${nasid}
51 `;
52 if (start_disabled)
53 str += `
54 start_disabled=1
55 `;
56 }
57
58 return str;
59 }
60
61 function iface_freq_info(iface, config, params)
62 {
63 let freq = params.frequency;
64 if (!freq)
65 return null;
66
67 let sec_offset = params.sec_chan_offset;
68 if (sec_offset != -1 && sec_offset != 1)
69 sec_offset = 0;
70
71 let width = 0;
72 for (let line in config.radio.data) {
73 if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
74 sec_offset = null; // auto-detect
75 continue;
76 }
77
78 let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
79 if (!val)
80 continue;
81
82 val = int(val[2]);
83 if (val > width)
84 width = val;
85 }
86
87 if (freq < 4000)
88 width = 0;
89
90 return hostapd.freq_info(freq, sec_offset, width);
91 }
92
93 function iface_add(phy, config, phy_status)
94 {
95 let config_inline = iface_gen_config(phy, config, !!phy_status);
96
97 let bss = config.bss[0];
98 let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
99 if (ret < 0)
100 return false;
101
102 if (!phy_status)
103 return true;
104
105 let iface = hostapd.interfaces[phy];
106 if (!iface)
107 return false;
108
109 let freq_info = iface_freq_info(iface, config, phy_status);
110
111 return iface.start(freq_info) >= 0;
112 }
113
114 function iface_config_macaddr_list(config)
115 {
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;
121 }
122
123 return macaddr_list;
124 }
125
126 function __iface_pending_next(pending, state, ret, data)
127 {
128 let config = pending.config;
129 let phydev = pending.phydev;
130 let phy = pending.phy;
131
132 if (pending.defer)
133 pending.defer.abort();
134 delete pending.defer;
135 switch (state) {
136 case "init":
137 let macaddr_list = [];
138 for (let i = 0; i < length(config.bss); i++)
139 push(macaddr_list, config.bss[i].bssid);
140 pending.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
141 return "create_bss";
142 case "create_bss":
143 let bss = config.bss[0];
144 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
145 if (err) {
146 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
147 return null;
148 }
149
150 pending.call("wpa_supplicant", "phy_status", { phy: phy });
151 return "check_phy";
152 case "check_phy":
153 let phy_status = data;
154 if (phy_status && phy_status.state == "COMPLETED") {
155 if (iface_add(phy, config, phy_status))
156 return "done";
157
158 hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
159 }
160 pending.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
161 return "wpas_stopped";
162 case "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 });
166 return null;
167 case "done":
168 default:
169 delete hostapd.data.pending_config[phy];
170 break;
171 }
172 }
173
174 function iface_pending_next(ret, data)
175 {
176 let pending = true;
177 let cfg = this;
178
179 while (pending) {
180 this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
181 if (!this.next_state) {
182 __iface_pending_next(cfg, "done");
183 return;
184 }
185 pending = !this.defer;
186 }
187 }
188
189 function iface_pending_abort()
190 {
191 this.next_state = "done";
192 this.next();
193 }
194
195 function iface_pending_ubus_call(obj, method, arg)
196 {
197 let ubus = hostapd.data.ubus;
198 let pending = this;
199 this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
200 }
201
202 const iface_pending_proto = {
203 next: iface_pending_next,
204 call: iface_pending_ubus_call,
205 abort: iface_pending_abort,
206 };
207
208 function iface_pending_init(phydev, config)
209 {
210 let phy = phydev.name;
211
212 let pending = proto({
213 next_state: "init",
214 phydev: phydev,
215 phy: phy,
216 config: config,
217 next: iface_pending_next,
218 }, iface_pending_proto);
219
220 hostapd.data.pending_config[phy] = pending;
221 pending.next();
222 }
223
224 function iface_restart(phydev, config, old_config)
225 {
226 let phy = phydev.name;
227 let pending = hostapd.data.pending_config[phy];
228
229 if (pending)
230 pending.abort();
231
232 hostapd.remove_iface(phy);
233 iface_remove(old_config);
234 iface_remove(config);
235
236 if (!config.bss || !config.bss[0]) {
237 hostapd.printf(`No bss for phy ${phy}`);
238 return;
239 }
240
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();
246 }
247
248 iface_pending_init(phydev, config);
249 }
250
251 function array_to_obj(arr, key, start)
252 {
253 let obj = {};
254
255 start ??= 0;
256 for (let i = start; i < length(arr); i++) {
257 let cur = arr[i];
258 obj[cur[key]] = cur;
259 }
260
261 return obj;
262 }
263
264 function find_array_idx(arr, key, val)
265 {
266 for (let i = 0; i < length(arr); i++)
267 if (arr[i][key] == val)
268 return i;
269
270 return -1;
271 }
272
273 function bss_reload_psk(bss, config, old_config)
274 {
275 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
276 return;
277
278 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
279 if (!is_equal(old_config, config))
280 return;
281
282 let ret = bss.ctrl("RELOAD_WPA_PSK");
283 ret ??= "failed";
284
285 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
286 }
287
288 function remove_file_fields(config)
289 {
290 return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
291 }
292
293 function bss_remove_file_fields(config)
294 {
295 let new_cfg = {};
296
297 for (let key in config)
298 new_cfg[key] = config[key];
299 new_cfg.data = remove_file_fields(new_cfg.data);
300 new_cfg.hash = {};
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;
305
306 return new_cfg;
307 }
308
309 function bss_config_hash(config)
310 {
311 return hostapd.sha1(remove_file_fields(config) + "");
312 }
313
314 function bss_find_existing(config, prev_config, prev_hash)
315 {
316 let hash = bss_config_hash(config.data);
317
318 for (let i = 0; i < length(prev_config.bss); i++) {
319 if (!prev_hash[i] || hash != prev_hash[i])
320 continue;
321
322 prev_hash[i] = null;
323 return i;
324 }
325
326 return -1;
327 }
328
329 function get_config_bss(config, idx)
330 {
331 if (!config.bss[idx]) {
332 hostapd.printf(`Invalid bss index ${idx}`);
333 return null;
334 }
335
336 let ifname = config.bss[idx].ifname;
337 if (!ifname)
338 hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
339
340 return hostapd.bss[ifname];
341 }
342
343 function iface_reload_config(phydev, config, old_config)
344 {
345 let phy = phydev.name;
346
347 if (!old_config || !is_equal(old_config.radio, config.radio))
348 return false;
349
350 if (is_equal(old_config.bss, config.bss))
351 return true;
352
353 if (hostapd.data.pending_config[phy])
354 return false;
355
356 if (!old_config.bss || !old_config.bss[0])
357 return false;
358
359 let iface = hostapd.interfaces[phy];
360 let iface_name = old_config.bss[0].ifname;
361 if (!iface) {
362 hostapd.printf(`Could not find previous interface ${iface_name}`);
363 return false;
364 }
365
366 let first_bss = hostapd.bss[iface_name];
367 if (!first_bss) {
368 hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
369 return false;
370 }
371
372 let macaddr_list = iface_config_macaddr_list(config);
373 let bss_list = [];
374 let bss_list_cfg = [];
375 let prev_bss_hash = [];
376
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));
380 }
381
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++) {
385 let prev;
386
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)) {
390 prev = 0;
391 prev_bss_hash[0] = null;
392 } else {
393 prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
394 }
395 if (prev < 0)
396 continue;
397
398 let cur_config = config.bss[i];
399 let prev_config = old_config.bss[prev];
400
401 let prev_bss = get_config_bss(old_config, prev);
402 if (!prev_bss)
403 return false;
404
405 // try to preserve MAC address of this BSS by reassigning another
406 // BSS if necessary
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;
411 }
412
413 bss_list[i] = prev_bss;
414 bss_list_cfg[i] = old_config.bss[prev];
415 }
416
417 if (config.mbssid && !bss_list_cfg[0]) {
418 hostapd.printf("First BSS changed with MBSSID enabled");
419 return false;
420 }
421
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)}`);
428 return false;
429 }
430
431 config.bss[0].bssid = old_config.bss[0].bssid;
432 }
433
434 let prev_bss = get_config_bss(old_config, 0);
435 if (!prev_bss)
436 return false;
437
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;
442 }
443
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])
447 continue;
448
449 let prev_bss = get_config_bss(old_config, i);
450 if (!prev_bss)
451 return false;
452
453 let ifname = old_config.bss[i].ifname;
454 hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
455 prev_bss.delete();
456 wdev_remove(ifname);
457 }
458
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++) {
462 if (!bss_list[i])
463 continue;
464
465 let old_ifname = bss_list_cfg[i].ifname;
466 let new_ifname = config.bss[i].ifname;
467 if (old_ifname == new_ifname)
468 continue;
469
470 if (hostapd.bss[new_ifname]) {
471 new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
472 push(rename_list, i);
473 }
474
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}`);
478 return false;
479 }
480
481 bss_list_cfg[i].ifname = new_ifname;
482 }
483
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}`);
489 return false;
490 }
491 bss_list_cfg[i].ifname = new_ifname;
492 }
493
494 // Step 6: assign BSSID for newly created interfaces
495 let macaddr_data = {
496 num_global: config.num_global_macaddr ?? 1,
497 mbssid: config.mbssid ?? 0,
498 };
499 macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
500 for (let i = 0; i < length(config.bss); i++) {
501 if (bss_list[i])
502 continue;
503 let bsscfg = config.bss[i];
504
505 let mac_idx = macaddr_list[bsscfg.bssid];
506 if (mac_idx < 0)
507 macaddr_list[bsscfg.bssid] = i;
508 if (mac_idx == i)
509 continue;
510
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
515 if (!mac_idx < 0)
516 return false;
517
518 bsscfg = config.bss[mac_idx];
519 }
520
521 let addr = phydev.macaddr_next(i);
522 if (!addr) {
523 hostapd.printf(`Failed to generate mac address for phy ${phy}`);
524 return false;
525 }
526 bsscfg.bssid = addr;
527 }
528
529 let config_inline = iface_gen_config(phy, config);
530
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];
535
536 if (bss)
537 continue;
538
539 hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
540 bss_list[i] = iface.add_bss(config_inline, i);
541 if (!bss_list[i]) {
542 hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
543 return false;
544 }
545 }
546
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}'`);
550 return false;
551 }
552
553 // Step 9: update config
554 for (let i = 0; i < length(config.bss); i++) {
555 if (!bss_list_cfg[i])
556 continue;
557
558 let ifname = config.bss[i].ifname;
559 let bss = bss_list[i];
560
561 if (is_equal(config.bss[i], bss_list_cfg[i]))
562 continue;
563
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}`);
569 return false;
570 } else {
571 bss.ctrl("RELOAD_WPA_PSK");
572 continue;
573 }
574 }
575
576 bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
577 if (is_equal(config.bss[i], bss_list_cfg[i]))
578 continue;
579
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}`);
583 return false;
584 }
585 }
586
587 return true;
588 }
589
590 function iface_set_config(phy, config)
591 {
592 let old_config = hostapd.data.config[phy];
593
594 hostapd.data.config[phy] = config;
595
596 if (!config) {
597 hostapd.remove_iface(phy);
598 return iface_remove(old_config);
599 }
600
601 let phydev = phy_open(phy);
602 if (!phydev) {
603 hostapd.printf(`Failed to open phy ${phy}`);
604 return false;
605 }
606
607 try {
608 let ret = iface_reload_config(phydev, config, old_config);
609 if (ret) {
610 iface_update_supplicant_macaddr(phy, config);
611 hostapd.printf(`Reloaded settings for phy ${phy}`);
612 return 0;
613 }
614 } catch (e) {
615 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
616 }
617
618 hostapd.printf(`Restart interface for phy ${phy}`);
619 let ret = iface_restart(phydev, config, old_config);
620
621 return ret;
622 }
623
624 function config_add_bss(config, name)
625 {
626 let bss = {
627 ifname: name,
628 data: [],
629 hash: {}
630 };
631
632 push(config.bss, bss);
633
634 return bss;
635 }
636
637 function iface_load_config(filename)
638 {
639 let f = open(filename, "r");
640 if (!f)
641 return null;
642
643 let config = {
644 radio: {
645 data: []
646 },
647 bss: [],
648 orig_file: filename,
649 };
650
651 let bss;
652 let line;
653 while ((line = rtrim(f.read("line"), "\n")) != null) {
654 let val = split(line, "=", 2);
655 if (!val[0])
656 continue;
657
658 if (val[0] == "interface") {
659 bss = config_add_bss(config, val[1]);
660 break;
661 }
662
663 if (val[0] == "channel") {
664 config.radio.channel = val[1];
665 continue;
666 }
667
668 if (val[0] == "#num_global_macaddr" ||
669 val[0] == "mbssid")
670 config[val[0]] = int(val[1]);
671
672 push(config.radio.data, line);
673 }
674
675 while ((line = rtrim(f.read("line"), "\n")) != null) {
676 if (line == "#default_macaddr")
677 bss.default_macaddr = true;
678
679 let val = split(line, "=", 2);
680 if (!val[0])
681 continue;
682
683 if (val[0] == "bssid") {
684 bss.bssid = lc(val[1]);
685 continue;
686 }
687
688 if (val[0] == "nas_identifier")
689 bss.nasid = val[1];
690
691 if (val[0] == "bss") {
692 bss = config_add_bss(config, val[1]);
693 continue;
694 }
695
696 if (hostapd.data.file_fields[val[0]])
697 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
698
699 push(bss.data, line);
700 }
701 f.close();
702
703 return config;
704 }
705
706 function ex_wrap(func) {
707 return (req) => {
708 try {
709 let ret = func(req);
710 return ret;
711 } catch(e) {
712 hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
713 }
714 return libubus.STATUS_UNKNOWN_ERROR;
715 };
716 }
717
718 let main_obj = {
719 reload: {
720 args: {
721 phy: "",
722 },
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);
729 }
730
731 return 0;
732 })
733 },
734 apsta_state: {
735 args: {
736 phy: "",
737 up: true,
738 frequency: 0,
739 sec_chan_offset: 0,
740 csa: true,
741 csa_count: 0,
742 },
743 call: ex_wrap(function(req) {
744 if (req.args.up == null || !req.args.phy)
745 return libubus.STATUS_INVALID_ARGUMENT;
746
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)
750 return 0;
751
752 let iface = hostapd.interfaces[phy];
753 if (!iface)
754 return 0;
755
756 if (!req.args.up) {
757 iface.stop();
758 return 0;
759 }
760
761 if (!req.args.frequency)
762 return libubus.STATUS_INVALID_ARGUMENT;
763
764 let freq_info = iface_freq_info(iface, config, req.args);
765 if (!freq_info)
766 return libubus.STATUS_UNKNOWN_ERROR;
767
768 let ret;
769 if (req.args.csa) {
770 freq_info.csa_count = req.args.csa_count ?? 10;
771 ret = iface.switch_channel(freq_info);
772 } else {
773 ret = iface.start(freq_info);
774 }
775 if (!ret)
776 return libubus.STATUS_UNKNOWN_ERROR;
777
778 return 0;
779 })
780 },
781 config_get_macaddr_list: {
782 args: {
783 phy: ""
784 },
785 call: ex_wrap(function(req) {
786 let phy = req.args.phy;
787 if (!phy)
788 return libubus.STATUS_INVALID_ARGUMENT;
789
790 let ret = {
791 macaddr: [],
792 };
793
794 let config = hostapd.data.config[phy];
795 if (!config)
796 return ret;
797
798 ret.macaddr = map(config.bss, (bss) => bss.bssid);
799 return ret;
800 })
801 },
802 config_set: {
803 args: {
804 phy: "",
805 config: "",
806 prev_config: "",
807 },
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;
812
813 if (!phy)
814 return libubus.STATUS_INVALID_ARGUMENT;
815
816 if (prev_file && !hostapd.data.config[phy]) {
817 let config = iface_load_config(prev_file);
818 if (config)
819 config.radio.data = [];
820 hostapd.data.config[phy] = config;
821 }
822
823 let config = iface_load_config(file);
824
825 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
826 iface_set_config(phy, config);
827
828 return {
829 pid: hostapd.getpid()
830 };
831 })
832 },
833 config_add: {
834 args: {
835 iface: "",
836 config: "",
837 },
838 call: ex_wrap(function(req) {
839 if (!req.args.iface || !req.args.config)
840 return libubus.STATUS_INVALID_ARGUMENT;
841
842 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
843 return libubus.STATUS_INVALID_ARGUMENT;
844
845 return {
846 pid: hostapd.getpid()
847 };
848 })
849 },
850 config_remove: {
851 args: {
852 iface: ""
853 },
854 call: ex_wrap(function(req) {
855 if (!req.args.iface)
856 return libubus.STATUS_INVALID_ARGUMENT;
857
858 hostapd.remove_iface(req.args.iface);
859 return 0;
860 })
861 },
862 };
863
864 hostapd.data.ubus = ubus;
865 hostapd.data.obj = ubus.publish("hostapd", main_obj);
866 hostapd.udebug_set("hostapd", hostapd.data.ubus);
867
868 function bss_event(type, name, data) {
869 let ubus = hostapd.data.ubus;
870
871 data ??= {};
872 data.name = name;
873 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
874 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
875 }
876
877 return {
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();
883 },
884 bss_add: function(name, obj) {
885 bss_event("add", name);
886 },
887 bss_reload: function(name, obj, reconf) {
888 bss_event("reload", name, { reconf: reconf != 0 });
889 },
890 bss_remove: function(name, obj) {
891 bss_event("remove", name);
892 }
893 };