hostapd: add ucode support, use ucode for the main ubus object
[openwrt/staging/wigyori.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 } 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 hostapd.remove_iface(cfg.bss[0].ifname);
30 for (let bss in cfg.bss)
31 wdev_remove(bss.ifname);
32 }
33
34 function iface_gen_config(phy, config)
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
45 str += `
46 ${type}=${bss.ifname}
47 ${join("\n", bss.data)}
48 `;
49 }
50
51 return str;
52 }
53
54 function iface_restart(phy, config, old_config)
55 {
56 iface_remove(old_config);
57 iface_remove(config);
58
59 if (!config.bss || !config.bss[0]) {
60 hostapd.printf(`No bss for phy ${phy}`);
61 return;
62 }
63
64 let bss = config.bss[0];
65 let err = wdev_create(phy, bss.ifname, { mode: "ap" });
66 if (err)
67 hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
68 let config_inline = iface_gen_config(phy, config);
69 if (hostapd.add_iface(`bss_config=${bss.ifname}:${config_inline}`) < 0) {
70 hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
71 return;
72 }
73 }
74
75 function array_to_obj(arr, key, start)
76 {
77 let obj = {};
78
79 start ??= 0;
80 for (let i = start; i < length(arr); i++) {
81 let cur = arr[i];
82 obj[cur[key]] = cur;
83 }
84
85 return obj;
86 }
87
88 function find_array_idx(arr, key, val)
89 {
90 for (let i = 0; i < length(arr); i++)
91 if (arr[i][key] == val)
92 return i;
93
94 return -1;
95 }
96
97 function bss_reload_psk(bss, config, old_config)
98 {
99 if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
100 return;
101
102 old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
103 if (!is_equal(old_config, config))
104 return;
105
106 let ret = bss.ctrl("RELOAD_WPA_PSK");
107 ret ??= "failed";
108
109 hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
110 }
111
112 function iface_reload_config(phy, config, old_config)
113 {
114 if (!old_config || !is_equal(old_config.radio, config.radio))
115 return false;
116
117 if (is_equal(old_config.bss, config.bss))
118 return true;
119
120 if (config.bss[0].ifname != old_config.bss[0].ifname)
121 return false;
122
123 let iface = hostapd.interfaces[config.bss[0].ifname];
124 if (!iface)
125 return false;
126
127 let config_inline = iface_gen_config(phy, config);
128
129 bss_reload_psk(iface.bss[0], config.bss[0], old_config.bss[0]);
130 if (!is_equal(config.bss[0], old_config.bss[0])) {
131 if (phy_is_fullmac(phy))
132 return false;
133
134 hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${phy}'`);
135 if (iface.bss[0].set_config(config_inline, 0) < 0) {
136 hostapd.printf(`Failed to set config`);
137 return false;
138 }
139 }
140
141 let bss_list = array_to_obj(iface.bss, "name", 1);
142 let new_cfg = array_to_obj(config.bss, "ifname", 1);
143 let old_cfg = array_to_obj(old_config.bss, "ifname", 1);
144
145 for (let name in old_cfg) {
146 let bss = bss_list[name];
147 if (!bss) {
148 hostapd.printf(`bss '${name}' not found`);
149 return false;
150 }
151
152 if (!new_cfg[name]) {
153 hostapd.printf(`Remove bss '${name}' on phy '${phy}'`);
154 bss.delete();
155 wdev_remove(name);
156 continue;
157 }
158
159 let new_cfg_data = new_cfg[name];
160 delete new_cfg[name];
161
162 if (is_equal(old_cfg[name], new_cfg_data))
163 continue;
164
165 hostapd.printf(`Reload config for bss '${name}' on phy '${phy}'`);
166 let idx = find_array_idx(config.bss, "ifname", name);
167 if (idx < 0) {
168 hostapd.printf(`bss index not found`);
169 return false;
170 }
171
172 if (bss.set_config(config_inline, idx) < 0) {
173 hostapd.printf(`Failed to set config`);
174 return false;
175 }
176 }
177
178 for (let name in new_cfg) {
179 hostapd.printf(`Add bss '${name}' on phy '${phy}'`);
180
181 let idx = find_array_idx(config.bss, "ifname", name);
182 if (idx < 0) {
183 hostapd.printf(`bss index not found`);
184 return false;
185 }
186
187 if (iface.add_bss(config_inline, idx) < 0) {
188 hostapd.printf(`Failed to add bss`);
189 return false;
190 }
191 }
192
193 return true;
194 }
195
196 function iface_set_config(phy, config)
197 {
198 let old_config = hostapd.data.config[phy];
199
200 hostapd.data.config[phy] = config;
201
202 if (!config)
203 return iface_remove(old_config);
204
205 let ret = iface_reload_config(phy, config, old_config);
206 if (ret) {
207 hostapd.printf(`Reloaded settings for phy ${phy}`);
208 return 0;
209 }
210
211 hostapd.printf(`Restart interface for phy ${phy}`);
212 return iface_restart(phy, config, old_config);
213 }
214
215 function config_add_bss(config, name)
216 {
217 let bss = {
218 ifname: name,
219 data: [],
220 hash: {}
221 };
222
223 push(config.bss, bss);
224
225 return bss;
226 }
227
228 function iface_load_config(filename)
229 {
230 let f = open(filename, "r");
231 if (!f)
232 return null;
233
234 let config = {
235 radio: {
236 data: []
237 },
238 bss: [],
239 orig_file: filename,
240 };
241
242 let bss;
243 let line;
244 while ((line = trim(f.read("line"))) != null) {
245 let val = split(line, "=", 2);
246 if (!val[0])
247 continue;
248
249 if (val[0] == "interface") {
250 bss = config_add_bss(config, val[1]);
251 break;
252 }
253
254 if (val[0] == "channel") {
255 config.radio.channel = val[1];
256 continue;
257 }
258
259 push(config.radio.data, line);
260 }
261
262 while ((line = trim(f.read("line"))) != null) {
263 let val = split(line, "=", 2);
264 if (!val[0])
265 continue;
266
267 if (val[0] == "bss") {
268 bss = config_add_bss(config, val[1]);
269 continue;
270 }
271
272 if (hostapd.data.file_fields[val[0]])
273 bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
274
275 push(bss.data, line);
276 }
277 f.close();
278
279 return config;
280 }
281
282
283
284 let main_obj = {
285 reload: {
286 args: {
287 phy: "",
288 },
289 call: function(req) {
290 try {
291 let phy_list = req.args.phy ? [ req.args.phy ] : keys(hostapd.data.config);
292 for (let phy_name in phy_list) {
293 let phy = hostapd.data.config[phy_name];
294 let config = iface_load_config(phy.orig_file);
295 iface_set_config(phy_name, config);
296 }
297 } catch(e) {
298 hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
299 return libubus.STATUS_INVALID_ARGUMENT;
300 }
301
302 return 0;
303 }
304 },
305 config_set: {
306 args: {
307 phy: "",
308 config: "",
309 prev_config: "",
310 },
311 call: function(req) {
312 let phy = req.args.phy;
313 let file = req.args.config;
314 let prev_file = req.args.prev_config;
315
316 if (!phy)
317 return libubus.STATUS_INVALID_ARGUMENT;
318
319 try {
320 if (prev_file && !hostapd.data.config[phy]) {
321 let config = iface_load_config(prev_file);
322 if (config)
323 config.radio.data = [];
324 hostapd.data.config[phy] = config;
325 }
326
327 let config = iface_load_config(file);
328
329 hostapd.printf(`Set new config for phy ${phy}: ${file}`);
330 iface_set_config(phy, config);
331 } catch(e) {
332 hostapd.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
333 return libubus.STATUS_INVALID_ARGUMENT;
334 }
335
336 return {
337 pid: hostapd.getpid()
338 };
339 }
340 },
341 config_add: {
342 args: {
343 iface: "",
344 config: "",
345 },
346 call: function(req) {
347 if (!req.args.iface || !req.args.config)
348 return libubus.STATUS_INVALID_ARGUMENT;
349
350 if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
351 return libubus.STATUS_INVALID_ARGUMENT;
352
353 return {
354 pid: hostapd.getpid()
355 };
356 }
357 },
358 config_remove: {
359 args: {
360 iface: ""
361 },
362 call: function(req) {
363 if (!req.args.iface)
364 return libubus.STATUS_INVALID_ARGUMENT;
365
366 hostapd.remove_iface(req.args.iface);
367 return 0;
368 }
369 },
370 };
371
372 hostapd.data.ubus = ubus;
373 hostapd.data.obj = ubus.publish("hostapd", main_obj);
374
375 function bss_event(type, name, data) {
376 let ubus = hostapd.data.ubus;
377
378 data ??= {};
379 data.name = name;
380 hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
381 ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
382 }
383
384 return {
385 shutdown: function() {
386 for (let phy in hostapd.data.config)
387 iface_set_config(phy, null);
388 hostapd.ubus.disconnect();
389 },
390 bss_add: function(name, obj) {
391 bss_event("add", name);
392 },
393 bss_reload: function(name, obj, reconf) {
394 bss_event("reload", name, { reconf: reconf != 0 });
395 },
396 bss_remove: function(name, obj) {
397 bss_event("remove", name);
398 }
399 };