wifi-scripts: add multi-radio config support

Emit one wifi-device section per wiphy radio

Signed-off-by: Felix Fietkau <nbd@nbd.name>
This commit is contained in:
Felix Fietkau
2024-06-12 15:06:05 +02:00
parent cb60bee04d
commit 04fb05914e
7 changed files with 370 additions and 189 deletions

View File

@@ -29,7 +29,7 @@ drv_mac80211_init_device_config() {
config_add_string path phy 'macaddr:macaddr'
config_add_string tx_burst
config_add_string distance
config_add_int beacon_int chanbw frag rts
config_add_int radio beacon_int chanbw frag rts
config_add_int rxantenna txantenna txpower min_tx_power
config_add_int num_global_macaddr multiple_bssid
config_add_boolean noscan ht_coex acs_exclude_dfs background_radar
@@ -556,7 +556,7 @@ mac80211_hostapd_setup_bss() {
}
[ "$staidx" -gt 0 -o "$start_disabled" -eq 1 ] && append hostapd_cfg "start_disabled=1" "$N"
cat >> /var/run/hostapd-$phy.conf <<EOF
cat >> /var/run/hostapd-$phy$vif_phy_suffix.conf <<EOF
$hostapd_cfg
bssid=$macaddr
${default_macaddr:+#default_macaddr}
@@ -576,7 +576,7 @@ mac80211_generate_mac() {
local phy="$1"
local id="${macidx:-0}"
wdev_tool "$phy" get_macaddr id=$id num_global=$num_global_macaddr mbssid=${multiple_bssid:-0}
wdev_tool "$phy$phy_suffix" get_macaddr id=$id num_global=$num_global_macaddr mbssid=${multiple_bssid:-0}
}
get_board_phy_name() (
@@ -679,7 +679,7 @@ mac80211_prepare_vif() {
monitor) prefix=mon;;
esac
mac80211_set_ifname "$phy" "$prefix"
mac80211_set_ifname "$phy$vif_phy_suffix" "$prefix"
}
append active_ifnames "$ifname"
@@ -880,7 +880,12 @@ mac80211_set_vif_txpower() {
json_get_vars vif_txpower
json_select ..
[ -z "$vif_txpower" ] || iw dev "$ifname" set txpower fixed "${vif_txpower%%.*}00"
set_default vif_txpower "$txpower"
if [ -n "$vif_txpower" ]; then
iw dev "$ifname" set txpower fixed "${vif_txpower%%.*}00"
else
iw dev "$ifname" set txpower auto
fi
}
wpa_supplicant_init_config() {
@@ -920,11 +925,13 @@ wpa_supplicant_add_interface() {
wpa_supplicant_set_config() {
local phy="$1"
local radio="$2"
local prev
json_set_namespace wpa_supp prev
json_close_array
json_add_string phy "$phy"
json_add_int radio "$radio"
json_add_int num_global_macaddr "$num_global_macaddr"
json_add_boolean defer 1
local data="$(json_dump)"
@@ -947,13 +954,16 @@ wpa_supplicant_set_config() {
}
hostapd_set_config() {
local phy="$1"
local radio="$2"
[ -n "$hostapd_ctrl" ] || {
ubus_call hostapd config_set '{ "phy": "'"$phy"'", "config": "", "prev_config": "'"${hostapd_conf_file}.prev"'" }' > /dev/null
ubus_call hostapd config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "config": "", "prev_config": "'"${hostapd_conf_file}.prev"'" }' > /dev/null
return 0;
}
ubus wait_for hostapd
local hostapd_res="$(ubus_call hostapd config_set "{ \"phy\": \"$phy\", \"config\":\"${hostapd_conf_file}\", \"prev_config\": \"${hostapd_conf_file}.prev\"}")"
local hostapd_res="$(ubus_call hostapd config_set "{ \"phy\": \"$phy\", \"radio\": $radio, \"config\":\"${hostapd_conf_file}\", \"prev_config\": \"${hostapd_conf_file}.prev\"}")"
ret="$?"
[ "$ret" != 0 -o -z "$hostapd_res" ] && {
wireless_setup_failed HOSTAPD_START_FAILED
@@ -965,10 +975,11 @@ hostapd_set_config() {
wpa_supplicant_start() {
local phy="$1"
local radio="$2"
[ -n "$wpa_supp_init" ] || return 0
ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "num_global_macaddr": '"$num_global_macaddr"' }' > /dev/null
ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "num_global_macaddr": '"$num_global_macaddr"' }' > /dev/null
}
mac80211_setup_supplicant() {
@@ -1073,18 +1084,23 @@ drv_mac80211_cleanup() {
}
mac80211_reset_config() {
local phy="$1"
hostapd_conf_file="/var/run/hostapd-$phy$vif_phy_suffix.conf"
ubus_call hostapd config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "config": "", "prev_config": "'"$hostapd_conf_file"'" }' > /dev/null
ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "radio": '"$radio"', "config": [] }' > /dev/null
wdev_tool "$phy$phy_suffix" set_config '{}'
}
hostapd_conf_file="/var/run/hostapd-$phy.conf"
ubus_call hostapd config_set '{ "phy": "'"$phy"'", "config": "", "prev_config": "'"$hostapd_conf_file"'" }' > /dev/null
ubus_call wpa_supplicant config_set '{ "phy": "'"$phy"'", "config": [] }' > /dev/null
wdev_tool "$phy" set_config '{}'
mac80211_set_suffix() {
[ "$radio" = "-1" ] && radio=
phy_suffix="${radio:+:$radio}"
vif_phy_suffix="${radio:+.$radio}"
set_default radio -1
}
drv_mac80211_setup() {
json_select config
json_get_vars \
phy macaddr path \
radio phy macaddr path \
country chanbw distance \
txpower \
rxantenna txantenna \
@@ -1094,6 +1110,8 @@ drv_mac80211_setup() {
json_get_values scan_list scan_list
json_select ..
mac80211_set_suffix
json_select data && {
json_get_var prev_rxantenna rxantenna
json_get_var prev_txantenna txantenna
@@ -1120,7 +1138,7 @@ drv_mac80211_setup() {
}
}
hostapd_conf_file="/var/run/hostapd-$phy.conf"
hostapd_conf_file="/var/run/hostapd-$phy$vif_phy_suffix.conf"
macidx=0
staidx=0
@@ -1139,17 +1157,11 @@ drv_mac80211_setup() {
[ "$rxantenna" = "all" ] && rxantenna=0xffffffff
[ "$rxantenna" = "$prev_rxantenna" -a "$txantenna" = "$prev_txantenna" ] || mac80211_reset_config "$phy"
wireless_set_data phy="$phy" txantenna="$txantenna" rxantenna="$rxantenna"
wireless_set_data phy="$phy" radio="$radio" txantenna="$txantenna" rxantenna="$rxantenna"
iw phy "$phy" set antenna $txantenna $rxantenna >/dev/null 2>&1
iw phy "$phy" set distance "$distance" >/dev/null 2>&1
if [ -n "$txpower" ]; then
iw phy "$phy" set txpower fixed "${txpower%%.*}00"
else
iw phy "$phy" set txpower auto
fi
[ -n "$frag" ] && iw phy "$phy" set frag "${frag%%.*}"
[ -n "$rts" ] && iw phy "$phy" set rts "${rts%%.*}"
@@ -1177,13 +1189,13 @@ drv_mac80211_setup() {
for_each_interface "ap sta adhoc mesh monitor" mac80211_prepare_vif
for_each_interface "ap sta adhoc mesh monitor" mac80211_setup_vif
[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_set_config "$phy"
[ -x /usr/sbin/hostapd ] && hostapd_set_config "$phy"
[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_set_config "$phy" "$radio"
[ -x /usr/sbin/hostapd ] && hostapd_set_config "$phy" "$radio"
[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_start "$phy"
[ -x /usr/sbin/wpa_supplicant ] && wpa_supplicant_start "$phy" "$radio"
json_set_namespace wdev_uc prev
wdev_tool "$phy" set_config "$(json_dump)" $active_ifnames
wdev_tool "$phy$phy_suffix" set_config "$(json_dump)" $active_ifnames
json_set_namespace "$prev"
for_each_interface "ap sta adhoc mesh monitor" mac80211_set_vif_txpower
@@ -1210,19 +1222,15 @@ list_phy_interfaces() {
drv_mac80211_teardown() {
json_select data
json_get_vars phy
json_get_vars phy radio
json_select ..
[ -n "$phy" ] || {
echo "Bug: PHY is undefined for device '$1'"
return 1
}
mac80211_set_suffix
mac80211_reset_config "$phy"
for wdev in $(list_phy_interfaces "$phy"); do
ip link set dev "$wdev" down
iw dev "$wdev" del
done
}
add_driver mac80211

View File

@@ -14,10 +14,12 @@ let commit;
let config = uci.cursor().get_all("wireless") ?? {};
function radio_exists(path, macaddr, phy) {
function radio_exists(path, macaddr, phy, radio) {
for (let name, s in config) {
if (s[".type"] != "wifi-device")
continue;
if (radio != null && int(s.radio) != radio)
continue;
if (s.macaddr & lc(s.macaddr) == lc(macaddr))
return true;
if (s.phy == phy)
@@ -34,55 +36,61 @@ for (let phy_name, phy in board.wlan) {
if (!info || !length(info.bands))
continue;
while (config[`radio${idx}`])
idx++;
let name = "radio" + idx++;
let radios = length(info.radios) > 0 ? info.radios : [{ bands: info.bands }];
for (let radio in radios) {
while (config[`radio${idx}`])
idx++;
let name = "radio" + idx;
let s = "wireless." + name;
let si = "wireless.default_" + name;
let s = "wireless." + name;
let si = "wireless.default_" + name;
let band_name = filter(bands_order, (b) => info.bands[b])[0];
if (!band_name)
continue;
let band_name = filter(bands_order, (b) => radio.bands[b])[0];
if (!band_name)
continue;
let band = info.bands[band_name];
let channel = band.default_channel ?? "auto";
let band = info.bands[band_name];
let rband = radio.bands[band_name];
let channel = rband.default_channel ?? "auto";
let width = band.max_width;
if (band_name == "2G")
width = 20;
else if (width > 80)
width = 80;
let width = band.max_width;
if (band_name == "2G")
width = 20;
else if (width > 80)
width = 80;
let htmode = filter(htmode_order, (m) => band[lc(m)])[0];
if (htmode)
htmode += width;
else
htmode = "NOHT";
let htmode = filter(htmode_order, (m) => band[lc(m)])[0];
if (htmode)
htmode += width;
else
htmode = "NOHT";
if (!phy.path)
continue;
if (!phy.path)
continue;
let macaddr = trim(readfile(`/sys/class/ieee80211/${phy_name}/macaddress`));
if (radio_exists(phy.path, macaddr, phy_name))
continue;
let macaddr = trim(readfile(`/sys/class/ieee80211/${phy_name}/macaddress`));
if (radio_exists(phy.path, macaddr, phy_name, radio.index))
continue;
let id = `phy='${phy_name}'`;
if (match(phy_name, /^phy[0-9]/))
id = `path='${phy.path}'`;
let id = `phy='${phy_name}'`;
if (match(phy_name, /^phy[0-9]/))
id = `path='${phy.path}'`;
band_name = lc(band_name);
band_name = lc(band_name);
let country, defaults, num_global_macaddr;
if (board.wlan.defaults) {
defaults = board.wlan.defaults.ssids?.[band_name]?.ssid ? board.wlan.defaults.ssids?.[band_name] : board.wlan.defaults.ssids?.all;
country = board.wlan.defaults.country;
if (!country && band_name != '2g')
defaults = null;
num_global_macaddr = board.wlan.defaults.ssids?.[band_name]?.mac_count;
}
let country, defaults, num_global_macaddr;
if (board.wlan.defaults) {
defaults = board.wlan.defaults.ssids?.[band_name]?.ssid ? board.wlan.defaults.ssids?.[band_name] : board.wlan.defaults.ssids?.all;
country = board.wlan.defaults.country;
if (!country && band_name != '2g')
defaults = null;
num_global_macaddr = board.wlan.defaults.ssids?.[band_name]?.mac_count;
}
print(`set ${s}=wifi-device
if (length(info.radios) > 0)
id += `\nset ${s}.radio='${radio.index}'`;
print(`set ${s}=wifi-device
set ${s}.type='mac80211'
set ${s}.${id}
set ${s}.band='${band_name}'
@@ -101,7 +109,9 @@ set ${si}.encryption='${defaults?.encryption || "none"}'
set ${si}.key='${defaults?.key || ""}'
`);
commit = true;
config[name] = {};
commit = true;
}
}
if (commit)

View File

@@ -1,6 +1,6 @@
import * as nl80211 from "nl80211";
import * as rtnl from "rtnl";
import { readfile, glob, basename, readlink } from "fs";
import { readfile, glob, basename, readlink, open } from "fs";
const iftypes = {
ap: nl80211.const.NL80211_IFTYPE_AP,
@@ -74,6 +74,14 @@ function find_reusable_wdev(phyidx)
return null;
}
function wdev_set_radio_mask(name, mask)
{
nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, {
dev: name,
vif_radio_mask: mask
});
}
function wdev_create(phy, name, data)
{
let phyidx = int(readfile(`/sys/class/ieee80211/${phy}/index`));
@@ -93,24 +101,24 @@ function wdev_create(phy, name, data)
req["4addr"] = data["4addr"];
if (data.macaddr)
req.mac = data.macaddr;
if (data.radio != null && data.radio >= 0)
req.vif_radio_mask = 1 << data.radio;
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
rtnl.request(rtnl.const.RTM_SETLINK, 0, { dev: reuse_ifname, ifname: name}) != false)) {
req.dev = req.ifname;
delete req.ifname;
nl80211.request(nl80211.const.NL80211_CMD_SET_INTERFACE, 0, req);
} else {
nl80211.request(
nl80211.const.NL80211_CMD_NEW_INTERFACE,
nl80211.const.NLM_F_CREATE,
req);
}
let error = nl80211.error();
if (error)
@@ -190,7 +198,8 @@ const phy_proto = {
},
macaddr_generate: function(data) {
let phy = this.name;
let phy = this.phy;
let radio_idx = this.radio;
let idx = int(data.id ?? 0);
let mbssid = int(data.mbssid ?? 0) > 0;
let num_global = int(data.num_global ?? 1);
@@ -200,22 +209,30 @@ const phy_proto = {
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) {
if (base_mask == "00:00:00:00:00:00" &&
(radio_idx > 0 || idx >= num_global)) {
let addrs = split(phy_sysfs_file(phy, "addresses"), "\n");
if (idx < length(addrs))
return addrs[idx];
if (radio_idx != null) {
if (radio_idx && radio_idx < length(addrs))
base_addr = addrs[radio_idx];
else
idx += radio_idx * 16;
} else {
if (idx < length(addrs))
return addrs[idx];
base_mask = "ff:ff:ff:ff:ff:ff";
base_mask = "ff:ff:ff:ff:ff:ff";
}
}
if (!idx && !mbssid)
return base_addr;
let addr = macaddr_split(base_addr);
let mask = macaddr_split(base_mask);
let type;
@@ -275,27 +292,55 @@ const phy_proto = {
}
},
wdev_add: function(name, data) {
let phydev = this;
wdev_create(this.phy, name, {
...data,
radio: this.radio,
});
},
for_each_wdev: function(cb) {
let wdevs = glob(`/sys/class/ieee80211/${this.name}/device/net/*`);
wdevs = map(wdevs, (arg) => basename(arg));
let wdevs = nl80211.request(
nl80211.const.NL80211_CMD_GET_INTERFACE,
nl80211.const.NLM_F_DUMP,
{ wiphy: this.idx }
);
let mac_wdev = {};
for (let wdev in wdevs) {
if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != this.name)
if (wdev.iftype == nl80211.const.NL80211_IFTYPE_AP_VLAN)
continue;
if (this.radio != null && wdev.vif_radio_mask != null &&
!(wdev.vif_radio_mask & (1 << this.radio)))
continue;
mac_wdev[wdev.mac] = wdev;
}
for (let wdev in wdevs) {
if (!mac_wdev[wdev.mac])
continue;
cb(wdev);
cb(wdev.ifname);
}
}
};
function phy_open(phy)
function phy_open(phy, radio)
{
let phyidx = readfile(`/sys/class/ieee80211/${phy}/index`);
if (!phyidx)
return null;
let name = phy;
if (radio === "" || radio < 0)
radio = null;
if (radio != null)
name += "." + radio;
return proto({
name: phy,
idx: int(phyidx)
phy, name, radio,
idx: int(phyidx),
}, phy_proto);
}
@@ -365,9 +410,9 @@ function is_equal(val1, val2) {
function vlist_new(cb) {
return proto({
cb: cb,
data: {}
}, vlist_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 };
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 };

View File

@@ -1,13 +1,13 @@
#!/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 { vlist_new, is_equal, 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 phy_name = shift(ARGV);
let command = shift(ARGV);
let phydev;
let phy, phydev;
function iface_stop(wdev)
{
@@ -30,7 +30,7 @@ function iface_start(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);
phydev.wdev_add(ifname, wdev_config);
wdev_set_up(ifname, true);
let htmode = wdev.htmode || "NOHT";
if (wdev.freq)
@@ -85,20 +85,15 @@ function delete_ifname(config)
delete config[key].ifname;
}
function add_existing(phy, config)
function add_existing(phydev, config)
{
let wdevs = glob(`/sys/class/ieee80211/${phy}/device/net/*`);
wdevs = map(wdevs, (arg) => basename(arg));
for (let wdev in wdevs) {
phydev.for_each_wdev((wdev) => {
if (config[wdev])
continue;
if (basename(readlink(`/sys/class/net/${wdev}/phy80211`)) != phy)
continue;
return;
if (trim(readfile(`/sys/class/net/${wdev}/operstate`)) == "down")
config[wdev] = {};
}
});
}
function usage()
@@ -114,7 +109,7 @@ Commands:
const commands = {
set_config: function(args) {
let statefile = `/var/run/wdev-${phy}.json`;
let statefile = `/var/run/wdev-${phy_name}.json`;
let new_config = shift(args);
for (let dev in ARGV)
@@ -137,12 +132,12 @@ const commands = {
if (type(old_config) == "object")
config.data = old_config;
add_existing(phy, config.data);
add_existing(phydev, 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 data = ubus.call("hostapd", "config_get_macaddr_list", { phy: phydev.name, radio: phydev.radio ?? -1 });
let macaddr_list = [];
if (type(data) == "object" && data.macaddr)
macaddr_list = data.macaddr;
@@ -166,7 +161,7 @@ const commands = {
let macaddr = phydev.macaddr_generate(data);
if (!macaddr) {
warn(`Could not get MAC address for phy ${phy}\n`);
warn(`Could not get MAC address for phy ${phy_name}\n`);
exit(1);
}
@@ -174,12 +169,14 @@ const commands = {
},
};
if (!phy || !command | !commands[command])
if (!phy_name || !command | !commands[command])
usage();
phydev = phy_open(phy);
let phy_split = split(phy_name, ":");
phydev = phy_open(phy_split[0], phy_split[1]);
phy = phydev.phy;
if (!phydev) {
warn(`PHY ${phy} does not exist\n`);
warn(`PHY ${phy_name} does not exist\n`);
exit(1);
}

View File

@@ -73,6 +73,15 @@ function freq_to_channel(freq) {
return 0;
}
function freq_range_match(ranges, freq) {
freq *= 1000;
for (let range in ranges) {
if (freq >= range[0] && freq <= range[1])
return true;
}
return false;
}
function wiphy_detect() {
let phys = nl.request(nl.const.NL80211_CMD_GET_WIPHY, nl.const.NLM_F_DUMP, { split_wiphy_dump: true });
if (!phys)
@@ -88,8 +97,27 @@ function wiphy_detect() {
antenna_rx: phy.wiphy_antenna_avail_rx,
antenna_tx: phy.wiphy_antenna_avail_tx,
bands: {},
radios: []
};
for (let radio in phy.radios) {
// S1G is not supported yet
radio.freq_ranges = filter(radio.freq_ranges,
(range) => range.end > 2000000
);
if (!length(radio.freq_ranges))
continue;
push(info.radios, {
index: radio.index,
freq_ranges: map(radio.freq_ranges,
(range) => [ range.start, range.end ]
),
bands: {}
});
}
let bands = info.bands;
for (let band in phy.wiphy_bands) {
if (!band || !band.freqs)
@@ -160,6 +188,25 @@ function wiphy_detect() {
if (eht_phy_cap && he_phy_cap & 2)
push(modes, "EHT40");
for (let radio in info.radios) {
let freq_match = filter(band.freqs,
(freq) => freq_range_match(radio.freq_ranges, freq.freq)
);
if (!length(freq_match))
continue;
let radio_band = {};
radio.bands[band_name] = radio_band;
freq_match = filter(freq_match,
(freq) => !freq.disabled
);
let freq = freq_match[0];
if (freq)
radio_band.default_channel = freq_to_channel(freq.freq);
}
for (let freq in band.freqs) {
if (freq.disabled)
continue;