wifi-scripts: add new package, move wifi scripts to a single place
Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
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 };
|
||||
@@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env ucode
|
||||
'use strict';
|
||||
import { vlist_new, is_equal, wdev_create, wdev_set_mesh_params, wdev_remove, wdev_set_up, phy_open } from "/usr/share/hostap/common.uc";
|
||||
import { readfile, writefile, basename, readlink, glob } from "fs";
|
||||
let libubus = require("ubus");
|
||||
|
||||
let keep_devices = {};
|
||||
let phy = shift(ARGV);
|
||||
let command = shift(ARGV);
|
||||
let phydev;
|
||||
|
||||
function iface_stop(wdev)
|
||||
{
|
||||
if (keep_devices[wdev.ifname])
|
||||
return;
|
||||
|
||||
wdev_remove(wdev.ifname);
|
||||
}
|
||||
|
||||
function iface_start(wdev)
|
||||
{
|
||||
let ifname = wdev.ifname;
|
||||
|
||||
if (readfile(`/sys/class/net/${ifname}/ifindex`)) {
|
||||
wdev_set_up(ifname, false);
|
||||
wdev_remove(ifname);
|
||||
}
|
||||
let wdev_config = {};
|
||||
for (let key in wdev)
|
||||
wdev_config[key] = wdev[key];
|
||||
if (!wdev_config.macaddr && wdev.mode != "monitor")
|
||||
wdev_config.macaddr = phydev.macaddr_next();
|
||||
wdev_create(phy, ifname, wdev_config);
|
||||
wdev_set_up(ifname, true);
|
||||
if (wdev.freq)
|
||||
system(`iw dev ${ifname} set freq ${wdev.freq} ${wdev.htmode}`);
|
||||
if (wdev.mode == "adhoc") {
|
||||
let cmd = ["iw", "dev", ifname, "ibss", "join", wdev.ssid, wdev.freq, wdev.htmode, "fixed-freq" ];
|
||||
if (wdev.bssid)
|
||||
push(cmd, wdev.bssid);
|
||||
for (let key in [ "beacon-interval", "basic-rates", "mcast-rate", "keys" ])
|
||||
if (wdev[key])
|
||||
push(cmd, key, wdev[key]);
|
||||
system(cmd);
|
||||
} else if (wdev.mode == "mesh") {
|
||||
let cmd = [ "iw", "dev", ifname, "mesh", "join", wdev.ssid, "freq", wdev.freq, wdev.htmode ];
|
||||
for (let key in [ "mcast-rate", "beacon-interval" ])
|
||||
if (wdev[key])
|
||||
push(cmd, key, wdev[key]);
|
||||
system(cmd);
|
||||
|
||||
wdev_set_mesh_params(ifname, wdev);
|
||||
}
|
||||
}
|
||||
|
||||
function iface_cb(new_if, old_if)
|
||||
{
|
||||
if (old_if && new_if && is_equal(old_if, new_if))
|
||||
return;
|
||||
|
||||
if (old_if)
|
||||
iface_stop(old_if);
|
||||
if (new_if)
|
||||
iface_start(new_if);
|
||||
}
|
||||
|
||||
function drop_inactive(config)
|
||||
{
|
||||
for (let key in config) {
|
||||
if (!readfile(`/sys/class/net/${key}/ifindex`))
|
||||
delete config[key];
|
||||
}
|
||||
}
|
||||
|
||||
function add_ifname(config)
|
||||
{
|
||||
for (let key in config)
|
||||
config[key].ifname = key;
|
||||
}
|
||||
|
||||
function delete_ifname(config)
|
||||
{
|
||||
for (let key in config)
|
||||
delete config[key].ifname;
|
||||
}
|
||||
|
||||
function add_existing(phy, config)
|
||||
{
|
||||
let wdevs = glob(`/sys/class/ieee80211/${phy}/device/net/*`);
|
||||
wdevs = map(wdevs, (arg) => basename(arg));
|
||||
for (let wdev in wdevs) {
|
||||
if (config[wdev])
|
||||
continue;
|
||||
|
||||
if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != phy)
|
||||
continue;
|
||||
|
||||
if (trim(readfile(`/sys/class/net/${wdev}/operstate`)) == "down")
|
||||
config[wdev] = {};
|
||||
}
|
||||
}
|
||||
|
||||
function usage()
|
||||
{
|
||||
warn(`Usage: ${basename(sourcepath())} <phy> <command> [<arguments>]
|
||||
|
||||
Commands:
|
||||
set_config <config> [<device]...] - set phy configuration
|
||||
get_macaddr <id> - get phy MAC address for vif index <id>
|
||||
`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
const commands = {
|
||||
set_config: function(args) {
|
||||
let statefile = `/var/run/wdev-${phy}.json`;
|
||||
|
||||
let new_config = shift(args);
|
||||
for (let dev in ARGV)
|
||||
keep_devices[dev] = true;
|
||||
|
||||
if (!new_config)
|
||||
usage();
|
||||
|
||||
new_config = json(new_config);
|
||||
if (!new_config) {
|
||||
warn("Invalid configuration\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let old_config = readfile(statefile);
|
||||
if (old_config)
|
||||
old_config = json(old_config);
|
||||
|
||||
let config = vlist_new(iface_cb);
|
||||
if (type(old_config) == "object")
|
||||
config.data = old_config;
|
||||
|
||||
add_existing(phy, config.data);
|
||||
add_ifname(config.data);
|
||||
drop_inactive(config.data);
|
||||
|
||||
let ubus = libubus.connect();
|
||||
let data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phy });
|
||||
let macaddr_list = [];
|
||||
if (type(data) == "object" && data.macaddr)
|
||||
macaddr_list = data.macaddr;
|
||||
ubus.disconnect();
|
||||
phydev.macaddr_init(macaddr_list);
|
||||
|
||||
add_ifname(new_config);
|
||||
config.update(new_config);
|
||||
|
||||
drop_inactive(config.data);
|
||||
delete_ifname(config.data);
|
||||
writefile(statefile, sprintf("%J", config.data));
|
||||
},
|
||||
get_macaddr: function(args) {
|
||||
let data = {};
|
||||
|
||||
for (let arg in args) {
|
||||
arg = split(arg, "=", 2);
|
||||
data[arg[0]] = arg[1];
|
||||
}
|
||||
|
||||
let macaddr = phydev.macaddr_generate(data);
|
||||
if (!macaddr) {
|
||||
warn(`Could not get MAC address for phy ${phy}\n`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
print(macaddr + "\n");
|
||||
},
|
||||
};
|
||||
|
||||
if (!phy || !command | !commands[command])
|
||||
usage();
|
||||
|
||||
phydev = phy_open(phy);
|
||||
if (!phydev) {
|
||||
warn(`PHY ${phy} does not exist\n`);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
commands[command](ARGV);
|
||||
Reference in New Issue
Block a user