import * as nl80211 from "nl80211"; import * as rtnl from "rtnl"; import { readfile, glob, basename, readlink } from "fs"; const iftypes = { ap: nl80211.const.NL80211_IFTYPE_AP, mesh: nl80211.const.NL80211_IFTYPE_MESH_POINT, sta: nl80211.const.NL80211_IFTYPE_STATION, adhoc: nl80211.const.NL80211_IFTYPE_ADHOC, monitor: nl80211.const.NL80211_IFTYPE_MONITOR, }; const mesh_params = { mesh_retry_timeout: "retry_timeout", mesh_confirm_timeout: "confirm_timeout", mesh_holding_timeout: "holding_timeout", mesh_max_peer_links: "max_peer_links", mesh_max_retries: "max_retries", mesh_ttl: "ttl", mesh_element_ttl: "element_ttl", mesh_auto_open_plinks: "auto_open_plinks", mesh_hwmp_max_preq_retries: "hwmp_max_preq_retries", mesh_path_refresh_time: "path_refresh_time", mesh_min_discovery_timeout: "min_discovery_timeout", mesh_hwmp_active_path_timeout: "hwmp_active_path_timeout", mesh_hwmp_preq_min_interval: "hwmp_preq_min_interval", mesh_hwmp_net_diameter_traversal_time: "hwmp_net_diam_trvs_time", mesh_hwmp_rootmode: "hwmp_rootmode", mesh_hwmp_rann_interval: "hwmp_rann_interval", mesh_gate_announcements: "gate_announcements", mesh_sync_offset_max_neighor: "sync_offset_max_neighbor", mesh_rssi_threshold: "rssi_threshold", mesh_hwmp_active_path_to_root_timeout: "hwmp_path_to_root_timeout", mesh_hwmp_root_interval: "hwmp_root_interval", mesh_hwmp_confirmation_interval: "hwmp_confirmation_interval", mesh_awake_window: "awake_window", mesh_plink_timeout: "plink_timeout", mesh_fwding: "forwarding", mesh_power_mode: "power_mode", mesh_nolearn: "nolearn" }; function wdev_remove(name) { nl80211.request(nl80211.const.NL80211_CMD_DEL_INTERFACE, 0, { dev: name }); } function __phy_is_fullmac(phyidx) { let data = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, 0, { wiphy: phyidx }); return !data.software_iftypes.ap_vlan; } function phy_is_fullmac(phy) { let phyidx = int(trim(readfile(`/sys/class/ieee80211/${phy}/index`))); return __phy_is_fullmac(phyidx); } function find_reusable_wdev(phyidx) { if (!__phy_is_fullmac(phyidx)) return null; let data = nl80211.request( nl80211.const.NL80211_CMD_GET_INTERFACE, nl80211.const.NLM_F_DUMP, { wiphy: phyidx }); for (let res in data) if (trim(readfile(`/sys/class/net/${res.ifname}/operstate`)) == "down") return res.ifname; return null; } function wdev_create(phy, name, data) { let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`)); wdev_remove(name); if (!iftypes[data.mode]) return `Invalid mode: ${data.mode}`; let req = { wiphy: phyidx, ifname: name, iftype: iftypes[data.mode], }; if (data["4addr"]) req["4addr"] = data["4addr"]; if (data.macaddr) req.mac = data.macaddr; nl80211.error(); let reuse_ifname = find_reusable_wdev(phyidx); if (reuse_ifname && (reuse_ifname == name || rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false)) nl80211.request( nl80211.const.NL80211_CMD_SET_INTERFACE, 0, { wiphy: phyidx, dev: name, iftype: iftypes[data.mode], }); else nl80211.request( nl80211.const.NL80211_CMD_NEW_INTERFACE, nl80211.const.NLM_F_CREATE, req); let error = nl80211.error(); if (error) return error; if (data.powersave != null) { nl80211.request(nl80211.const.NL80211_CMD_SET_POWER_SAVE, 0, { dev: name, ps_state: data.powersave ? 1 : 0}); } return null; } function wdev_set_mesh_params(name, data) { let mesh_cfg = {}; for (let key in mesh_params) { let val = data[key]; if (val == null) continue; mesh_cfg[mesh_params[key]] = int(val); } if (!length(mesh_cfg)) return null; nl80211.request(nl80211.const.NL80211_CMD_SET_MESH_CONFIG, 0, { dev: name, mesh_params: mesh_cfg }); return nl80211.error(); } function phy_sysfs_file(phy, name) { return trim(readfile(`/sys/class/ieee80211/${phy}/${name}`)); } function macaddr_split(str) { return map(split(str, ":"), (val) => hex(val)); } function macaddr_join(addr) { return join(":", map(addr, (val) => sprintf("%02x", val))); } function wdev_macaddr(wdev) { return trim(readfile(`/sys/class/net/${wdev}/address`)); } const phy_proto = { macaddr_init: function(used, options) { this.macaddr_options = options ?? {}; this.macaddr_list = {}; if (type(used) == "object") for (let addr in used) this.macaddr_list[addr] = used[addr]; else for (let addr in used) this.macaddr_list[addr] = -1; this.for_each_wdev((wdev) => { let macaddr = wdev_macaddr(wdev); this.macaddr_list[macaddr] ??= -1; }); return this.macaddr_list; }, macaddr_generate: function(data) { let phy = this.name; let idx = int(data.id ?? 0); let mbssid = int(data.mbssid ?? 0) > 0; let num_global = int(data.num_global ?? 1); let use_global = !mbssid && idx < num_global; let base_addr = phy_sysfs_file(phy, "macaddress"); if (!base_addr) return null; if (!idx && !mbssid) return base_addr; let base_mask = phy_sysfs_file(phy, "address_mask"); if (!base_mask) return null; if (base_mask == "00:00:00:00:00:00" && idx >= num_global) { let addrs = split(phy_sysfs_file(phy, "addresses"), "\n"); if (idx < length(addrs)) return addrs[idx]; base_mask = "ff:ff:ff:ff:ff:ff"; } let addr = macaddr_split(base_addr); let mask = macaddr_split(base_mask); let type; if (mbssid) type = "b5"; else if (use_global) type = "add"; else if (mask[0] > 0) type = "b1"; else if (mask[5] < 0xff) type = "b5"; else type = "add"; switch (type) { case "b1": if (!(addr[0] & 2)) idx--; addr[0] |= 2; addr[0] ^= idx << 2; break; case "b5": if (mbssid) addr[0] |= 2; addr[5] ^= idx; break; default: for (let i = 5; i > 0; i--) { addr[i] += idx; if (addr[i] < 256) break; addr[i] %= 256; } break; } return macaddr_join(addr); }, macaddr_next: function(val) { let data = this.macaddr_options ?? {}; let list = this.macaddr_list; for (let i = 0; i < 32; i++) { data.id = i; let mac = this.macaddr_generate(data); if (!mac) return null; if (list[mac] != null) continue; list[mac] = val != null ? val : -1; return mac; } }, for_each_wdev: function(cb) { let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`); wdevs = map(wdevs, (arg) => basename(arg)); for (let wdev in wdevs) { if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name) continue; cb(wdev); } } }; function phy_open(phy) { let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`); if (!phyidx) return null; return proto({ name: phy, idx: int(phyidx) }, phy_proto); } const vlist_proto = { update: function(values, arg) { let data = this.data; let cb = this.cb; let seq = { }; let new_data = {}; let old_data = {}; this.data = new_data; if (type(values) == "object") { for (let key in values) { old_data[key] = data[key]; new_data[key] = values[key]; delete data[key]; } } else { for (let val in values) { let cur_key = val[0]; let cur_obj = val[1]; old_data[cur_key] = data[cur_key]; new_data[cur_key] = val[1]; delete data[cur_key]; } } for (let key in data) { cb(null, data[key], arg); delete data[key]; } for (let key in new_data) cb(new_data[key], old_data[key], arg); } }; function is_equal(val1, val2) { let t1 = type(val1); if (t1 != type(val2)) return false; if (t1 == "array") { if (length(val1) != length(val2)) return false; for (let i = 0; i < length(val1); i++) if (!is_equal(val1[i], val2[i])) return false; return true; } else if (t1 == "object") { for (let key in val1) if (!is_equal(val1[key], val2[key])) return false; for (let key in val2) if (val1[key] == null) return false; return true; } else { return val1 == val2; } } function vlist_new(cb) { return proto({ cb: cb, data: {} }, vlist_proto); } export { wdev_remove, wdev_create, wdev_set_mesh_params, is_equal, vlist_new, phy_is_fullmac, phy_open };