hostapd.uc 23 KB

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