common.uc 9.2 KB

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