blob: f288121e9d4b034d051df2334056f7df52795ec9 [file] [log] [blame]
b.liue9582032025-04-17 19:18:16 +08001let libubus = require("ubus");
2import { open, readfile } from "fs";
3import { wdev_create, wdev_set_mesh_params, wdev_remove, is_equal, wdev_set_up, vlist_new, phy_open } from "common";
4
5let ubus = libubus.connect();
6
7wpas.data.config = {};
8wpas.data.iface_phy = {};
9wpas.data.macaddr_list = {};
10
11function iface_stop(iface)
12{
13 let ifname = iface.config.iface;
14
15 if (!iface.running)
16 return;
17
18 delete wpas.data.iface_phy[ifname];
19 wpas.remove_iface(ifname);
20 wdev_remove(ifname);
21 iface.running = false;
22}
23
24function iface_start(phydev, iface, macaddr_list)
25{
26 let phy = phydev.name;
27
28 if (iface.running)
29 return;
30
31 let ifname = iface.config.iface;
32 let wdev_config = {};
33 for (let field in iface.config)
34 wdev_config[field] = iface.config[field];
35 if (!wdev_config.macaddr)
36 wdev_config.macaddr = phydev.macaddr_next();
37
38 wpas.data.iface_phy[ifname] = phy;
39 wdev_remove(ifname);
40 let ret = phydev.wdev_add(ifname, wdev_config);
41 if (ret)
42 wpas.printf(`Failed to create device ${ifname}: ${ret}`);
43 wdev_set_up(ifname, true);
44 wpas.add_iface(iface.config);
45 iface.running = true;
46}
47
48function iface_cb(new_if, old_if)
49{
50 if (old_if && new_if && is_equal(old_if.config, new_if.config)) {
51 new_if.running = old_if.running;
52 return;
53 }
54
55 if (new_if && old_if)
56 wpas.printf(`Update configuration for interface ${old_if.config.iface}`);
57 else if (old_if)
58 wpas.printf(`Remove interface ${old_if.config.iface}`);
59
60 if (old_if)
61 iface_stop(old_if);
62}
63
64function prepare_config(config, radio)
65{
66 config.config_data = readfile(config.config);
67
68 return { config };
69}
70
71function set_config(config_name, phy_name, radio, num_global_macaddr, macaddr_base, config_list)
72{
73 let phy = wpas.data.config[config_name];
74
75 if (radio < 0)
76 radio = null;
77
78 if (!phy) {
79 phy = vlist_new(iface_cb, false);
80 phy.name = phy_name;
81 wpas.data.config[config_name] = phy;
82 }
83
84 phy.radio = radio;
85 phy.num_global_macaddr = num_global_macaddr;
86 phy.macaddr_base = macaddr_base;
87
88 let values = [];
89 for (let config in config_list)
90 push(values, [ config.iface, prepare_config(config) ]);
91
92 phy.update(values);
93}
94
95function start_pending(phy_name)
96{
97 let phy = wpas.data.config[phy_name];
98 let ubus = wpas.data.ubus;
99
100 if (!phy || !phy.data)
101 return;
102
103 let phydev = phy_open(phy.name, phy.radio);
104 if (!phydev) {
105 wpas.printf(`Could not open phy ${phy_name}`);
106 return;
107 }
108
109 let macaddr_list = wpas.data.macaddr_list[phy_name];
110 phydev.macaddr_init(macaddr_list, {
111 num_global: phy.num_global_macaddr,
112 macaddr_base: phy.macaddr_base,
113 });
114
115 for (let ifname in phy.data)
116 iface_start(phydev, phy.data[ifname]);
117}
118
119function phy_name(phy, radio)
120{
121 if (!phy)
122 return null;
123
124 if (radio != null && radio >= 0)
125 phy += "." + radio;
126
127 return phy;
128}
129
130let main_obj = {
131 phy_set_state: {
132 args: {
133 phy: "",
134 radio: 0,
135 stop: true,
136 },
137 call: function(req) {
138 let name = phy_name(req.args.phy, req.args.radio);
139 if (!name || req.args.stop == null)
140 return libubus.STATUS_INVALID_ARGUMENT;
141
142 let phy = wpas.data.config[name];
143 if (!phy)
144 return libubus.STATUS_NOT_FOUND;
145
146 try {
147 if (req.args.stop) {
148 for (let ifname in phy.data)
149 iface_stop(phy.data[ifname]);
150 } else {
151 start_pending(name);
152 }
153 } catch (e) {
154 wpas.printf(`Error chaging state: ${e}\n${e.stacktrace[0].context}`);
155 return libubus.STATUS_INVALID_ARGUMENT;
156 }
157 return 0;
158 }
159 },
160 phy_set_macaddr_list: {
161 args: {
162 phy: "",
163 radio: 0,
164 macaddr: [],
165 },
166 call: function(req) {
167 let phy = phy_name(req.args.phy, req.args.radio);
168 if (!phy)
169 return libubus.STATUS_INVALID_ARGUMENT;
170
171 wpas.data.macaddr_list[phy] = req.args.macaddr;
172 return 0;
173 }
174 },
175 phy_status: {
176 args: {
177 phy: "",
178 radio: 0,
179 },
180 call: function(req) {
181 let phy = phy_name(req.args.phy, req.args.radio);
182 if (!phy)
183 return libubus.STATUS_INVALID_ARGUMENT;
184
185 phy = wpas.data.config[phy];
186 if (!phy)
187 return libubus.STATUS_NOT_FOUND;
188
189 for (let ifname in phy.data) {
190 try {
191 let iface = wpas.interfaces[ifname];
192 if (!iface)
193 continue;
194
195 let status = iface.status();
196 if (!status)
197 continue;
198
199 if (status.state == "INTERFACE_DISABLED")
200 continue;
201
202 status.ifname = ifname;
203 return status;
204 } catch (e) {
205 continue;
206 }
207 }
208
209 return libubus.STATUS_NOT_FOUND;
210 }
211 },
212 config_set: {
213 args: {
214 phy: "",
215 radio: 0,
216 num_global_macaddr: 0,
217 macaddr_base: "",
218 config: [],
219 defer: true,
220 },
221 call: function(req) {
222 let phy = phy_name(req.args.phy, req.args.radio);
223 if (!phy)
224 return libubus.STATUS_INVALID_ARGUMENT;
225
226 wpas.printf(`Set new config for phy ${phy}`);
227 try {
228 if (req.args.config)
229 set_config(phy, req.args.phy, req.args.radio, req.args.num_global_macaddr, req.args.macaddr_base, req.args.config);
230
231 if (!req.args.defer)
232 start_pending(phy);
233 } catch (e) {
234 wpas.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
235 return libubus.STATUS_INVALID_ARGUMENT;
236 }
237
238 return {
239 pid: wpas.getpid()
240 };
241 }
242 },
243 config_add: {
244 args: {
245 driver: "",
246 iface: "",
247 bridge: "",
248 hostapd_ctrl: "",
249 ctrl: "",
250 config: "",
251 },
252 call: function(req) {
253 if (!req.args.iface || !req.args.config)
254 return libubus.STATUS_INVALID_ARGUMENT;
255
256 if (wpas.add_iface(req.args) < 0)
257 return libubus.STATUS_INVALID_ARGUMENT;
258
259 return {
260 pid: wpas.getpid()
261 };
262 }
263 },
264 config_remove: {
265 args: {
266 iface: ""
267 },
268 call: function(req) {
269 if (!req.args.iface)
270 return libubus.STATUS_INVALID_ARGUMENT;
271
272 wpas.remove_iface(req.args.iface);
273 return 0;
274 }
275 },
276 bss_info: {
277 args: {
278 iface: "",
279 },
280 call: function(req) {
281 let ifname = req.args.iface;
282 if (!ifname)
283 return libubus.STATUS_INVALID_ARGUMENT;
284
285 let iface = wpas.interfaces[ifname];
286 if (!iface)
287 return libubus.STATUS_NOT_FOUND;
288
289 let status = iface.ctrl("STATUS");
290 if (!status)
291 return libubus.STATUS_NOT_FOUND;
292
293 let ret = {};
294 status = split(status, "\n");
295 for (let line in status) {
296 line = split(line, "=", 2);
297 ret[line[0]] = line[1];
298 }
299
300 return ret;
301 }
302 },
303};
304
305wpas.data.ubus = ubus;
306wpas.data.obj = ubus.publish("wpa_supplicant", main_obj);
307wpas.udebug_set("wpa_supplicant", wpas.data.ubus);
308
309function iface_event(type, name, data) {
310 let ubus = wpas.data.ubus;
311
312 data ??= {};
313 data.name = name;
314 wpas.data.obj.notify(`iface.${type}`, data, null, null, null, -1);
315 ubus.call("service", "event", { type: `wpa_supplicant.${name}.${type}`, data: {} });
316}
317
318function iface_hostapd_notify(phy, ifname, iface, state)
319{
320 let ubus = wpas.data.ubus;
321 let status = iface.status();
322 let msg = { phy: phy };
323
324 switch (state) {
325 case "DISCONNECTED":
326 case "AUTHENTICATING":
327 case "SCANNING":
328 msg.up = false;
329 break;
330 case "INTERFACE_DISABLED":
331 case "INACTIVE":
332 msg.up = true;
333 break;
334 case "COMPLETED":
335 msg.up = true;
336 msg.frequency = status.frequency;
337 msg.sec_chan_offset = status.sec_chan_offset;
338 break;
339 default:
340 return;
341 }
342
343 ubus.call("hostapd", "apsta_state", msg);
344}
345
346function iface_channel_switch(phy, ifname, iface, info)
347{
348 let msg = {
349 phy: phy,
350 up: true,
351 csa: true,
352 csa_count: info.csa_count ? info.csa_count - 1 : 0,
353 frequency: info.frequency,
354 sec_chan_offset: info.sec_chan_offset,
355 };
356 ubus.call("hostapd", "apsta_state", msg);
357}
358
359return {
360 shutdown: function() {
361 for (let phy in wpas.data.config)
362 set_config(phy, []);
363 wpas.ubus.disconnect();
364 },
365 iface_add: function(name, obj) {
366 iface_event("add", name);
367 },
368 iface_remove: function(name, obj) {
369 iface_event("remove", name);
370 },
371 state: function(ifname, iface, state) {
372 let phy = wpas.data.iface_phy[ifname];
373 if (!phy) {
374 wpas.printf(`no PHY for ifname ${ifname}`);
375 return;
376 }
377
378 iface_hostapd_notify(phy, ifname, iface, state);
379
380 if (state != "COMPLETED")
381 return;
382
383 let phy_data = wpas.data.config[phy];
384 if (!phy_data)
385 return;
386
387 let iface_data = phy_data.data[ifname];
388 if (!iface_data)
389 return;
390
391 let wdev_config = iface_data.config;
392 if (!wdev_config || wdev_config.mode != "mesh")
393 return;
394
395 wdev_set_mesh_params(ifname, wdev_config);
396 },
397 event: function(ifname, iface, ev, info) {
398 let phy = wpas.data.iface_phy[ifname];
399 if (!phy) {
400 wpas.printf(`no PHY for ifname ${ifname}`);
401 return;
402 }
403
404 if (ev == "CH_SWITCH_STARTED")
405 iface_channel_switch(phy, ifname, iface, info);
406 }
407};