blob: ccffe3eb4362199aabdb5655a5e85e9d54be746d [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
13function wdev_remove(name)
14{
15 nl80211.request(nl80211.const.NL80211_CMD_DEL_INTERFACE, 0, { dev: name });
16}
17
18function __phy_is_fullmac(phyidx)
19{
20 let data = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, 0, { wiphy: phyidx });
21
22 return !data.software_iftypes.ap_vlan;
23}
24
25function phy_is_fullmac(phy)
26{
27 let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`)));
28
29 return __phy_is_fullmac(phyidx);
30}
31
32function find_reusable_wdev(phyidx)
33{
34 if (!__phy_is_fullmac(phyidx))
35 return null;
36
37 let data = nl80211.request(
38 nl80211.const.NL80211_CMD_GET_INTERFACE,
39 nl80211.const.NLM_F_DUMP,
40 { wiphy: phyidx });
41 for (let res in data)
42 if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down")
43 return res.ifname;
44 return null;
45}
46
47function wdev_create(phy, name, data)
48{
49 let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`));
50
51 wdev_remove(name);
52
53 if (!iftypes[data.mode])
54 return `Invalid mode: ${data.mode}`;
55
56 let req = {
57 wiphy: phyidx,
58 ifname: name,
59 iftype: iftypes[data.mode],
60 };
61
62 if (data["4addr"])
63 req["4addr"] = data["4addr"];
64 if (data.macaddr)
65 req.mac = data.macaddr;
66
67 nl80211.error();
68
69 let reuse_ifname = find_reusable_wdev(phyidx);
70 if (reuse_ifname &&
71 (reuse_ifname == name ||
72 rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false))
73 nl80211.request(
74 nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
75 wiphy: phyidx,
76 dev: name,
77 iftype: iftypes[data.mode],
78 });
79 else
80 nl80211.request(
81 nl80211.const.NL80211_CMD_NEW_INTERFACE,
82 nl80211.const.NLM_F_CREATE,
83 req);
84
85 let error = nl80211.error();
86 if (error)
87 return error;
88
89 if (data.powersave != null) {
90 nl80211.request(nl80211.const.NL80211_CMD_SET_POWER_SAVE, 0,
91 { dev: name, ps_state: data.powersave ? 1 : 0});
92 }
93
94 return null;
95}
96
developere6670672023-10-25 17:01:28 +080097function phy_sysfs_file(phy, name)
98{
99 return trim(readfile(`/sys/class/ieee80211/${phy}/${name}`));
100}
101
102function macaddr_split(str)
103{
104 return map(split(str, ":"), (val) => hex(val));
105}
106
107function macaddr_join(addr)
108{
109 return join(":", map(addr, (val) => sprintf("%02x", val)));
110}
111
112function wdev_macaddr(wdev)
113{
114 return trim(readfile(`/sys/class/net/${wdev}/address`));
115}
116
117const phy_proto = {
118 macaddr_init: function(used, options) {
119 this.macaddr_options = options ?? {};
120 this.macaddr_list = {};
121
122 if (type(used) == "object")
123 for (let addr in used)
124 this.macaddr_list[addr] = used[addr];
125 else
126 for (let addr in used)
127 this.macaddr_list[addr] = -1;
128
129 this.for_each_wdev((wdev) => {
130 let macaddr = wdev_macaddr(wdev);
131 this.macaddr_list[macaddr] ??= -1;
132 });
133
134 return this.macaddr_list;
135 },
136
137 macaddr_generate: function(data) {
138 let phy = this.name;
139 let idx = int(data.id ?? 0);
140 let mbssid = int(data.mbssid ?? 0) > 0;
141 let num_global = int(data.num_global ?? 1);
142 let use_global = !mbssid && idx < num_global;
143
144 let base_addr = phy_sysfs_file(phy, "macaddress");
145 if (!base_addr)
146 return null;
147
148 if (!idx && !mbssid)
149 return base_addr;
150
151 let base_mask = phy_sysfs_file(phy, "address_mask");
152 if (!base_mask)
153 return null;
154
155 if (base_mask == "00:00:00:00:00:00" && idx >= num_global) {
156 let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
157
158 if (idx < length(addrs))
159 return addrs[idx];
160
161 base_mask = "ff:ff:ff:ff:ff:ff";
162 }
163
164 let addr = macaddr_split(base_addr);
165 let mask = macaddr_split(base_mask);
166 let type;
167
168 if (mbssid)
169 type = "b5";
170 else if (use_global)
171 type = "add";
172 else if (mask[0] > 0)
173 type = "b1";
174 else if (mask[5] < 0xff)
175 type = "b5";
176 else
177 type = "add";
178
179 switch (type) {
180 case "b1":
181 if (!(addr[0] & 2))
182 idx--;
183 addr[0] |= 2;
184 addr[0] ^= idx << 2;
185 break;
186 case "b5":
187 if (mbssid)
188 addr[0] |= 2;
189 addr[5] ^= idx;
190 break;
191 default:
192 for (let i = 5; i > 0; i--) {
193 addr[i] += idx;
194 if (addr[i] < 256)
195 break;
196 addr[i] %= 256;
197 }
198 break;
199 }
200
201 return macaddr_join(addr);
202 },
203
204 macaddr_next: function(val) {
205 let data = this.macaddr_options ?? {};
206 let list = this.macaddr_list;
207
208 for (let i = 0; i < 32; i++) {
209 data.id = i;
210
211 let mac = this.macaddr_generate(data);
212 if (!mac)
213 return null;
214
215 if (list[mac] != null)
216 continue;
217
218 list[mac] = val != null ? val : -1;
219 return mac;
220 }
221 },
222
223 for_each_wdev: function(cb) {
224 let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`);
225 wdevs = map(wdevs, (arg) => basename(arg));
226 for (let wdev in wdevs) {
227 if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name)
228 continue;
229
230 cb(wdev);
231 }
232 }
233};
234
235function phy_open(phy)
236{
237 let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
238 if (!phyidx)
239 return null;
240
241 return proto({
242 name: phy,
243 idx: int(phyidx)
244 }, phy_proto);
245}
246
developer2a209692023-08-14 20:23:42 +0800247const vlist_proto = {
248 update: function(values, arg) {
249 let data = this.data;
250 let cb = this.cb;
251 let seq = { };
252 let new_data = {};
253 let old_data = {};
254
255 this.data = new_data;
256
257 if (type(values) == "object") {
258 for (let key in values) {
259 old_data[key] = data[key];
260 new_data[key] = values[key];
261 delete data[key];
262 }
263 } else {
264 for (let val in values) {
265 let cur_key = val[0];
266 let cur_obj = val[1];
267
268 old_data[cur_key] = data[cur_key];
269 new_data[cur_key] = val[1];
270 delete data[cur_key];
271 }
272 }
273
274 for (let key in data) {
275 cb(null, data[key], arg);
276 delete data[key];
277 }
278 for (let key in new_data)
279 cb(new_data[key], old_data[key], arg);
280 }
281};
282
283function is_equal(val1, val2) {
284 let t1 = type(val1);
285
286 if (t1 != type(val2))
287 return false;
288
289 if (t1 == "array") {
290 if (length(val1) != length(val2))
291 return false;
292
293 for (let i = 0; i < length(val1); i++)
294 if (!is_equal(val1[i], val2[i]))
295 return false;
296
297 return true;
298 } else if (t1 == "object") {
299 for (let key in val1)
300 if (!is_equal(val1[key], val2[key]))
301 return false;
302 for (let key in val2)
developere6670672023-10-25 17:01:28 +0800303 if (val1[key] == null)
developer2a209692023-08-14 20:23:42 +0800304 return false;
305 return true;
306 } else {
307 return val1 == val2;
308 }
309}
310
311function vlist_new(cb) {
312 return proto({
313 cb: cb,
314 data: {}
315 }, vlist_proto);
316}
317
developere6670672023-10-25 17:01:28 +0800318export { wdev_remove, wdev_create, is_equal, vlist_new, phy_is_fullmac, phy_open };