hostapd: use new udebug ubus api to make debug rings configurable
[openwrt/staging/stintel.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();
6
7 hostapd.data.config = {};
8
9 hostapd.data.file_fields = {
10 vlan_file: true,
11 wpa_psk_file: true,
12 accept_mac_file: true,
13 deny_mac_file: true,
14 eap_user_file: true,
15 ca_cert: true,
16 server_cert: true,
17 server_cert2: true,
18 private_key: true,
19 private_key2: true,
20 dh_file: true,
21 eap_sim_db: true,
22 };
23
24 function iface_remove(cfg)
25 {
26 if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
27 return;
28
29 for (let bss in cfg.bss)
30 wdev_remove(bss.ifname);
31 }
32
33 function iface_gen_config(phy, config, start_disabled)
34 {
35 let str = `data:
36 ${join("\n", config.radio.data)}
37 channel=${config.radio.channel}
38 `;
39
40 for (let i = 0; i < length(config.bss); i++) {
41 let bss = config.bss[i];
42 let type = i > 0 ? "bss" : "interface";
43 let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
44
45 str += `
46 ${type}=${bss.ifname}
47 bssid=${bss.bssid}
48 ${join("\n", bss.data)}
49 nas_identifier=${nasid}
50 `;
51 if (start_disabled)
52 str += `
53 start_disabled=1
54 `;
55 }
56
57 return str;
58 }
59
60 function iface_freq_info(iface, config, params)
61 {
62 let freq = params.frequency;
63 if (!freq)
64 return null;
65
66 let sec_offset = params.sec_chan_offset;
67 if (sec_offset != -1 && sec_offset != 1)
68 sec_offset = 0;
69
70 let width = 0;
71 for (let line in config.radio.data) {
72 if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
73 sec_offset = null; // auto-detect
74 continue;
75 }
76
77 let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
78 if (!val)
79 continue;
80
81 val = int(val[2]);
82 if (val > width)
83 width = val;
84 }
85
86 if (freq < 4000)
87 width = 0;
88
89 return hostapd.freq_info(freq, sec_offset, width);
90 }
91
92 function iface_add(phy, config, phy_status)
93 {
94 let config_inline = iface_gen_config(phy, config, !!phy_status);
95
96 let bss = config.bss[0];
97 let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
98 if (ret < 0)
99 return false;
100
101 if (!phy_status)
102 return true;
103
104 let iface = hostapd.interfaces[phy];
105 if (!iface)
106 return false;
107
108 let freq_info = iface_freq_info(iface, config, phy_status);
109
110 return iface.start(freq_info) >= 0;
111 }
112
113 function iface_config_macaddr_list(config)
114 {
115 let macaddr_list = {};
116 for (let i = 0; i < length(config.bss); i++) {
117 let bss = config.bss[i];
118 if (!bss.default_macaddr)
119 macaddr_list[bss.bssid] = i;
120 }
121
122 return macaddr_list;
123 }
124
125 function iface_update_supplicant_macaddr(phy, config)
126 {
127 let macaddr_list = [];
128 for (let i = 0; i < length(config.bss); i++)
129 push(macaddr_list, config.bss[i].bssid);
130 ubus.call("wpa_supplicant", "phy_set_macaddr_list", { phy: phy, macaddr: macaddr_list });
131 }
132
133 function iface_restart(phydev, config, old_config)
134 {
135 let phy = phydev.name;
136
137 hostapd.remove_iface(phy);
138 iface_remove(old_config);
139 iface_remove(config);
140
141 if (!config.bss || !config.bss[0]) {
142 hostapd.printf(`No bss for phy ${phy}`);
143 return;
144 }
145
146 phydev.macaddr_init(iface_config_macaddr_list(config));
147 for (let i = 0; i < length(config.bss); i++) {
148 let bss = config.bss[i];
149 if (bss.default_macaddr)
150 bss.bssid = phydev.macaddr_next();
151 }
152
153 iface_update_supplicant_macaddr(phy, config);
154
155 let bss = config.bss[0];
156 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
157 if (err)
158 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
159
160 let ubus = hostapd.data.ubus;
161 let phy_status = ubus.call("wpa_supplicant", "phy_status", { phy: phy });
162 if (phy_status && phy_status.state == "COMPLETED") {
163 if (iface_add(phy, config, phy_status))
164 return;
165
166 hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
167 }
168
169 ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: true });
170 if (!iface_add(phy, config))
171 hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
172 ubus.call("wpa_supplicant", "phy_set_state", { phy: phy, stop: false });
173 }
174
175 function array_to_obj(arr, key, start)
176 {
177 let obj = {};
178
179 start ??= 0;
180 for (let i = start; i < length(arr); i++) {
181 let cur = arr[i];
182 obj[cur[key]] = cur;
183 }
184
185 return obj;
186 }
187
188 function find_array_idx(arr, key, val)
189 {
190 for (let i = 0; i < length(arr); i++)
191 if (arr[i][key] == val)
192 return i;
193
194 return -1;
195 }
196
197 function bss_reload_psk(bss, config, old_config)
198 {
199 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
200 return;
201
202 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
203 if (!is_equal(old_config, config))
204 return;
205
206 let ret = bss.ctrl("RELOAD_WPA_PSK");
207 ret ??= "failed";
208
209 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
210 }
211
212 function remove_file_fields(config)
213 {
214 return filter(config, (line) => !hostapd.data.file_fields[split(line, "=")[0]]);
215 }
216
217 function bss_remove_file_fields(config)
218 {
219 let new_cfg = {};
220
221 for (let key in config)
222 new_cfg[key] = config[key];
223 new_cfg.data = remove_file_fields(new_cfg.data);
224 new_cfg.hash = {};
225 for (let key in config.hash)
226 new_cfg.hash[key] = config.hash[key];
227 delete new_cfg.hash.wpa_psk_file;
228 delete new_cfg.hash.vlan_file;
229
230 return new_cfg;
231 }
232
233 function bss_config_hash(config)
234 {
235 return hostapd.sha1(remove_file_fields(config) + "");
236 }
237
238 function bss_find_existing(config, prev_config, prev_hash)
239 {
240 let hash = bss_config_hash(config.data);
241
242 for (let i = 0; i < length(prev_config.bss); i++) {
243 if (!prev_hash[i] || hash != prev_hash[i])
244 continue;
245
246 prev_hash[i] = null;
247 return i;
248 }
249
250 return -1;
251 }
252
253 function get_config_bss(config, idx)
254 {
255 if (!config.bss[idx]) {
256 hostapd.printf(`Invalid bss index ${idx}`);
257 return null;
258 }
259
260 let ifname = config.bss[idx].ifname;
261 if (!ifname)
262 hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
263
264 return hostapd.bss[ifname];
265 }
266
267 function iface_reload_config(phydev, config, old_config)
268 {
269 let phy = phydev.name;
270
271 if (!old_config || !is_equal(old_config.radio, config.radio))
272 return false;
273
274 if (is_equal(old_config.bss, config.bss))
275 return true;
276
277 if (!old_config.bss || !old_config.bss[0])
278 return false;
279
280 let iface = hostapd.interfaces[phy];
281 let iface_name = old_config.bss[0].ifname;
282 if (!iface) {
283 hostapd.printf(`Could not find previous interface ${iface_name}`);
284 return false;
285 }
286
287 let first_bss = hostapd.bss[iface_name];
288 if (!first_bss) {
289 hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
290 return false;
291 }
292
293 let macaddr_list = iface_config_macaddr_list(config);
294 let bss_list = [];
295 let bss_list_cfg = [];
296 let prev_bss_hash = [];
297
298 for (let bss in old_config.bss) {
299 let hash = bss_config_hash(bss.data);
300 push(prev_bss_hash, bss_config_hash(bss.data));
301 }
302
303 // Step 1: find (possibly renamed) interfaces with the same config
304 // and store them in the new order (with gaps)
305 for (let i = 0; i < length(config.bss); i++) {
306 let prev;
307
308 // For fullmac devices, the first interface needs to be preserved,
309 // since it's treated as the master
310 if (!i && phy_is_fullmac(phy)) {
311 prev = 0;
312 prev_bss_hash[0] = null;
313 } else {
314 prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
315 }
316 if (prev < 0)
317 continue;
318
319 let cur_config = config.bss[i];
320 let prev_config = old_config.bss[prev];
321
322 let prev_bss = get_config_bss(old_config, prev);
323 if (!prev_bss)
324 return false;
325
326 // try to preserve MAC address of this BSS by reassigning another
327 // BSS if necessary
328 if (cur_config.default_macaddr &&
329 !macaddr_list[prev_config.bssid]) {
330 macaddr_list[prev_config.bssid] = i;
331 cur_config.bssid = prev_config.bssid;
332 }
333
334 bss_list[i] = prev_bss;
335 bss_list_cfg[i] = old_config.bss[prev];
336 }
337
338 if (config.mbssid && !bss_list_cfg[0]) {
339 hostapd.printf("First BSS changed with MBSSID enabled");
340 return false;
341 }
342
343 // Step 2: if none were found, rename and preserve the first one
344 if (length(bss_list) == 0) {
345 // can't change the bssid of the first bss
346 if (config.bss[0].bssid != old_config.bss[0].bssid) {
347 if (!config.bss[0].default_macaddr) {
348 hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
349 return false;
350 }
351
352 config.bss[0].bssid = old_config.bss[0].bssid;
353 }
354
355 let prev_bss = get_config_bss(old_config, 0);
356 if (!prev_bss)
357 return false;
358
359 macaddr_list[config.bss[0].bssid] = 0;
360 bss_list[0] = prev_bss;
361 bss_list_cfg[0] = old_config.bss[0];
362 prev_bss_hash[0] = null;
363 }
364
365 // Step 3: delete all unused old interfaces
366 for (let i = 0; i < length(prev_bss_hash); i++) {
367 if (!prev_bss_hash[i])
368 continue;
369
370 let prev_bss = get_config_bss(old_config, i);
371 if (!prev_bss)
372 return false;
373
374 let ifname = old_config.bss[i].ifname;
375 hostapd.printf(`Remove bss '${ifname}' on phy '${phy}'`);
376 prev_bss.delete();
377 wdev_remove(ifname);
378 }
379
380 // Step 4: rename preserved interfaces, use temporary name on duplicates
381 let rename_list = [];
382 for (let i = 0; i < length(bss_list); i++) {
383 if (!bss_list[i])
384 continue;
385
386 let old_ifname = bss_list_cfg[i].ifname;
387 let new_ifname = config.bss[i].ifname;
388 if (old_ifname == new_ifname)
389 continue;
390
391 if (hostapd.bss[new_ifname]) {
392 new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
393 push(rename_list, i);
394 }
395
396 hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
397 if (!bss_list[i].rename(new_ifname)) {
398 hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
399 return false;
400 }
401
402 bss_list_cfg[i].ifname = new_ifname;
403 }
404
405 // Step 5: rename interfaces with temporary names
406 for (let i in rename_list) {
407 let new_ifname = config.bss[i].ifname;
408 if (!bss_list[i].rename(new_ifname)) {
409 hostapd.printf(`Failed to rename bss to ${new_ifname}`);
410 return false;
411 }
412 bss_list_cfg[i].ifname = new_ifname;
413 }
414
415 // Step 6: assign BSSID for newly created interfaces
416 let macaddr_data = {
417 num_global: config.num_global_macaddr ?? 1,
418 mbssid: config.mbssid ?? 0,
419 };
420 macaddr_list = phydev.macaddr_init(macaddr_list, macaddr_data);
421 for (let i = 0; i < length(config.bss); i++) {
422 if (bss_list[i])
423 continue;
424 let bsscfg = config.bss[i];
425
426 let mac_idx = macaddr_list[bsscfg.bssid];
427 if (mac_idx < 0)
428 macaddr_list[bsscfg.bssid] = i;
429 if (mac_idx == i)
430 continue;
431
432 // statically assigned bssid of the new interface is in conflict
433 // with the bssid of a reused interface. reassign the reused interface
434 if (!bsscfg.default_macaddr) {
435 // can't update bssid of the first BSS, need to restart
436 if (!mac_idx < 0)
437 return false;
438
439 bsscfg = config.bss[mac_idx];
440 }
441
442 let addr = phydev.macaddr_next(i);
443 if (!addr) {
444 hostapd.printf(`Failed to generate mac address for phy ${phy}`);
445 return false;
446 }
447 bsscfg.bssid = addr;
448 }
449
450 let config_inline = iface_gen_config(phy, config);
451
452 // Step 7: fill in the gaps with new interfaces
453 for (let i = 0; i < length(config.bss); i++) {
454 let ifname = config.bss[i].ifname;
455 let bss = bss_list[i];
456
457 if (bss)
458 continue;
459
460 hostapd.printf(`Add bss ${ifname} on phy ${phy}`);
461 bss_list[i] = iface.add_bss(config_inline, i);
462 if (!bss_list[i]) {
463 hostapd.printf(`Failed to add new bss ${ifname} on phy ${phy}`);
464 return false;
465 }
466 }
467
468 // Step 8: update interface bss order
469 if (!iface.set_bss_order(bss_list)) {
470 hostapd.printf(`Failed to update BSS order on phy '${phy}'`);
471 return false;
472 }
473
474 // Step 9: update config
475 for (let i = 0; i < length(config.bss); i++) {
476 if (!bss_list_cfg[i])
477 continue;
478
479 let ifname = config.bss[i].ifname;
480 let bss = bss_list[i];
481
482 if (is_equal(config.bss[i], bss_list_cfg[i]))
483 continue;
484
485 if (is_equal(bss_remove_file_fields(config.bss[i]),
486 bss_remove_file_fields(bss_list_cfg[i]))) {
487 hostapd.printf(`Update config data files for bss ${ifname}`);
488 if (bss.set_config(config_inline, i, true) < 0) {
489 hostapd.printf(`Could not update config data files for bss ${ifname}`);
490 return false;
491 } else {
492 bss.ctrl("RELOAD_WPA_PSK");
493 continue;
494 }
495 }
496
497 bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
498 if (is_equal(config.bss[i], bss_list_cfg[i]))
499 continue;
500
501 hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
502 if (bss.set_config(config_inline, i) < 0) {
503 hostapd.printf(`Failed to set config for bss ${ifname}`);
504 return false;
505 }
506 }
507
508 return true;
509 }
510
511 function iface_set_config(phy, config)
512 {
513 let old_config = hostapd.data.config[phy];
514
515 hostapd.data.config[phy] = config;
516
517 if (!config) {
518 hostapd.remove_iface(phy);
519 return iface_remove(old_config);
520 }
521
522 let phydev = phy_open(phy);
523 if (!phydev) {
524 hostapd.printf(`Failed to open phy ${phy}`);
525 return false;
526 }
527
528 try {
529 let ret = iface_reload_config(phydev, config, old_config);
530 if (ret) {
531 iface_update_supplicant_macaddr(phy, config);
532 hostapd.printf(`Reloaded settings for phy ${phy}`);
533 return 0;
534 }
535 } catch (e) {
536 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
537 }
538
539 hostapd.printf(`Restart interface for phy ${phy}`);
540 let ret = iface_restart(phydev, config, old_config);
541
542 return ret;
543 }
544
545 function config_add_bss(config, name)
546 {
547 let bss = {
548 ifname: name,
549 data: [],
550 hash: {}
551 };
552
553 push(config.bss, bss);
554
555 return bss;
556 }
557
558 function iface_load_config(filename)
559 {
560 let f = open(filename, "r");
561 if (!f)
562 return null;
563
564 let config = {
565 radio: {
566 data: []
567 },
568 bss: [],
569 orig_file: filename,
570 };
571
572 let bss;
573 let line;
574 while ((line = rtrim(f.read("line"), "\n")) != null) {
575 let val = split(line, "=", 2);
576 if (!val[0])
577 continue;
578
579 if (val[0] == "interface") {
580 bss = config_add_bss(config, val[1]);
581 break;
582 }
583
584 if (val[0] == "channel") {
585 config.radio.channel = val[1];
586 continue;
587 }
588
589 if (val[0] == "#num_global_macaddr" ||
590 val[0] == "mbssid")
591 config[val[0]] = int(val[1]);
592
593 push(config.radio.data, line);
594 }
595
596 while ((line = rtrim(f.read("line"), "\n")) != null) {
597 if (line == "#default_macaddr")
598 bss.default_macaddr = true;
599
600 let val = split(line, "=", 2);
601 if (!val[0])
602 continue;
603
604 if (val[0] == "bssid") {
605 bss.bssid = lc(val[1]);
606 continue;
607 }
608
609 if (val[0] == "nas_identifier")
610 bss.nasid = val[1];
611
612 if (val[0] == "bss") {
613 bss = config_add_bss(config, val[1]);
614 continue;
615 }
616
617 if (hostapd.data.file_fields[val[0]])
618 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
619
620 push(bss.data, line);
621 }
622 f.close();
623
624 return config;
625 }
626
627 function ex_wrap(func) {
628 return (req) => {
629 try {
630 let ret = func(req);
631 return ret;
632 } catch(e) {
633 hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
634 }
635 return libubus.STATUS_UNKNOWN_ERROR;
636 };
637 }
638
639 let main_obj = {
640 reload: {
641 args: {
642 phy: "",
643 },
644 call: ex_wrap(function(req) {
645 let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
646 for (let phy_name in phy_list) {
647 let phy = hostapd.data.config[phy_name];
648 let config = iface_load_config(phy.orig_file);
649 iface_set_config(phy_name, config);
650 }
651
652 return 0;
653 })
654 },
655 apsta_state: {
656 args: {
657 phy: "",
658 up: true,
659 frequency: 0,
660 sec_chan_offset: 0,
661 csa: true,
662 csa_count: 0,
663 },
664 call: ex_wrap(function(req) {
665 if (req.args.up == null || !req.args.phy)
666 return libubus.STATUS_INVALID_ARGUMENT;
667
668 let phy = req.args.phy;
669 let config = hostapd.data.config[phy];
670 if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
671 return 0;
672
673 let iface = hostapd.interfaces[phy];
674 if (!iface)
675 return 0;
676
677 if (!req.args.up) {
678 iface.stop();
679 return 0;
680 }
681
682 if (!req.args.frequency)
683 return libubus.STATUS_INVALID_ARGUMENT;
684
685 let freq_info = iface_freq_info(iface, config, req.args);
686 if (!freq_info)
687 return libubus.STATUS_UNKNOWN_ERROR;
688
689 let ret;
690 if (req.args.csa) {
691 freq_info.csa_count = req.args.csa_count ?? 10;
692 ret = iface.switch_channel(freq_info);
693 } else {
694 ret = iface.start(freq_info);
695 }
696 if (!ret)
697 return libubus.STATUS_UNKNOWN_ERROR;
698
699 return 0;
700 })
701 },
702 config_get_macaddr_list: {
703 args: {
704 phy: ""
705 },
706 call: ex_wrap(function(req) {
707 let phy = req.args.phy;
708 if (!phy)
709 return libubus.STATUS_INVALID_ARGUMENT;
710
711 let ret = {
712 macaddr: [],
713 };
714
715 let config = hostapd.data.config[phy];
716 if (!config)
717 return ret;
718
719 ret.macaddr = map(config.bss, (bss) => bss.bssid);
720 return ret;
721 })
722 },
723 config_set: {
724 args: {
725 phy: "",
726 config: "",
727 prev_config: "",
728 },
729 call: ex_wrap(function(req) {
730 let phy = req.args.phy;
731 let file = req.args.config;
732 let prev_file = req.args.prev_config;
733
734 if (!phy)
735 return libubus.STATUS_INVALID_ARGUMENT;
736
737 if (prev_file && !hostapd.data.config[phy]) {
738 let config = iface_load_config(prev_file);
739 if (config)
740 config.radio.data = [];
741 hostapd.data.config[phy] = config;
742 }
743
744 let config = iface_load_config(file);
745
746 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
747 iface_set_config(phy, config);
748
749 return {
750 pid: hostapd.getpid()
751 };
752 })
753 },
754 config_add: {
755 args: {
756 iface: "",
757 config: "",
758 },
759 call: ex_wrap(function(req) {
760 if (!req.args.iface || !req.args.config)
761 return libubus.STATUS_INVALID_ARGUMENT;
762
763 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
764 return libubus.STATUS_INVALID_ARGUMENT;
765
766 return {
767 pid: hostapd.getpid()
768 };
769 })
770 },
771 config_remove: {
772 args: {
773 iface: ""
774 },
775 call: ex_wrap(function(req) {
776 if (!req.args.iface)
777 return libubus.STATUS_INVALID_ARGUMENT;
778
779 hostapd.remove_iface(req.args.iface);
780 return 0;
781 })
782 },
783 };
784
785 hostapd.data.ubus = ubus;
786 hostapd.data.obj = ubus.publish("hostapd", main_obj);
787 hostapd.udebug_set("hostapd", hostapd.data.ubus);
788
789 function bss_event(type, name, data) {
790 let ubus = hostapd.data.ubus;
791
792 data ??= {};
793 data.name = name;
794 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
795 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
796 }
797
798 return {
799 shutdown: function() {
800 for (let phy in hostapd.data.config)
801 iface_set_config(phy, null);
802 hostapd.udebug_set(null);
803 hostapd.ubus.disconnect();
804 },
805 bss_add: function(name, obj) {
806 bss_event("add", name);
807 },
808 bss_reload: function(name, obj, reconf) {
809 bss_event("reload", name, { reconf: reconf != 0 });
810 },
811 bss_remove: function(name, obj) {
812 bss_event("remove", name);
813 }
814 };