| 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 wdev_set_up(name, up) |
| { |
| rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: name, change: 1, flags: up ? 1 : 0 }); |
| } |
| |
| 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, wdev_set_up, is_equal, vlist_new, phy_is_fullmac, phy_open }; |