blob: 4c33779af935e1051ad220310b2d4d5559aa1054 [file] [log] [blame]
developer2a209692023-08-14 20:23:42 +08001import * as nl80211 from "nl80211";
2import * as rtnl from "rtnl";
developere6670672023-10-25 17:01:28 +08003import { readfile, glob, basename, readlink } from "fs";
developer2a209692023-08-14 20:23:42 +08004
5const iftypes = {
6 ap: nl80211.const.NL80211_IFTYPE_AP,
7 mesh: nl80211.const.NL80211_IFTYPE_MESH_POINT,
8 sta: nl80211.const.NL80211_IFTYPE_STATION,
9 adhoc: nl80211.const.NL80211_IFTYPE_ADHOC,
10 monitor: nl80211.const.NL80211_IFTYPE_MONITOR,
11};
12
developerbf0f2d62023-11-14 17:01:47 +080013const mesh_params = {
14 mesh_retry_timeout: "retry_timeout",
15 mesh_confirm_timeout: "confirm_timeout",
16 mesh_holding_timeout: "holding_timeout",
17 mesh_max_peer_links: "max_peer_links",
18 mesh_max_retries: "max_retries",
19 mesh_ttl: "ttl",
20 mesh_element_ttl: "element_ttl",
21 mesh_auto_open_plinks: "auto_open_plinks",
22 mesh_hwmp_max_preq_retries: "hwmp_max_preq_retries",
23 mesh_path_refresh_time: "path_refresh_time",
24 mesh_min_discovery_timeout: "min_discovery_timeout",
25 mesh_hwmp_active_path_timeout: "hwmp_active_path_timeout",
26 mesh_hwmp_preq_min_interval: "hwmp_preq_min_interval",
27 mesh_hwmp_net_diameter_traversal_time: "hwmp_net_diam_trvs_time",
28 mesh_hwmp_rootmode: "hwmp_rootmode",
29 mesh_hwmp_rann_interval: "hwmp_rann_interval",
30 mesh_gate_announcements: "gate_announcements",
31 mesh_sync_offset_max_neighor: "sync_offset_max_neighbor",
32 mesh_rssi_threshold: "rssi_threshold",
33 mesh_hwmp_active_path_to_root_timeout: "hwmp_path_to_root_timeout",
34 mesh_hwmp_root_interval: "hwmp_root_interval",
35 mesh_hwmp_confirmation_interval: "hwmp_confirmation_interval",
36 mesh_awake_window: "awake_window",
37 mesh_plink_timeout: "plink_timeout",
38 mesh_fwding: "forwarding",
39 mesh_power_mode: "power_mode",
40 mesh_nolearn: "nolearn"
41};
42
developer2a209692023-08-14 20:23:42 +080043function wdev_remove(name)
44{
45 nl80211.request(nl80211.const.NL80211_CMD_DEL_INTERFACE, 0, { dev: name });
46}
47
48function __phy_is_fullmac(phyidx)
49{
50 let data = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, 0, { wiphy: phyidx });
51
52 return !data.software_iftypes.ap_vlan;
53}
54
55function phy_is_fullmac(phy)
56{
57 let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`)));
58
59 return __phy_is_fullmac(phyidx);
60}
61
62function find_reusable_wdev(phyidx)
63{
64 if (!__phy_is_fullmac(phyidx))
65 return null;
66
67 let data = nl80211.request(
68 nl80211.const.NL80211_CMD_GET_INTERFACE,
69 nl80211.const.NLM_F_DUMP,
70 { wiphy: phyidx });
71 for (let res in data)
72 if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down")
73 return res.ifname;
74 return null;
75}
76
77function wdev_create(phy, name, data)
78{
79 let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`));
80
81 wdev_remove(name);
82
83 if (!iftypes[data.mode])
84 return `Invalid mode: ${data.mode}`;
85
86 let req = {
87 wiphy: phyidx,
88 ifname: name,
89 iftype: iftypes[data.mode],
90 };
91
92 if (data["4addr"])
93 req["4addr"] = data["4addr"];
94 if (data.macaddr)
95 req.mac = data.macaddr;
96
97 nl80211.error();
98
99 let reuse_ifname = find_reusable_wdev(phyidx);
100 if (reuse_ifname &&
101 (reuse_ifname == name ||
102 rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false))
103 nl80211.request(
104 nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
105 wiphy: phyidx,
106 dev: name,
107 iftype: iftypes[data.mode],
108 });
109 else
110 nl80211.request(
111 nl80211.const.NL80211_CMD_NEW_INTERFACE,
112 nl80211.const.NLM_F_CREATE,
113 req);
114
115 let error = nl80211.error();
116 if (error)
117 return error;
118
119 if (data.powersave != null) {
120 nl80211.request(nl80211.const.NL80211_CMD_SET_POWER_SAVE, 0,
121 { dev: name, ps_state: data.powersave ? 1 : 0});
122 }
123
124 return null;
125}
126
developerbf0f2d62023-11-14 17:01:47 +0800127function wdev_set_mesh_params(name, data)
128{
129 let mesh_cfg = {};
130
131 for (let key in mesh_params) {
132 let val = data[key];
133 if (val == null)
134 continue;
135 mesh_cfg[mesh_params[key]] = int(val);
136 }
137
138 if (!length(mesh_cfg))
139 return null;
140
141 nl80211.request(nl80211.const.NL80211_CMD_SET_MESH_CONFIG, 0,
142 { dev: name, mesh_params: mesh_cfg });
143
144 return nl80211.error();
145}
146
147function wdev_set_up(name, up)
148{
149 rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: name, change: 1, flags: up ? 1 : 0 });
150}
151
developere6670672023-10-25 17:01:28 +0800152function phy_sysfs_file(phy, name)
153{
154 return trim(readfile(`/sys/class/ieee80211/${phy}/${name}`));
155}
156
157function macaddr_split(str)
158{
159 return map(split(str, ":"), (val) => hex(val));
160}
161
162function macaddr_join(addr)
163{
164 return join(":", map(addr, (val) => sprintf("%02x", val)));
165}
166
167function wdev_macaddr(wdev)
168{
169 return trim(readfile(`/sys/class/net/${wdev}/address`));
170}
171
172const phy_proto = {
173 macaddr_init: function(used, options) {
174 this.macaddr_options = options ?? {};
175 this.macaddr_list = {};
176
177 if (type(used) == "object")
178 for (let addr in used)
179 this.macaddr_list[addr] = used[addr];
180 else
181 for (let addr in used)
182 this.macaddr_list[addr] = -1;
183
184 this.for_each_wdev((wdev) => {
185 let macaddr = wdev_macaddr(wdev);
186 this.macaddr_list[macaddr] ??= -1;
187 });
188
189 return this.macaddr_list;
190 },
191
192 macaddr_generate: function(data) {
193 let phy = this.name;
194 let idx = int(data.id ?? 0);
195 let mbssid = int(data.mbssid ?? 0) > 0;
196 let num_global = int(data.num_global ?? 1);
197 let use_global = !mbssid && idx < num_global;
198
199 let base_addr = phy_sysfs_file(phy, "macaddress");
200 if (!base_addr)
201 return null;
202
203 if (!idx && !mbssid)
204 return base_addr;
205
206 let base_mask = phy_sysfs_file(phy, "address_mask");
207 if (!base_mask)
208 return null;
209
210 if (base_mask == "00:00:00:00:00:00" && idx >= num_global) {
211 let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
212
213 if (idx < length(addrs))
214 return addrs[idx];
215
216 base_mask = "ff:ff:ff:ff:ff:ff";
217 }
218
219 let addr = macaddr_split(base_addr);
220 let mask = macaddr_split(base_mask);
221 let type;
222
223 if (mbssid)
224 type = "b5";
225 else if (use_global)
226 type = "add";
227 else if (mask[0] > 0)
228 type = "b1";
229 else if (mask[5] < 0xff)
230 type = "b5";
231 else
232 type = "add";
233
234 switch (type) {
235 case "b1":
236 if (!(addr[0] & 2))
237 idx--;
238 addr[0] |= 2;
239 addr[0] ^= idx << 2;
240 break;
241 case "b5":
242 if (mbssid)
243 addr[0] |= 2;
244 addr[5] ^= idx;
245 break;
246 default:
247 for (let i = 5; i > 0; i--) {
248 addr[i] += idx;
249 if (addr[i] < 256)
250 break;
251 addr[i] %= 256;
252 }
253 break;
254 }
255
256 return macaddr_join(addr);
257 },
258
259 macaddr_next: function(val) {
260 let data = this.macaddr_options ?? {};
261 let list = this.macaddr_list;
262
263 for (let i = 0; i < 32; i++) {
264 data.id = i;
265
266 let mac = this.macaddr_generate(data);
267 if (!mac)
268 return null;
269
270 if (list[mac] != null)
271 continue;
272
273 list[mac] = val != null ? val : -1;
274 return mac;
275 }
276 },
277
278 for_each_wdev: function(cb) {
279 let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`);
280 wdevs = map(wdevs, (arg) => basename(arg));
281 for (let wdev in wdevs) {
282 if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name)
283 continue;
284
285 cb(wdev);
286 }
287 }
288};
289
290function phy_open(phy)
291{
292 let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
293 if (!phyidx)
294 return null;
295
296 return proto({
297 name: phy,
298 idx: int(phyidx)
299 }, phy_proto);
300}
301
developer2a209692023-08-14 20:23:42 +0800302const vlist_proto = {
303 update: function(values, arg) {
304 let data = this.data;
305 let cb = this.cb;
306 let seq = { };
307 let new_data = {};
308 let old_data = {};
309
310 this.data = new_data;
311
312 if (type(values) == "object") {
313 for (let key in values) {
314 old_data[key] = data[key];
315 new_data[key] = values[key];
316 delete data[key];
317 }
318 } else {
319 for (let val in values) {
320 let cur_key = val[0];
321 let cur_obj = val[1];
322
323 old_data[cur_key] = data[cur_key];
324 new_data[cur_key] = val[1];
325 delete data[cur_key];
326 }
327 }
328
329 for (let key in data) {
330 cb(null, data[key], arg);
331 delete data[key];
332 }
333 for (let key in new_data)
334 cb(new_data[key], old_data[key], arg);
335 }
336};
337
338function is_equal(val1, val2) {
339 let t1 = type(val1);
340
341 if (t1 != type(val2))
342 return false;
343
344 if (t1 == "array") {
345 if (length(val1) != length(val2))
346 return false;
347
348 for (let i = 0; i < length(val1); i++)
349 if (!is_equal(val1[i], val2[i]))
350 return false;
351
352 return true;
353 } else if (t1 == "object") {
354 for (let key in val1)
355 if (!is_equal(val1[key], val2[key]))
356 return false;
357 for (let key in val2)
developere6670672023-10-25 17:01:28 +0800358 if (val1[key] == null)
developer2a209692023-08-14 20:23:42 +0800359 return false;
360 return true;
361 } else {
362 return val1 == val2;
363 }
364}
365
366function vlist_new(cb) {
367 return proto({
368 cb: cb,
369 data: {}
370 }, vlist_proto);
371}
372
developerbf0f2d62023-11-14 17:01:47 +0800373export { wdev_remove, wdev_create, wdev_set_mesh_params, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open };