diff options
author | Felix Fietkau <nbd@nbd.name> | 2023-08-29 14:32:42 +0200 |
---|---|---|
committer | Felix Fietkau <nbd@nbd.name> | 2023-09-18 16:52:25 +0200 |
commit | 9720b094aef89802327683f25824820581fed0b9 (patch) | |
tree | 1add9555f456586bf676dca47ac674887d6f7cc2 /package/network/services/hostapd/files/common.uc | |
parent | 263583dc1e569fae3f8e99d73f2fd72376421b17 (diff) | |
download | upstream-9720b094aef89802327683f25824820581fed0b9.tar.gz upstream-9720b094aef89802327683f25824820581fed0b9.tar.bz2 upstream-9720b094aef89802327683f25824820581fed0b9.zip |
hostapd: backport from master, including ucode based reload support
This significantly improves config reload behavior and also fixes some
corner cases related to running AP + mesh interfaces at the same time.
Signed-off-by: Felix Fietkau <nbd@nbd.name>
Diffstat (limited to 'package/network/services/hostapd/files/common.uc')
-rw-r--r-- | package/network/services/hostapd/files/common.uc | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/package/network/services/hostapd/files/common.uc b/package/network/services/hostapd/files/common.uc new file mode 100644 index 0000000000..ccffe3eb43 --- /dev/null +++ b/package/network/services/hostapd/files/common.uc @@ -0,0 +1,318 @@ +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, +}; + +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 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, is_equal, vlist_new, phy_is_fullmac, phy_open }; |