hostapd: allow adding initial AP without breaking STA interface connection
[openwrt/staging/jow.git] / package / network / services / hostapd / files / wpa_supplicant.uc
1 let libubus = require("ubus");
2 import { open, readfile } from "fs";
3 import { wdev_create, wdev_remove, is_equal, vlist_new } from "common";
4
5 let ubus = libubus.connect();
6
7 wpas.data.config = {};
8 wpas.data.iface_phy = {};
9
10 function iface_stop(iface)
11 {
12 let ifname = iface.config.iface;
13
14 if (!iface.running)
15 return;
16
17 delete wpas.data.iface_phy[ifname];
18 wpas.remove_iface(ifname);
19 wdev_remove(ifname);
20 iface.running = false;
21 }
22
23 function iface_start(phy, iface)
24 {
25 if (iface.running)
26 return;
27
28 let ifname = iface.config.iface;
29
30 wpas.data.iface_phy[ifname] = phy;
31 wdev_remove(ifname);
32 let ret = wdev_create(phy, ifname, iface.config);
33 if (ret)
34 wpas.printf(`Failed to create device ${ifname}: ${ret}`);
35 wpas.add_iface(iface.config);
36 iface.running = true;
37 }
38
39 function iface_cb(new_if, old_if)
40 {
41 if (old_if && new_if && is_equal(old_if.config, new_if.config)) {
42 new_if.running = old_if.running;
43 return;
44 }
45
46 if (new_if && old_if)
47 wpas.printf(`Update configuration for interface ${old_if.config.iface}`);
48 else if (old_if)
49 wpas.printf(`Remove interface ${old_if.config.iface}`);
50
51 if (old_if)
52 iface_stop(old_if);
53 }
54
55 function prepare_config(config)
56 {
57 config.config_data = readfile(config.config);
58
59 return { config: config };
60 }
61
62 function set_config(phy_name, config_list)
63 {
64 let phy = wpas.data.config[phy_name];
65
66 if (!phy) {
67 phy = vlist_new(iface_cb, false);
68 wpas.data.config[phy_name] = phy;
69 }
70
71 let values = [];
72 for (let config in config_list)
73 push(values, [ config.iface, prepare_config(config) ]);
74
75 phy.update(values);
76 }
77
78 function start_pending(phy_name)
79 {
80 let phy = wpas.data.config[phy_name];
81
82 for (let ifname in phy.data)
83 iface_start(phy_name, phy.data[ifname]);
84 }
85
86 let main_obj = {
87 phy_set_state: {
88 args: {
89 phy: "",
90 stop: true,
91 },
92 call: function(req) {
93 if (!req.args.phy || req.args.stop == null)
94 return libubus.STATUS_INVALID_ARGUMENT;
95
96 let phy = wpas.data.config[req.args.phy];
97 if (!phy)
98 return libubus.STATUS_NOT_FOUND;
99
100 try {
101 if (req.args.stop) {
102 for (let ifname in phy.data)
103 iface_stop(phy.data[ifname]);
104 } else {
105 start_pending(req.args.phy);
106 }
107 } catch (e) {
108 wpas.printf(`Error chaging state: ${e}\n${e.stacktrace[0].context}`);
109 return libubus.STATUS_INVALID_ARGUMENT;
110 }
111 return 0;
112 }
113 },
114 phy_status: {
115 args: {
116 phy: ""
117 },
118 call: function(req) {
119 if (!req.args.phy)
120 return libubus.STATUS_INVALID_ARGUMENT;
121
122 let phy = wpas.data.config[req.args.phy];
123 if (!phy)
124 return libubus.STATUS_NOT_FOUND;
125
126 for (let ifname in phy.data) {
127 try {
128 let iface = wpas.interfaces[ifname];
129 if (!iface)
130 continue;
131
132 let status = iface.status();
133 if (!status)
134 continue;
135
136 if (status.state == "INTERFACE_DISABLED")
137 continue;
138
139 status.ifname = ifname;
140 return status;
141 } catch (e) {
142 continue;
143 }
144 }
145
146 return libubus.STATUS_NOT_FOUND;
147 }
148 },
149 config_set: {
150 args: {
151 phy: "",
152 config: [],
153 defer: true,
154 },
155 call: function(req) {
156 if (!req.args.phy)
157 return libubus.STATUS_INVALID_ARGUMENT;
158
159 wpas.printf(`Set new config for phy ${req.args.phy}`);
160 try {
161 if (req.args.config)
162 set_config(req.args.phy, req.args.config);
163
164 if (!req.args.defer)
165 start_pending(req.args.phy);
166 } catch (e) {
167 wpas.printf(`Error loading config: ${e}\n${e.stacktrace[0].context}`);
168 return libubus.STATUS_INVALID_ARGUMENT;
169 }
170
171 return {
172 pid: wpas.getpid()
173 };
174 }
175 },
176 config_add: {
177 args: {
178 driver: "",
179 iface: "",
180 bridge: "",
181 hostapd_ctrl: "",
182 ctrl: "",
183 config: "",
184 },
185 call: function(req) {
186 if (!req.args.iface || !req.args.config)
187 return libubus.STATUS_INVALID_ARGUMENT;
188
189 if (wpas.add_iface(req.args) < 0)
190 return libubus.STATUS_INVALID_ARGUMENT;
191
192 return {
193 pid: wpas.getpid()
194 };
195 }
196 },
197 config_remove: {
198 args: {
199 iface: ""
200 },
201 call: function(req) {
202 if (!req.args.iface)
203 return libubus.STATUS_INVALID_ARGUMENT;
204
205 wpas.remove_iface(req.args.iface);
206 return 0;
207 }
208 },
209 };
210
211 wpas.data.ubus = ubus;
212 wpas.data.obj = ubus.publish("wpa_supplicant", main_obj);
213
214 function iface_event(type, name, data) {
215 let ubus = wpas.data.ubus;
216
217 data ??= {};
218 data.name = name;
219 wpas.data.obj.notify(`iface.${type}`, data, null, null, null, -1);
220 ubus.call("service", "event", { type: `wpa_supplicant.${name}.${type}`, data: {} });
221 }
222
223 function iface_hostapd_notify(phy, ifname, iface, state)
224 {
225 let ubus = wpas.data.ubus;
226 let status = iface.status();
227 let msg = { phy: phy };
228
229 switch (state) {
230 case "DISCONNECTED":
231 case "AUTHENTICATING":
232 case "SCANNING":
233 msg.up = false;
234 break;
235 case "INTERFACE_DISABLED":
236 case "INACTIVE":
237 msg.up = true;
238 break;
239 case "COMPLETED":
240 msg.up = true;
241 msg.frequency = status.frequency;
242 msg.sec_chan_offset = status.sec_chan_offset;
243 break;
244 default:
245 return;
246 }
247
248 ubus.call("hostapd", "apsta_state", msg);
249 }
250
251 function iface_channel_switch(phy, ifname, iface, info)
252 {
253 let msg = {
254 phy: phy,
255 up: true,
256 csa: true,
257 csa_count: info.csa_count ? info.csa_count - 1 : 0,
258 frequency: info.frequency,
259 sec_chan_offset: info.sec_chan_offset,
260 };
261 ubus.call("hostapd", "apsta_state", msg);
262 }
263
264 return {
265 shutdown: function() {
266 for (let phy in wpas.data.config)
267 set_config(phy, []);
268 wpas.ubus.disconnect();
269 },
270 iface_add: function(name, obj) {
271 iface_event("add", name);
272 },
273 iface_remove: function(name, obj) {
274 iface_event("remove", name);
275 },
276 state: function(ifname, iface, state) {
277 let phy = wpas.data.iface_phy[ifname];
278 if (!phy) {
279 wpas.printf(`no PHY for ifname ${ifname}`);
280 return;
281 }
282
283 iface_hostapd_notify(phy, ifname, iface, state);
284 },
285 event: function(ifname, iface, ev, info) {
286 let phy = wpas.data.iface_phy[ifname];
287 if (!phy) {
288 wpas.printf(`no PHY for ifname ${ifname}`);
289 return;
290 }
291
292 if (ev == "CH_SWITCH_STARTED")
293 iface_channel_switch(phy, ifname, iface, info);
294 }
295 };