mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-18 21:28:02 +00:00
wifi-scripts: add ucode based scripts
Add an ucode based re-implementation of the shell script based wifi code. The new code is jsonschema driven. The code has been refactored into several files making it easier to follow. The new scripts are also way faster than the previous sh implementation. The new code is currently opt-in via WIFI_SCRIPTS_UCODE and defaults to EXPERIMENTAL. Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
parent
0210279888
commit
218f3884d2
3
package/network/config/wifi-scripts/Config.in
Normal file
3
package/network/config/wifi-scripts/Config.in
Normal file
@ -0,0 +1,3 @@
|
||||
config WIFI_SCRIPTS_UCODE
|
||||
bool "Use new ucode based scripts"
|
||||
default n
|
@ -13,17 +13,22 @@ PKG_RELEASE:=1
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
||||
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
|
||||
PKG_CONFIG_DEPENDS:=CONFIG_WIFI_SCRIPTS_UCODE
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/wifi-scripts
|
||||
SECTION:=utils
|
||||
CATEGORY:=Base system
|
||||
DEPENDS:=+netifd +ucode +ucode-mod-nl80211 +ucode-mod-rtnl +ucode-mod-ubus +ucode-mod-uci
|
||||
DEPENDS:=+netifd +ucode +ucode-mod-nl80211 +ucode-mod-rtnl +ucode-mod-ubus +ucode-mod-uci +ucode-mod-digest
|
||||
TITLE:=Wi-Fi configuration scripts
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/wifi-scripts/config
|
||||
source "$(SOURCE)/Config.in"
|
||||
endef
|
||||
|
||||
define Package/wifi-scripts/description
|
||||
A set of scripts that handle setup and configuration of Wi-Fi devices.
|
||||
endef
|
||||
@ -40,6 +45,9 @@ endef
|
||||
define Package/wifi-scripts/install
|
||||
$(INSTALL_DIR) $(1)
|
||||
$(CP) ./files/* $(1)/
|
||||
ifeq ($(CONFIG_WIFI_SCRIPTS_UCODE),y)
|
||||
$(CP) ./files-ucode/* $(1)/
|
||||
endif
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,wifi-scripts))
|
||||
|
347
package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh
Executable file
347
package/network/config/wifi-scripts/files-ucode/lib/netifd/wireless/mac80211.sh
Executable file
@ -0,0 +1,347 @@
|
||||
#!/usr/bin/ucode
|
||||
|
||||
'use strict';
|
||||
|
||||
import { set_default, log } from 'wifi.common';
|
||||
import { validate, dump_options } from 'wifi.validate';
|
||||
import * as supplicant from 'wifi.supplicant';
|
||||
import * as hostapd from 'wifi.hostapd';
|
||||
import * as netifd from 'wifi.netifd';
|
||||
import * as iface from 'wifi.iface';
|
||||
import * as fs from 'fs';
|
||||
|
||||
global.radio = ARGV[2];
|
||||
|
||||
const mesh_param_list = [
|
||||
"mesh_retry_timeout", "mesh_confirm_timeout", "mesh_holding_timeout", "mesh_max_peer_links",
|
||||
"mesh_max_retries", "mesh_ttl", "mesh_element_ttl", "mesh_hwmp_max_preq_retries",
|
||||
"mesh_path_refresh_time", "mesh_min_discovery_timeout", "mesh_hwmp_active_path_timeout",
|
||||
"mesh_hwmp_preq_min_interval", "mesh_hwmp_net_diameter_traversal_time", "mesh_hwmp_rootmode",
|
||||
"mesh_hwmp_rann_interval", "mesh_gate_announcements", "mesh_sync_offset_max_neighor",
|
||||
"mesh_rssi_threshold", "mesh_hwmp_active_path_to_root_timeout", "mesh_hwmp_root_interval",
|
||||
"mesh_hwmp_confirmation_interval", "mesh_awake_window", "mesh_plink_timeout",
|
||||
"mesh_auto_open_plinks", "mesh_fwding", "mesh_power_mode"
|
||||
];
|
||||
|
||||
function phy_suffix(radio, sep) {
|
||||
if (radio == null || radio < 0)
|
||||
return "";
|
||||
return sep + radio;
|
||||
}
|
||||
|
||||
function reset_config(phy, radio) {
|
||||
let name = phy + phy_suffix(radio, ".");
|
||||
let prev_config = `/var/run/hostapd-${name}.conf`;
|
||||
|
||||
global.ubus.call('hostapd', 'config_set', { phy, radio, config: '', prev_config });
|
||||
global.ubus.call('wpa_supplicant', 'config_set', { phy, radio, config: []});
|
||||
|
||||
name = phy + phy_suffix(radio, ":");
|
||||
system(`ucode /usr/share/hostap/wdev.uc ${name} set_config '{}'`);
|
||||
}
|
||||
|
||||
function phy_filename(phy, name) {
|
||||
return `/sys/class/ieee80211/${phy}/${name}`;
|
||||
}
|
||||
|
||||
function phy_file(phy, name) {
|
||||
return fs.readfile(phy_filename(phy, name));
|
||||
}
|
||||
|
||||
function phy_index(phy) {
|
||||
return +phy_file(phy, "index");
|
||||
}
|
||||
|
||||
function phy_path_match(phy, path) {
|
||||
let phy_path = fs.realpath(phy_filename(phy, "device"));
|
||||
return substr(phy_path, -length(path)) == path;
|
||||
}
|
||||
|
||||
function find_phy_by_path(phys, path) {
|
||||
if (!path)
|
||||
return null;
|
||||
|
||||
path = split(path, "+");
|
||||
phys = filter(phys, (phy) => phy_path_match(phy, path[0]));
|
||||
phys = sort(phys, (a, b) => phy_index(a) - phy_index(b));
|
||||
|
||||
return phys[+path[1]];
|
||||
}
|
||||
|
||||
function find_phy_by_macaddr(phys, macaddr) {
|
||||
macaddr = lc(macaddr);
|
||||
return filter(phys, (phy) => phy_file(phy, "macaddr") == macaddr)[0];
|
||||
}
|
||||
|
||||
function find_phy_by_name(phys, name) {
|
||||
return index(phys, name) < 0 ? null : name;
|
||||
}
|
||||
|
||||
function find_phy(config) {
|
||||
let phys = fs.lsdir("/sys/class/ieee80211");
|
||||
|
||||
return find_phy_by_path(phys, config.path) ??
|
||||
find_phy_by_macaddr(phys, config.macaddr) ??
|
||||
find_phy_by_name(phys, config.phy);
|
||||
}
|
||||
|
||||
function get_channel_frequency(band, channel) {
|
||||
if (channel < 1)
|
||||
return null;
|
||||
|
||||
switch (band) {
|
||||
case '2g':
|
||||
if (channel == 14)
|
||||
return 2484;
|
||||
return 2407 + channel * 5;
|
||||
case '5g':
|
||||
if (channel >= 182 && channel <= 196)
|
||||
return 4000 + channel * 5;
|
||||
return 5000 + channel * 5;
|
||||
case '6g':
|
||||
if (channel == 2)
|
||||
return 5935;
|
||||
return 5950 + channel * 5;
|
||||
case '60g':
|
||||
return 56160 + channel * 2160;
|
||||
}
|
||||
}
|
||||
|
||||
function setup_phy(phy, config, data) {
|
||||
if (config.channel == "auto")
|
||||
config.channel = 0;
|
||||
config.channel = +config.channel;
|
||||
config.frequency = get_channel_frequency(config.band, config.channel);
|
||||
|
||||
if (config.country) {
|
||||
log(`Setting country code to ${config.country}`);
|
||||
system(`iw reg set ${config.country}`);
|
||||
}
|
||||
|
||||
set_default(config, 'rxantenna', 0xffffffff);
|
||||
set_default(config, 'txantenna', 0xffffffff);
|
||||
|
||||
if (config.txantenna == 'all')
|
||||
config.txantenna = 0xffffffff;
|
||||
if (config.rxantenna == 'all')
|
||||
config.rxantenna = 0xffffffff;
|
||||
|
||||
if (config.txantenna != data?.txantenna || config.rxantenna != data?.rxantenna)
|
||||
reset_config(phy, config.radio);
|
||||
|
||||
netifd.set_data({
|
||||
phy,
|
||||
radio: config.radio,
|
||||
txantenna: config.txantenna,
|
||||
rxantenna: config.rxantenna
|
||||
});
|
||||
|
||||
if (config.txpower)
|
||||
config.txpower = 'fixed ' + config.txpower + '00';
|
||||
else
|
||||
config.txpower = 'auto';
|
||||
|
||||
log(`Configuring '${phy}' txantenna: ${config.txantenna}, rxantenna: ${config.rxantenna} distance: ${config.distance}`);
|
||||
system(`iw phy ${phy} set antenna ${config.txantenna} ${config.rxantenna}`);
|
||||
system(`iw phy ${phy} set distance ${config.distance}`);
|
||||
|
||||
if (config.frag)
|
||||
system(`iw phy ${phy} set frag ${frag}`);
|
||||
if (config.rts)
|
||||
system(`iw phy ${phy} set rts ${rts}`);
|
||||
}
|
||||
|
||||
function iw_htmode(config) {
|
||||
let suffix = substr(config.htmode, 3);
|
||||
if (suffix == "40+" || suffix == "40-")
|
||||
return "HT" + suffix;
|
||||
|
||||
switch (config.htmode ?? "NONE") {
|
||||
case "HT20":
|
||||
case "VHT20":
|
||||
case "HE20":
|
||||
case "EHT20":
|
||||
return "HT20";
|
||||
case "VHT80":
|
||||
case "HE80":
|
||||
case "EHT80":
|
||||
case "HE160":
|
||||
case "EHT160":
|
||||
case "EHT320":
|
||||
return "80MHZ";
|
||||
case "NONE":
|
||||
case "NOHT":
|
||||
return "NOHT";
|
||||
}
|
||||
|
||||
if (substr(config.htmode, 2) == "40") {
|
||||
switch (config.band) {
|
||||
case "2g":
|
||||
if (+config.channel < 7)
|
||||
return "HT40+";
|
||||
else
|
||||
return "HT40-";
|
||||
default:
|
||||
return ((+config.channel / 4) % 2) ? "HT40+" : "HT40-";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function config_add(config, name, val) {
|
||||
if (val != null)
|
||||
config[name] = val;
|
||||
}
|
||||
|
||||
function config_add_mesh_params(config, data) {
|
||||
for (let param in mesh_param_list)
|
||||
config_add(config, param, data[param]);
|
||||
}
|
||||
|
||||
function setup() {
|
||||
let data = json(ARGV[3]);
|
||||
|
||||
data.phy = find_phy(data.config);
|
||||
if (!data.phy) {
|
||||
log('Bug: PHY is undefined for device');
|
||||
netifd.set_retry(false);
|
||||
return 1;
|
||||
}
|
||||
data.phy_suffix = phy_suffix(data.config.radio, ":");
|
||||
data.vif_phy_suffix = phy_suffix(data.config.radio, ".");
|
||||
let active_ifnames = [];
|
||||
|
||||
log('Starting');
|
||||
|
||||
validate('device', data.config);
|
||||
setup_phy(data.phy, data.config, data.data);
|
||||
|
||||
let supplicant_mesh;
|
||||
let has_ap = false;
|
||||
let idx = {};
|
||||
let supplicant_data = [];
|
||||
let wdev_data = {};
|
||||
|
||||
for (let k, v in data.interfaces) {
|
||||
let mode = v.config.mode;
|
||||
idx[mode] ??= 0;
|
||||
let mode_idx = idx[mode]++;
|
||||
|
||||
if (!v.config.ifname)
|
||||
v.config.ifname = data.phy + data.vif_phy_suffix + "-" + mode + mode_idx;
|
||||
push(active_ifnames, v.config.ifname);
|
||||
|
||||
if (v.config.encryption == 'owe' && v.config.owe_transition) {
|
||||
mode_idx = idx[mode]++;
|
||||
v.config.owe_transition_ifname = data.phy + data.vif_phy_suffix + "-" + mode + mode_idx;
|
||||
push(active_ifnames, v.config.ifname);
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'ap':
|
||||
has_ap = true;
|
||||
// fallthrough
|
||||
case 'sta':
|
||||
case 'adhoc':
|
||||
case 'mesh':
|
||||
if (mode != "ap")
|
||||
data.config.noscan = true;
|
||||
validate('iface', v.config);
|
||||
iface.prepare(v.config, data.phy + data.phy_suffix, data.config.num_global_macaddr);
|
||||
netifd.set_vif(k, v.config.ifname);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (mode) {
|
||||
case 'adhoc':
|
||||
if (config.frequency && !v.config.wpa)
|
||||
break;
|
||||
// fallthrough
|
||||
case 'mesh':
|
||||
supplicant_mesh ??= !system("wpa_supplicant -vmesh");
|
||||
if (mode == "mesh" && !supplicant_mesh)
|
||||
break;
|
||||
// fallthrough
|
||||
case 'sta':
|
||||
let config = supplicant.generate(supplicant_data, data, v);
|
||||
if (mode == "mesh")
|
||||
config_add_mesh_params(config, v.config);
|
||||
continue;
|
||||
case 'monitor':
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
// fallback to wdev setup
|
||||
let config = {
|
||||
mode,
|
||||
ssid: v.config.ssid,
|
||||
};
|
||||
|
||||
if (!v.config.default_macaddr)
|
||||
config.macaddr = v.config.macaddr;
|
||||
|
||||
config_add(config, "htmode", wdev_htmode(data.config));
|
||||
if (mode != "monitor") {
|
||||
config_add(config, "basic-rates", supplicant.ratelist(data.config.basic_rate));
|
||||
config_add(config, "mcast-rate", supplicant.ratestr(v.config.mcast_rate));
|
||||
config_add(config, "beacon-interval", data.config.beacon_int);
|
||||
if (mode == "mesh") {
|
||||
config_add(config, "ssid", v.config.mesh_id);
|
||||
config_add_mesh_params(config, v.config);
|
||||
}
|
||||
}
|
||||
|
||||
wdev_data[v.config.ifname] = config;
|
||||
}
|
||||
|
||||
if (length(supplicant_data) > 0)
|
||||
supplicant.setup(supplicant_data, data);
|
||||
|
||||
if (has_ap)
|
||||
hostapd.setup(data);
|
||||
|
||||
system(`ucode /usr/share/hostap/wdev.uc ${data.phy}${data.phy_suffix} set_config '${printf("%J", wdev_data)}' ${join(' ', active_ifnames)}`);
|
||||
|
||||
if (length(supplicant_data) > 0)
|
||||
supplicant.start(data);
|
||||
|
||||
netifd.set_up();
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
let data = json(ARGV[3]);
|
||||
|
||||
if (!data.data?.phy) {
|
||||
log('Bug: PHY is undefined for device');
|
||||
return 1;
|
||||
}
|
||||
|
||||
log(`Tearing down ${data.data.phy}`);
|
||||
|
||||
reset_config(data.data.phy, data.data.radio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
let ret = 1;
|
||||
|
||||
switch(ARGV[1]) {
|
||||
case 'dump':
|
||||
ret = dump_options();
|
||||
break;
|
||||
|
||||
case 'setup':
|
||||
ret = setup();
|
||||
break;
|
||||
|
||||
case 'teardown':
|
||||
ret = teardown();
|
||||
break;
|
||||
}
|
||||
|
||||
exit(ret);
|
@ -0,0 +1,717 @@
|
||||
{
|
||||
"$id": "https://openwrt.org/wifi.device.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "OpenWrt WiFi Device Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"acs_chan_bias": {
|
||||
"description": "Can be used to increase (or decrease) the likelihood of a specific channel to be selected by the ACS algorithm",
|
||||
"type": "string"
|
||||
},
|
||||
"acs_exclude_dfs": {
|
||||
"description": "Exclude DFS channels from ACS",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"airtime_mode": {
|
||||
"description": "Set the airtime policy operating mode",
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"minimum": 0,
|
||||
"maximum": 3
|
||||
},
|
||||
"antenna_gain": {
|
||||
"description": "Reduction in antenna gain from regulatory maximum in dBi",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"assoc_sa_query_max_timeout": {
|
||||
"description": "Association SA Query maximum timeout",
|
||||
"type": "number"
|
||||
},
|
||||
"assoc_sa_query_retry_timeout": {
|
||||
"description": "Association SA Query retry timeout",
|
||||
"type": "number"
|
||||
},
|
||||
"auth_cache": {
|
||||
"type": "alias",
|
||||
"default": "okc"
|
||||
},
|
||||
"background_radar": {
|
||||
"type": "alias",
|
||||
"default": "enable_background_radar"
|
||||
},
|
||||
"band": {
|
||||
"description": "The wireless band thatthe radio shall operate on",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"2g",
|
||||
"5g",
|
||||
"6g",
|
||||
"60g"
|
||||
]
|
||||
},
|
||||
"basic_rate": {
|
||||
"type": "alias",
|
||||
"default": "basic_rates"
|
||||
},
|
||||
"basic_rates": {
|
||||
"description": "Set the supported basic rates. Each basic_rate is measured in kb/s. This option only has an effect on ap and adhoc wifi-ifaces. ",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"beacon_int": {
|
||||
"description": "Set the beacon interval. This is the time interval between beacon frames, measured in units of 1.024 ms. hostapd permits this to be set between 15 and 65535. This option only has an effect on ap and adhoc wifi-ifaces",
|
||||
"type": "number",
|
||||
"default": 100,
|
||||
"minimum": 15,
|
||||
"maximum": 65535
|
||||
},
|
||||
"beacon_rate": {
|
||||
"description": "Beacon frame TX rate configuration",
|
||||
"type": "string"
|
||||
},
|
||||
"beamformee_antennas": {
|
||||
"description": "Beamformee antenna override",
|
||||
"type": "number",
|
||||
"default": 4
|
||||
},
|
||||
"beamformer_antennas": {
|
||||
"description": "Beamformer antenna override",
|
||||
"type": "number",
|
||||
"default": 4
|
||||
},
|
||||
"bssid": {
|
||||
"description": "Overrides the MAC address used for the Wi-Fi interface. Warning: if the MAC address specified is a multicast address, this override will fail silently. To avoid this problem, ensure that the mac address specified is a valid unicast mac address",
|
||||
"type": "string"
|
||||
},
|
||||
"cell_density": {
|
||||
"description": "Configures data rates based on the coverage cell density. Normal configures basic rates to 6, 12, 24 Mbps if legacy_rates is 0, else to 5.5, 11 Mbps. High configures basic rates to 12, 24 Mbps if legacy_rates is 0, else to the 11 Mbps rate. Very High configures 24 Mbps as the basic rate. Supported rates lower than the minimum basic rate are not offered. The basic_rate and supported_rates options overrides this option. 0 = Disabled, 1 = Normal, 2 = High, 3 = Very High",
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"minimum": 0,
|
||||
"maximum": 3
|
||||
},
|
||||
"chanbw": {
|
||||
"description": "Specifies a narrow channel width in MHz, possible values are: 5, 10, 20",
|
||||
"type": "number",
|
||||
"enum": [ 5, 10, 20 ]
|
||||
},
|
||||
"channel": {
|
||||
"description": "Specifies the wireless channel. “auto” defaults to the lowest available channel, or utilizes the ACS algorithm depending on hardware/driver support",
|
||||
"type": "string"
|
||||
},
|
||||
"channels": {
|
||||
"type": "alias",
|
||||
"default": "chanlist"
|
||||
},
|
||||
"channel_list": {
|
||||
"type": "alias",
|
||||
"default": "chanlist"
|
||||
},
|
||||
"chanlist": {
|
||||
"description": "Use specific channels, when channel is in “auto” mode. This option allows hostapd to select one of the provided channels when a channel should be automatically selected. Channels can be provided as range using hyphen ('-') or individual channels can be specified by space (' ') separated values",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"country": {
|
||||
"type": "alias",
|
||||
"default": "country_code"
|
||||
},
|
||||
"country3": {
|
||||
"description": "The third octet of the Country String (dot11CountryString)",
|
||||
"type": "string"
|
||||
},
|
||||
"country_code": {
|
||||
"description": "Specifies the country code, affects the available channels and transmission powers. For types mac80211 and broadcom a two letter country code is used (EN or DE). The madwifi driver expects a numeric code",
|
||||
"type": "string"
|
||||
},
|
||||
"country_ie": {
|
||||
"type": "alias",
|
||||
"default": "ieee80211d"
|
||||
},
|
||||
"disabled": {
|
||||
"description": "When set to 1, wireless network is disabled",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"distance": {
|
||||
"description": "Distance between the ap and the furthest client in meters",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"doth": {
|
||||
"type": "alias",
|
||||
"default": "ieee80211h"
|
||||
},
|
||||
"dsss_cck_40": {
|
||||
"description": "DSSS/CCK Mode in 40 MHz allowed in Beacon, Measurement Pilot and Probe Response frames",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"enable_background_radar": {
|
||||
"description": "This feature allows CAC to be run on dedicated radio RF chains",
|
||||
"type": "boolean"
|
||||
},
|
||||
"frag": {
|
||||
"description": "Fragmentation threshold",
|
||||
"type": "number"
|
||||
},
|
||||
"greenfield": {
|
||||
"description": "Receive Greenfield - treats pre-80211n traffic as noise",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"he_bss_color": {
|
||||
"description": "BSS color to be announced",
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 128,
|
||||
"default": 128
|
||||
},
|
||||
"he_bss_color_enabled": {
|
||||
"description": "Enable BSS color",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"he_default_pe_duration": {
|
||||
"description": "The duration of PE field in an HE PPDU in us",
|
||||
"type": "number",
|
||||
"default": 4,
|
||||
"enum": [ 4, 8, 12, 16 ]
|
||||
},
|
||||
"he_mu_beamformer": {
|
||||
"description": "HE multiple user beamformer support",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"he_mu_edca_ac_be_aci": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"he_mu_edca_ac_be_aifsn": {
|
||||
"type": "number",
|
||||
"default": 8
|
||||
},
|
||||
"he_mu_edca_ac_be_ecwmax": {
|
||||
"type": "number",
|
||||
"default": 10
|
||||
},
|
||||
"he_mu_edca_ac_be_ecwmin": {
|
||||
"type": "number",
|
||||
"default": 9
|
||||
},
|
||||
"he_mu_edca_ac_be_timer": {
|
||||
"type": "number",
|
||||
"default": 255
|
||||
},
|
||||
"he_mu_edca_ac_bk_aci": {
|
||||
"type": "number",
|
||||
"default": 1
|
||||
},
|
||||
"he_mu_edca_ac_bk_aifsn": {
|
||||
"type": "number",
|
||||
"default": 15
|
||||
},
|
||||
"he_mu_edca_ac_bk_ecwmax": {
|
||||
"type": "number",
|
||||
"default": 10
|
||||
},
|
||||
"he_mu_edca_ac_bk_ecwmin": {
|
||||
"type": "number",
|
||||
"default": 9
|
||||
},
|
||||
"he_mu_edca_ac_bk_timer": {
|
||||
"type": "number",
|
||||
"default": 255
|
||||
},
|
||||
"he_mu_edca_ac_vi_aci": {
|
||||
"type": "number",
|
||||
"default": 2
|
||||
},
|
||||
"he_mu_edca_ac_vi_aifsn": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
},
|
||||
"he_mu_edca_ac_vi_ecwmax": {
|
||||
"type": "number",
|
||||
"default": 7
|
||||
},
|
||||
"he_mu_edca_ac_vi_ecwmin": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
},
|
||||
"he_mu_edca_ac_vi_timer": {
|
||||
"type": "number",
|
||||
"default": 255
|
||||
},
|
||||
"he_mu_edca_ac_vo_aci": {
|
||||
"type": "number",
|
||||
"default": 3
|
||||
},
|
||||
"he_mu_edca_ac_vo_aifsn": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
},
|
||||
"he_mu_edca_ac_vo_ecwmax": {
|
||||
"type": "number",
|
||||
"default": 7
|
||||
},
|
||||
"he_mu_edca_ac_vo_ecwmin": {
|
||||
"type": "number",
|
||||
"default": 5
|
||||
},
|
||||
"he_mu_edca_ac_vo_timer": {
|
||||
"type": "number",
|
||||
"default": 255
|
||||
},
|
||||
"he_mu_edca_qos_info_param_count": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"he_mu_edca_qos_info_q_ack": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"he_mu_edca_qos_info_queue_request": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"he_mu_edca_qos_info_txop_request": {
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"he_oper_centr_freq_seg0_idx": {
|
||||
"description": "",
|
||||
"type": "string"
|
||||
},
|
||||
"he_oper_chwidth": {
|
||||
"description": "",
|
||||
"type": "string"
|
||||
},
|
||||
"he_6ghz_reg_pwr_type": {
|
||||
"description": "This config is to set the 6 GHz Access Point type.",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 4,
|
||||
"default": 0
|
||||
},
|
||||
"he_rts_threshold": {
|
||||
"description": "Duration of STA transmission",
|
||||
"type": "number",
|
||||
"default": 1023
|
||||
},
|
||||
"he_spr_non_srg_obss_pd_max_offset": {
|
||||
"description": "",
|
||||
"type": "number"
|
||||
},
|
||||
"he_spr_psr_enabled": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"he_spr_sr_control": {
|
||||
"description": "",
|
||||
"type": "number",
|
||||
"default": 3
|
||||
},
|
||||
"he_su_beamformee": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"he_su_beamformer": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"he_twt_required": {
|
||||
"description": "",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"hostapd_options": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"ht_coex": {
|
||||
"description": "Disable honoring 40 MHz intolerance in coexistence flags of stations",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"htc_vht": {
|
||||
"description": "STA supports receiving a VHT variant HT Control field",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"htmode": {
|
||||
"description": "Specifies the high throughput mode",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"NOHT", "HT20", "HT40-", "HT40+", "HT40",
|
||||
"VHT20", "VHT40", "VHT80", "VHT160",
|
||||
"HE20", "HE40", "HE80", "HE160",
|
||||
"EHT20", "EHT40", "EHT80", "EHT160", "EHT320" ]
|
||||
},
|
||||
"hwmode": {
|
||||
"type": "alias",
|
||||
"default": "hw_mode"
|
||||
},
|
||||
"hw_mode": {
|
||||
"description": "Legacy way, use the band property instead",
|
||||
"type": "string",
|
||||
"enum": [ "11a", "11b", "11g", "11ad" ]
|
||||
},
|
||||
"ieee80211d": {
|
||||
"description": "Enables IEEE 802.11d country IE (information element) advertisement in beacon and probe response frames. This IE contains the country code and channel/power map. Requires country",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"ieee80211h": {
|
||||
"description": "This enables radar detection and DFS support",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"ieee80211w": {
|
||||
"description": "Whether management frame protection (MFP) is enabled",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
},
|
||||
"ieee80211w_max_timeout": {
|
||||
"type": "alias",
|
||||
"default": "assoc_sa_query_max_timeout"
|
||||
},
|
||||
"ieee80211w_mgmt_cipher": {
|
||||
"description": "Cypher used for MFP",
|
||||
"type": "string"
|
||||
},
|
||||
"ieee80211w_retry_timeout": {
|
||||
"type": "alias",
|
||||
"default": "assoc_sa_query_retry_timeout"
|
||||
},
|
||||
"iface_max_num_sta": {
|
||||
"description": "Limits the maximum allowed number of associated clients",
|
||||
"type": "number"
|
||||
},
|
||||
"ldpc": {
|
||||
"description": " LDPC (Low-Density Parity-Check code) capability ",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"legacy_rates": {
|
||||
"description": "Allow legacy 802.11b data rates (used by cell_density)",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"local_pwr_constraint": {
|
||||
"description": "Add Power Constraint element to Beacon and Probe Response frame",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 255
|
||||
},
|
||||
"log_80211": {
|
||||
"description": "Enable IEEE 802.11 logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_8021x": {
|
||||
"description": "Enable IEEE 802.1X logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_driver": {
|
||||
"description": "Enable driver interface logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_iapp": {
|
||||
"description": "Enable iapp logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_level": {
|
||||
"description": "Log severity",
|
||||
"type": "number",
|
||||
"default": 2,
|
||||
"minimum": 0,
|
||||
"maximum": 4
|
||||
},
|
||||
"log_mlme": {
|
||||
"description": "Enable MLME logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_radius": {
|
||||
"description": "Enable Radius logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"log_wpa": {
|
||||
"description": "Enable WPA logging",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"logger_stdout": {
|
||||
"description": "Log to stdout",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"logger_stdout_level": {
|
||||
"description": "Log severity",
|
||||
"type": "number",
|
||||
"default": 2,
|
||||
"minimum": 0,
|
||||
"maximum": 4
|
||||
},
|
||||
"logger_syslog": {
|
||||
"description": "Log to syslog",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"logger_syslog_level": {
|
||||
"description": "Syslog severity",
|
||||
"type": "number",
|
||||
"default": 2,
|
||||
"minimum": 0,
|
||||
"maximum": 4
|
||||
},
|
||||
"macaddr": {
|
||||
"type": "alias",
|
||||
"default": "bssid"
|
||||
},
|
||||
"max_amsdu": {
|
||||
"description": "Maximum A-MSDU length of 7935 octects (3839 octets if option set to 0)",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"maxassoc": {
|
||||
"type": "alias",
|
||||
"default": "iface_max_num_sta"
|
||||
},
|
||||
"mbssid": {
|
||||
"description": "Multiple BSSID Advertisement in IEEE 802.11ax IEEE Std 802.11ax-2021 added a feature where instead of multiple interfaces on a common radio transmitting individual Beacon frames, those interfaces can form a set with a common Beacon frame transmitted for all Set minimum permitted max TX power (in dBm) for ACS and DFS channel selection",
|
||||
"type": "number",
|
||||
"default": 0,
|
||||
"minimum": 0,
|
||||
"maximum": 2
|
||||
},
|
||||
"min_tx_power": {
|
||||
"description": "Set minimum permitted max TX power (in dBm) for ACS and DFS channel selection",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"mu_beamformee": {
|
||||
"description": "Supports operation as an MU beamformee",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"mu_beamformer": {
|
||||
"description": " Supports operation as an MU beamformer",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"multiple_bssid": {
|
||||
"type": "alias",
|
||||
"default": "mbssid"
|
||||
},
|
||||
"num_global_macaddr": {
|
||||
"description": "The number of MACs that this radio can use",
|
||||
"type": "number",
|
||||
"default": 1
|
||||
},
|
||||
"no_probe_resp_if_max_sta": {
|
||||
"description": "Do not answer probe requests if iface_max_num_sta was reached",
|
||||
"type": "boolean"
|
||||
},
|
||||
"noscan": {
|
||||
"description": "Do not scan for overlapping BSSs in HT40+/- mode.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"okc": {
|
||||
"description": "Enable Opportunistic Key Caching",
|
||||
"type": "boolean"
|
||||
},
|
||||
"path": {
|
||||
"description": "Alternative to phy used to identify the device based paths in /sys/devices",
|
||||
"type": "string"
|
||||
},
|
||||
"radio": {
|
||||
"description": "Index of the phy radio (for multi-radio PHYs)",
|
||||
"type": "number",
|
||||
"default": -1
|
||||
},
|
||||
"reg_power_type": {
|
||||
"type": "alias",
|
||||
"default": "he_6ghz_reg_pwr_type"
|
||||
},
|
||||
"require_mode": {
|
||||
"description": "Sets the minimum client capability level mode that connecting clients must support to be allowed to connect",
|
||||
"type": "string",
|
||||
"enum": [ "ht", "ac", "ax" ]
|
||||
},
|
||||
"rnr_beacon": {
|
||||
"description": "",
|
||||
"type": "string"
|
||||
},
|
||||
"rsn_preauth": {
|
||||
"description": "Enable IEEE 802.11i/RSN/WPA2 pre-authentication",
|
||||
"type": "boolean"
|
||||
},
|
||||
"rssi_ignore_probe_request": {
|
||||
"description": "Ignore Probe Request frames if RSSI is below given threshold (in dBm)",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"rssi_reject_assoc_rssi": {
|
||||
"description": "Reject STA association if RSSI is below given threshold (in dBm)",
|
||||
"type": "number",
|
||||
"default": 0
|
||||
},
|
||||
"rts": {
|
||||
"description": "Override the RTS/CTS threshold",
|
||||
"type": "number"
|
||||
},
|
||||
"rts_threshold": {
|
||||
"description": "RTS/CTS threshold",
|
||||
"type": "number",
|
||||
"minimum": -1,
|
||||
"maximum": 65535
|
||||
},
|
||||
"rx_antenna_pattern": {
|
||||
"description": "Rx antenna pattern does not change during the lifetime of an association",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"rx_stbc": {
|
||||
"description": "Supports reception of PPDUs using STBC",
|
||||
"type": "number",
|
||||
"default": 3,
|
||||
"minimum": 0,
|
||||
"maximum": 4
|
||||
},
|
||||
"rxantenna": {
|
||||
"description": "Specifies the antenna for receiving, the value may be driver specific, usually it is 1 for the first and 2 for the second antenna. Specifying 0 enables automatic selection by the driver if supported. This option has no effect if diversity is enabled",
|
||||
"type": "number"
|
||||
},
|
||||
"rxldpc": {
|
||||
"description": "Supports receiving LDPC coded pkts",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"short_gi_160": {
|
||||
"description": "Short GI for 160 MHz",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"short_gi_20": {
|
||||
"description": "Short GI for 20 MHz",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"short_gi_40": {
|
||||
"description": "Short GI for 40 MHz",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"short_gi_80": {
|
||||
"description": "Short GI for 80 MHz",
|
||||
"type": "boolean"
|
||||
},
|
||||
"spectrum_mgmt_required": {
|
||||
"description": "Set Spectrum Management subfield in the Capability Information field",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"stationary_ap": {
|
||||
"description": "Stationary AP config indicates that the AP doesn't move hence location data can be considered as always up to date.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"su_beamformee": {
|
||||
"description": "Single user beamformee",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"su_beamformer": {
|
||||
"description": "Single user beamformer",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"supported_rates": {
|
||||
"description": "Set the supported data rates. Each supported rate is measured in kb/s. This option only has an effect on ap and adhoc wifi-ifaces. This must be a superset of the rates set in basic_rate. The minimum basic rate should also be the minimum supported rate. It is recommended to use the cell_density option instead",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"tx_antenna_pattern": {
|
||||
"description": "Tx antenna pattern does not change during the lifetime of an association",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tx_burst": {
|
||||
"type": "alias",
|
||||
"default": "tx_queue_data2_burst"
|
||||
},
|
||||
"tx_queue_data2_burst": {
|
||||
"description": "",
|
||||
"type": "number"
|
||||
},
|
||||
"tx_stbc": {
|
||||
"description": "Transmit STBC (Space-Time Block Coding)",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"tx_stbc_2by1": {
|
||||
"description": "Supports transmission of at least 2×1 STBC",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"txantenna": {
|
||||
"description": "Specifies the antenna for transmitting, values are identical to rxantenna",
|
||||
"type": "number"
|
||||
},
|
||||
"txpower": {
|
||||
"description": "Specifies the maximum desired transmission power in dBm. The actual txpower used depends on regulatory requirements",
|
||||
"type": "number"
|
||||
},
|
||||
"vht160": {
|
||||
"description": "Supported channel widths. 0 == 160MHz and 80+80 MHz not supported, 1 == 160 MHz supported, 2 == 160MHz and 80+80 MHz supported",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 2,
|
||||
"default": 2
|
||||
},
|
||||
"vht_link_adapt": {
|
||||
"description": "TA supports link adaptation using VHT variant HT Control field",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 3
|
||||
},
|
||||
"vht_max_a_mpdu_len_exp": {
|
||||
"description": "Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv",
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"maximum": 7
|
||||
},
|
||||
"vht_max_mpdu": {
|
||||
"description": "Maximum MPDU length",
|
||||
"type": "number",
|
||||
"enum": [ 3895, 7991, 11454 ],
|
||||
"default": 11454
|
||||
},
|
||||
"vht_txop_ps": {
|
||||
"description": "VHT TXOP PS mode",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,20 @@
|
||||
{
|
||||
"$id": "https://openwrt.org/wifi.station.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "OpenWrt WiFi Station Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mac": {
|
||||
"description": "The stations MAC",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"description": "The passphrase that shall be used",
|
||||
"type": "string"
|
||||
},
|
||||
"vid": {
|
||||
"description": "The VLAN Id used by the station",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"$id": "https://openwrt.org/wifi.vlan.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"description": "OpenWrt WiFi VLAN Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "VLAN name",
|
||||
"type": "string"
|
||||
},
|
||||
"vid": {
|
||||
"description": "VLAN ID",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,480 @@
|
||||
'use strict';
|
||||
|
||||
import * as libuci from 'uci';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { append, append_raw, append_value, append_vars, comment, push_config, set_default, touch_file } from 'wifi.common';
|
||||
import * as netifd from 'wifi.netifd';
|
||||
import * as iface from 'wifi.iface';
|
||||
|
||||
function iface_setup(config) {
|
||||
switch(config.fixup) {
|
||||
case 'owe':
|
||||
config.ignore_broadcast_ssid = true;
|
||||
config.ssid = config.ssid + 'OWE';
|
||||
break;
|
||||
|
||||
case 'owe-transition':
|
||||
let ifname = config.ifname;
|
||||
config.ifname = config.owe_transition_ifname;
|
||||
config.owe_transition_ifname = ifname;
|
||||
config.owe_transition_ssid = config.ssid + 'OWE';
|
||||
config.encryption = 'none';
|
||||
config.ignore_broadcast_ssid = false;
|
||||
iface.prepare(config);
|
||||
break;
|
||||
}
|
||||
|
||||
comment('Setup interface: ' + config.ifname);
|
||||
|
||||
config.bridge = config.network_bridge;
|
||||
config.snoop_iface = config.network_ifname;
|
||||
if (!config.wds)
|
||||
config.wds_bridge = null;
|
||||
else
|
||||
config.wds_sta = true;
|
||||
|
||||
if (!config.idx)
|
||||
append('interface', config.ifname);
|
||||
else
|
||||
append('bss', config.ifname);
|
||||
|
||||
if (config.multicast_to_unicast || config.proxy_arp)
|
||||
config.ap_isolate = 1;
|
||||
|
||||
append('bssid', config.macaddr);
|
||||
|
||||
append_vars(config, [
|
||||
'ctrl_interface', 'ap_isolate', 'max_num_sta', 'ap_max_inactivity', 'airtime_bss_weight',
|
||||
'airtime_bss_limit', 'airtime_sta_weight', 'bss_load_update_period', 'chan_util_avg_period',
|
||||
'disassoc_low_ack', 'skip_inactivity_poll', 'ignore_broadcast_ssid', 'uapsd_advertisement_enabled',
|
||||
'utf8_ssid', 'multi_ap', 'ssid', 'tdls_prohibit', 'bridge', 'wds_sta', 'wds_bridge',
|
||||
'snoop_iface', 'vendor_elements', 'nas_identifier', 'radius_acct_interim_interval',
|
||||
'ocv', 'multicast_to_unicast', 'preamble', 'wmm_enabled', 'proxy_arp', 'per_sta_vif', 'mbo',
|
||||
'bss_transition', 'wnm_sleep_mode', 'wnm_sleep_mode_no_keys', 'qos_map_set', 'max_listen_int',
|
||||
'dtim_period',
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_authentication_server(config) {
|
||||
for (let server in config.auth_server_addr) {
|
||||
append('auth_server_addr', server);
|
||||
append_vars(config, [ 'auth_server_port', 'auth_server_shared_secret' ]);
|
||||
}
|
||||
|
||||
append_vars(config, [ 'radius_auth_req_attr' ]);
|
||||
}
|
||||
|
||||
function iface_accounting_server(config) {
|
||||
for (let server in config.acct_server_addr) {
|
||||
append('acct_server_addr', server);
|
||||
append_vars(config, [ 'acct_server_port', 'acct_server_shared_secret' ]);
|
||||
}
|
||||
|
||||
append_vars(config, [ 'radius_acct_req_attr' ]);
|
||||
}
|
||||
|
||||
function iface_auth_type(config) {
|
||||
iface.parse_encryption(config);
|
||||
|
||||
if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) {
|
||||
config.ieee80211w = 2;
|
||||
config.sae_require_mfp = 1;
|
||||
config.sae_pwe = 2;
|
||||
}
|
||||
|
||||
if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) {
|
||||
config.ieee80211w = 1;
|
||||
config.sae_require_mfp = 1;
|
||||
config.sae_pwe = 2;
|
||||
}
|
||||
|
||||
if (config.own_ip_addr)
|
||||
config.dynamic_own_ip_addr = null;
|
||||
|
||||
if (!config.wpa)
|
||||
config.wpa_disable_eapol_key_retries = null;
|
||||
|
||||
switch(config.auth_type) {
|
||||
case 'none':
|
||||
case 'owe':
|
||||
config.wps_possible = 1;
|
||||
config.wps_state = 1;
|
||||
|
||||
if (config.owe_transition_ssid)
|
||||
config.owe_transition_ssid = `"${config.owe_transition_ssid}"`;
|
||||
|
||||
append_vars(config, [
|
||||
'owe_transition_ssid', 'owe_transition_bssid', 'owe_transition_ifname',
|
||||
]);
|
||||
break;
|
||||
|
||||
case 'psk':
|
||||
case 'psk2':
|
||||
case 'sae':
|
||||
case 'psk-sae':
|
||||
config.vlan_possible = 1;
|
||||
config.wps_possible = 1;
|
||||
|
||||
if (config.auth_type == 'psk' && config.ppsk) {
|
||||
iface_authentication_server(config);
|
||||
config.macaddr_acl = 2;
|
||||
config.wpa_psk_radius = 2;
|
||||
} else if (length(config.key) == 64) {
|
||||
config.wpa_psk = key;
|
||||
} else if (length(config.key) >= 8) {
|
||||
config.wpa_passphrase = config.key;
|
||||
} else if (!config.wpa_psk_file) {
|
||||
netifd.setup_failed('INVALID_WPA_PSK');
|
||||
}
|
||||
|
||||
set_default(config, 'wpa_psk_file', `/var/run/hostapd-${config.ifname}.psk`);
|
||||
touch_file(config.wpa_psk_file);
|
||||
set_default(config, 'dynamic_vlan', 0);
|
||||
break;
|
||||
|
||||
case 'eap':
|
||||
case 'eap2':
|
||||
case 'eap-eap2':
|
||||
case 'eap192':
|
||||
config.vlan_possible = 1;
|
||||
|
||||
if (config.fils) {
|
||||
set_default(config, 'erp_domain', substr(digest.md5(config.ssid), 0, 4));
|
||||
set_default(config, 'fils_realm', config.erp_domain);
|
||||
set_default(config, 'erp_send_reauth_start', 1);
|
||||
set_default(config, 'fils_cache_id', substr(digest.md5(config.fils_realm), 0, 4));
|
||||
}
|
||||
|
||||
if (!config.eap_server) {
|
||||
iface_authentication_server(config);
|
||||
iface_accounting_server(config);
|
||||
}
|
||||
|
||||
if (config.radius_das_client && config.radius_das_secret) {
|
||||
set_default(config, 'radius_das_port', 3799);
|
||||
set_default(config, 'radius_das_client', `${config.radius_das_client} ${config.radius_das_secret}`);
|
||||
}
|
||||
|
||||
set_default(config, 'eapol_version', config.wpa & 1);
|
||||
if (!config.eapol_version)
|
||||
config.eapol_version = null;
|
||||
append('eapol_key_index_workaround', '1');
|
||||
append('ieee8021x', '1');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
append_vars(config, [
|
||||
'sae_require_mfp', 'sae_pwe', 'time_advertisement', 'time_zone',
|
||||
'wpa_group_rekey', 'wpa_ptk_rekey', 'wpa_gmk_rekey', 'wpa_strict_rekey',
|
||||
'macaddr_acl', 'wpa_psk_radius', 'wpa_psk', 'wpa_passphrase', 'wpa_psk_file',
|
||||
'eapol_version', 'dynamic_vlan', 'radius_request_cui', 'eap_reauth_period',
|
||||
'radius_das_client', 'radius_das_port', 'own_ip_addr', 'dynamic_own_ip_addr',
|
||||
'wpa_disable_eapol_key_retries', 'auth_algs', 'wpa', 'wpa_pairwise',
|
||||
'erp_domain', 'fils_realm', 'erp_send_reauth_start', 'fils_cache_id'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_ppsk(config) {
|
||||
if (!(config.auth_type in [ 'none', 'owe', 'psk', 'sae', 'psk-sae', 'wep' ]) || !config.auth_server_addr)
|
||||
return;
|
||||
|
||||
iface_authentication_server(config);
|
||||
append('macaddr_acl', '2');
|
||||
}
|
||||
|
||||
function iface_wps(config) {
|
||||
push_config(config, 'config_methods', 'wps_pushbutton', 'push_button');
|
||||
push_config(config, 'config_methods', 'wps_label', 'label');
|
||||
|
||||
if (config.multi_ap == 1)
|
||||
config.wps_possible = false;
|
||||
|
||||
if (config.wps_possible && length(config.config_methods)) {
|
||||
config.eap_server = 1;
|
||||
set_default(config, 'wps_state', 2);
|
||||
|
||||
if (config.ext_registrar && config.network_bridge)
|
||||
set_default(config, 'upnp_iface', config.network_bridge);
|
||||
|
||||
if (config.multi_ap && config.multi_ap_backhaul_ssid) {
|
||||
append_vars(config, [ 'multi_ap_backhaul_ssid' ]);
|
||||
if (length(config.multi_ap_backhaul_key) == 64)
|
||||
append('multi_ap_backhaul_wpa_psk', config.multi_ap_backhaul_key);
|
||||
else if (length(config.multi_ap_backhaul_key) > 8)
|
||||
append('multi_ap_backhaul_wpa_passphrase', config.multi_ap_backhaul_key);
|
||||
else
|
||||
netifd.setup_failed('INVALID_WPA_PSK');
|
||||
}
|
||||
|
||||
append_vars(config, [
|
||||
'wps_state', 'device_type', 'device_name', 'config_methods', 'wps_independent', 'eap_server',
|
||||
'ap_pin', 'ap_setup_locked', 'upnp_iface'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
function iface_rrm(config) {
|
||||
set_default(config, 'rrm_neighbor_report', config.ieee80211k);
|
||||
set_default(config, 'rrm_beacon_report', config.ieee80211k);
|
||||
|
||||
append_vars(config, [
|
||||
'rrm_neighbor_report', 'rrm_beacon_report', 'rnr', 'ftm_responder',
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_ftm(config, phy_features) {
|
||||
if (!phy_features.ftm_responder || !config.ftm_responder)
|
||||
return;
|
||||
|
||||
append_vars(config, [
|
||||
'ftm_responder', 'lci', 'civic'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_macfilter(config) {
|
||||
let path = `/var/run/hostapd-${config.ifname}.maclist`;
|
||||
|
||||
switch(config.macfilter) {
|
||||
case 'allow':
|
||||
append('accept_mac_file', path);
|
||||
append('macaddr_acl', 1);
|
||||
config.vlan_possible = 1;
|
||||
break;
|
||||
|
||||
case 'deny':
|
||||
append('deny_mac_file', path);
|
||||
append('macaddr_acl', 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
let file = fs.open(path, 'w');
|
||||
if (!file) {
|
||||
warn(`Failed to open ${path}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.maclist)
|
||||
file.write(join('\n', config.maclist));
|
||||
|
||||
let macfile = fs.readfile(config.macfile);
|
||||
if (macfile)
|
||||
file.write(macfile);
|
||||
file.close();
|
||||
}
|
||||
|
||||
function iface_vlan(interface, config, vlans) {
|
||||
let path = `/var/run/hostapd-${config.ifname}.vlan`;
|
||||
|
||||
let file = fs.open(path, 'w');
|
||||
for (let k, vlan in vlans)
|
||||
if (vlan.config.name && vlan.config.vid) {
|
||||
let ifname = `${config.ifname}-${vlan.config.name}`;
|
||||
file.write(`${vlan.config.vid} ${ifname}\n`);
|
||||
netifd.set_vlan(interface, k, ifname);
|
||||
}
|
||||
file.close();
|
||||
|
||||
set_default(config, 'vlan_file', path);
|
||||
append_vars(config, [ 'vlan_file' ]);
|
||||
|
||||
if (!config.vlan_possible || !config.dynamic_vlan)
|
||||
return;
|
||||
|
||||
config.vlan_no_bridge = !config.vlan_bridge;
|
||||
|
||||
append_vars(config, [
|
||||
'dynamic_vlan', 'vlan_naming', 'vlan_bridge', 'vlan_no_bridge',
|
||||
'vlan_tagged_interface'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_stations(config, stas) {
|
||||
if (!length(stas))
|
||||
return;
|
||||
|
||||
let path = `/var/run/hostapd-${config.ifname}.psk`;
|
||||
|
||||
let file = fs.open(path, 'w');
|
||||
for (let k, sta in stas)
|
||||
if (sta.config.mac && sta.config.key) {
|
||||
let station = `${sta.config.mac} ${sta.config.key}\n`;
|
||||
if (sta.config.vid)
|
||||
station = `vlanid=${sta.config.vid} ` + station;
|
||||
file.write(station);
|
||||
}
|
||||
file.close();
|
||||
|
||||
set_default(config, 'wpa_psk_file', path);
|
||||
}
|
||||
|
||||
function iface_eap_server(config) {
|
||||
if (!config.eap_server)
|
||||
return;
|
||||
|
||||
set_default(config, 'eap_server', true);
|
||||
set_default(config, 'eap_server_erp', true);
|
||||
|
||||
append_vars(config, [
|
||||
'eap_server', 'eap_server_erp', 'eap_user_file', 'ca_cert', 'server_cert',
|
||||
'private_key', 'private_key_passwd', 'server_id',
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_roaming(config) {
|
||||
if (!config.ieee80211r || config.wpa < 2)
|
||||
return;
|
||||
|
||||
set_default(config, 'mobility_domain', substr(digest.md5(config.ssid), 0, 4));
|
||||
set_default(config, 'ft_psk_generate_local', config.auth_type == 'psk');
|
||||
set_default(config, 'ft_iface', config.network_ifname);
|
||||
|
||||
if (config.ft_psk_generate_local) {
|
||||
if (!config.r0kh || !config.r1kh) {
|
||||
if (!config.auth_secret && !config.key)
|
||||
netifd.setup_failed('FT_KEY_CANT_BE_DERIVED');
|
||||
|
||||
let ft_key = digest.md5(`${mobility_domain}/${auth_secret ?? key}`);
|
||||
|
||||
set_default(config, 'r0kh', 'ff:ff:ff:ff:ff:ff,*,' + ft_key);
|
||||
set_default(config, 'r1kh', '00:00:00:00:00:00,00:00:00:00:00:00,' + ft_key);
|
||||
}
|
||||
|
||||
append_vars(config, [
|
||||
'r0kh', 'r1kh', 'r1_key_holder', 'r0_key_lifetime', 'pmk_r1_push'
|
||||
]);
|
||||
}
|
||||
|
||||
append_vars(config, [
|
||||
'mobility_domain', 'ft_psk_generate_local', 'ft_over_ds', 'reassociation_deadline',
|
||||
'ft_iface'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_mfp(config) {
|
||||
if (!config.ieee80211w || config.wpa < 2) {
|
||||
append('ieee80211w', 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (config.auth_type == 'eap192')
|
||||
config.group_mgmt_cipher = 'BIP-GMAC-256';
|
||||
else
|
||||
config.group_mgmt_cipher = config.ieee80211w_mgmt_cipher ?? 'AES-128-CMAC';
|
||||
|
||||
append_vars(config, [
|
||||
'ieee80211w', 'group_mgmt_cipher', 'assoc_sa_query_max_timeout', 'assoc_sa_query_retry_timeout'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_key_caching(config) {
|
||||
if (config.wpa < 2)
|
||||
return;
|
||||
|
||||
if (config.network_bridge && config.rsn_preauth) {
|
||||
set_default(config, 'okc', true);
|
||||
config.rsn_preauth_interfaces = config.network_bridge;
|
||||
|
||||
append_vars(config, [
|
||||
'rsn_preauth', 'rsn_preauth_interfaces'
|
||||
]);
|
||||
} else {
|
||||
set_default(config, 'okc', (config.auth_type in [ 'sae', 'psk-sae', 'owe' ]));
|
||||
}
|
||||
|
||||
if (!config.okc && !config.fils)
|
||||
config.disable_pmksa_caching = 1;
|
||||
|
||||
append_vars(config, [
|
||||
'okc', 'disable_pmksa_caching'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_hs20(config) {
|
||||
if (!config.hs20)
|
||||
return;
|
||||
|
||||
let uci = libuci.cursor();
|
||||
let icons = uci.get_all('wireless');
|
||||
for (let k, icon in icons)
|
||||
if (icon['.type'] == 'hs20-icon')
|
||||
append('hs20_icon', `${icon.width}:${icon.heigth}:${icon.lang}:${icon.type}:${k}:${icon.path}`);
|
||||
|
||||
append_vars(config, [
|
||||
'hs20', 'disable_dgaf', 'osen', 'anqp_domain_id', 'hs20_deauth_req_timeout', 'osu_ssid',
|
||||
'hs20_wan_metrics', 'hs20_operating_class', 'hs20_t_c_filename', 'hs20_t_c_timestamp',
|
||||
'hs20_t_c_server_url', 'hs20_oper_friendly_name', 'hs20_conn_capab', 'osu_provider',
|
||||
'operator_icon'
|
||||
]);
|
||||
}
|
||||
|
||||
function iface_interworking(config) {
|
||||
if (!config.iw_enabled)
|
||||
return;
|
||||
|
||||
config.interworking = true;
|
||||
|
||||
if (config.domain_name)
|
||||
config.domain_name = join(',', config.domain_name);
|
||||
|
||||
if (config.anqp_3gpp_cell_net)
|
||||
config.domain_name = join(',', config.anqp_3gpp_cell_net);
|
||||
|
||||
append_vars(config, [
|
||||
'interworking', 'internet', 'asra', 'uesa', 'access_network_type', 'hessid', 'venue_group',
|
||||
'venue_type', 'network_auth_type', 'gas_address3', 'roaming_consortium', 'anqp_elem', 'nai_realm',
|
||||
'venue_name', 'venue_url', 'domain_name', 'anqp_3gpp_cell_net',
|
||||
]);
|
||||
}
|
||||
|
||||
export function generate(interface, config, vlans, stas, phy_features) {
|
||||
config.ctrl_interface = '/var/run/hostapd';
|
||||
|
||||
iface_stations(config, stas);
|
||||
|
||||
iface_setup(config);
|
||||
|
||||
iface_auth_type(config);
|
||||
|
||||
iface_accounting_server(config);
|
||||
|
||||
iface_ppsk(config);
|
||||
|
||||
iface_wps(config);
|
||||
|
||||
iface_rrm(config);
|
||||
|
||||
iface_ftm(config, phy_features);
|
||||
|
||||
iface_macfilter(config);
|
||||
|
||||
iface_vlan(interface, config, vlans);
|
||||
|
||||
iface_eap_server(config);
|
||||
|
||||
iface_roaming(config);
|
||||
|
||||
iface_mfp(config);
|
||||
|
||||
iface_key_caching(config);
|
||||
|
||||
iface_hs20(config);
|
||||
|
||||
iface_interworking(config);
|
||||
|
||||
iface.wpa_key_mgmt(config);
|
||||
append_vars(config, [
|
||||
'wpa_key_mgmt'
|
||||
]);
|
||||
|
||||
/* raw options */
|
||||
for (let raw in config.hostapd_options)
|
||||
append_raw(raw);
|
||||
|
||||
if (config.default_macaddr)
|
||||
append_raw('#default_macaddr');
|
||||
};
|
@ -0,0 +1,124 @@
|
||||
'use strict';
|
||||
|
||||
import * as libubus from 'ubus';
|
||||
import * as fs from 'fs';
|
||||
|
||||
global.ubus = libubus.connect();
|
||||
|
||||
let config_data = '';
|
||||
let network_data = '';
|
||||
|
||||
export function log(msg) {
|
||||
printf(`wifi-scripts: ${msg}\n`);
|
||||
};
|
||||
|
||||
export function append_raw(value) {
|
||||
config_data += value + '\n';
|
||||
};
|
||||
|
||||
export function append(key, value) {
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
switch (type(value)) {
|
||||
case 'array':
|
||||
value = join(' ', value);
|
||||
break;
|
||||
case 'bool':
|
||||
value = value ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
append_raw(key + '=' + value);
|
||||
};
|
||||
|
||||
export function append_vars(dict, keys) {
|
||||
for (let key in keys)
|
||||
append(key, dict[key]);
|
||||
};
|
||||
|
||||
export function network_append_raw(value) {
|
||||
network_data += value + '\n';
|
||||
};
|
||||
|
||||
export function network_append(key, value) {
|
||||
if (value == null)
|
||||
return;
|
||||
|
||||
switch (type(value)) {
|
||||
case 'array':
|
||||
value = join(' ', value);
|
||||
break;
|
||||
case 'bool':
|
||||
value = value ? 1 : 0;
|
||||
break;
|
||||
}
|
||||
|
||||
network_append_raw('\t' + key + '=' + value);
|
||||
};
|
||||
|
||||
export function network_append_vars(dict, keys) {
|
||||
for (let key in keys)
|
||||
network_append(key, dict[key]);
|
||||
};
|
||||
|
||||
export function set_default(dict, key, value) {
|
||||
if (dict[key] == null)
|
||||
dict[key] = value;
|
||||
};
|
||||
|
||||
export function push_config(dict, key, option, value) {
|
||||
if (!dict[option])
|
||||
return;
|
||||
|
||||
dict[key] ??= [];
|
||||
push(dict[key], value);
|
||||
};
|
||||
|
||||
export function touch_file(filename) {
|
||||
let file = fs.open(filename, "a");
|
||||
if (file)
|
||||
file.close();
|
||||
else
|
||||
log('Failed to touch ' + filename);
|
||||
};
|
||||
|
||||
export function append_value(config, key, value) {
|
||||
if (!config[key])
|
||||
config[key] = value;
|
||||
else
|
||||
config[key] += ' ' + value;
|
||||
};
|
||||
|
||||
export function comment(comment) {
|
||||
append_raw('\n# ' + comment);
|
||||
};
|
||||
|
||||
export function dump_config(file) {
|
||||
if (file)
|
||||
fs.writefile(file, config_data);
|
||||
|
||||
return config_data;
|
||||
};
|
||||
|
||||
export function dump_network(file) {
|
||||
config_data += 'network={\n';
|
||||
config_data += network_data;;
|
||||
config_data += '}\n';
|
||||
|
||||
if (file)
|
||||
fs.writefile(file, config_data);
|
||||
|
||||
printf('%s\n', config_data);
|
||||
|
||||
return config_data;
|
||||
};
|
||||
|
||||
export function flush_config() {
|
||||
config_data = '';
|
||||
};
|
||||
|
||||
export function flush_network() {
|
||||
config_data = '';
|
||||
network_data = '';
|
||||
};
|
@ -0,0 +1,577 @@
|
||||
'use strict';
|
||||
|
||||
import { append, append_raw, append_vars, dump_config, flush_config, set_default } from 'wifi.common';
|
||||
import { validate } from 'wifi.validate';
|
||||
import * as netifd from 'wifi.netifd';
|
||||
import * as iface from 'wifi.iface';
|
||||
import * as nl80211 from 'nl80211';
|
||||
import * as ap from 'wifi.ap';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER = 33;
|
||||
const NL80211_EXT_FEATURE_RADAR_BACKGROUND = 61;
|
||||
|
||||
let phy_features = {};
|
||||
let phy_capabilities = {};
|
||||
|
||||
/* make sure old style UCI and hwmode and newer band properties are correctly resolved */
|
||||
function set_device_defaults(config) {
|
||||
/* validate the hw mode */
|
||||
if (config.hwmode in [ '11a', '11b', '11g', '11ad' ])
|
||||
config.hw_mode = substr(config.hwmode, 2);
|
||||
else if (config.channel > 14)
|
||||
config.hw_mode = 'a';
|
||||
else
|
||||
config.hw_mode = 'g';
|
||||
|
||||
/* validate band */
|
||||
if (config.band == '2g')
|
||||
config.hw_mode = 'g';
|
||||
else if (config.band in [ '5g', '6g', '60g' ])
|
||||
config.hw_mode = 'a';
|
||||
else
|
||||
switch (config.hw_mode) {
|
||||
case 'a':
|
||||
config.band = '5g';
|
||||
break;
|
||||
|
||||
case 'ad':
|
||||
config.band = '60g';
|
||||
break;
|
||||
|
||||
default:
|
||||
config.band = '2g';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* setup sylog / stdout */
|
||||
function device_log_append(config) {
|
||||
let log_mask = 0;
|
||||
|
||||
for (let k in [ 'log_mlme', 'log_iapp', 'log_driver', 'log_wpa', 'log_radius', 'log_8021x', 'log_80211' ]) {
|
||||
log_mask <<= 1;
|
||||
log_mask |= config[k] ? 1 : 0;
|
||||
}
|
||||
|
||||
append('logger_syslog', log_mask);
|
||||
append('logger_syslog_level', config.log_level);
|
||||
append('logger_stdout', log_mask);
|
||||
append('logger_stdout_level', config.log_level);
|
||||
}
|
||||
|
||||
/* setup country code */
|
||||
function device_country_code(config) {
|
||||
let status = global.ubus.call('network.wireless', 'status');
|
||||
for (let name, radio in status) {
|
||||
if (!radio.config.country)
|
||||
continue;
|
||||
config.country_code = radio.config.country;
|
||||
}
|
||||
|
||||
if (!exists(config, 'country_code'))
|
||||
return;
|
||||
|
||||
if (config.hw_mode != 'a')
|
||||
delete config.ieee80211h;
|
||||
append_vars(config, [ 'country_code', 'country3', 'ieee80211h' ]);
|
||||
if (config.ieee80211d)
|
||||
append_vars(config, [ 'ieee80211d', 'local_pwr_constraint', 'spectrum_mgmt_required' ]);
|
||||
}
|
||||
|
||||
|
||||
/* setup cell density */
|
||||
function device_cell_density_append(config) {
|
||||
switch (config.hw_mode) {
|
||||
case 'b':
|
||||
if (config.cell_density == 1) {
|
||||
config.supported_rates = [ 5500, 11000 ];
|
||||
config.basic_rates = [ 5500, 11000 ];
|
||||
} else if (config.cell_density > 2) {
|
||||
config.supported_rates = [ 11000 ];
|
||||
config.basic_rates = [ 11000 ];
|
||||
}
|
||||
;;
|
||||
case 'g':
|
||||
if (config.cell_density in [ 0, 1 ]) {
|
||||
if (!config.legacy_rates) {
|
||||
config.supported_rates = [ 6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 6000, 12000, 24000 ];
|
||||
} else if (config.cell_density == 1) {
|
||||
config.supported_rates = [ 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 5500, 11000 ];
|
||||
}
|
||||
} else if (config.cell_density == 2 || (config.cell_density > 3 && config.legacy_rates)) {
|
||||
if (!config.legacy_rates) {
|
||||
config.supported_rates = [ 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 12000, 24000 ];
|
||||
} else {
|
||||
config.supported_rates = [ 11000, 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 11000 ];
|
||||
}
|
||||
} else if (config.cell_density > 2) {
|
||||
config.supported_rates = [ 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 24000 ];
|
||||
}
|
||||
;;
|
||||
case 'a':
|
||||
switch (config.cell_density) {
|
||||
case 1:
|
||||
config.supported_rates = [ 6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 6000, 12000, 24000 ];
|
||||
break;
|
||||
|
||||
case 2:
|
||||
config.supported_rates = [ 12000, 18000, 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 12000, 24000 ];
|
||||
break;
|
||||
|
||||
case 3:
|
||||
config.supported_rates = [ 24000, 36000, 48000, 54000 ];
|
||||
config.basic_rates = [ 24000 ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function device_rates(config) {
|
||||
for (let key in [ 'supported_rates', 'basic_rates' ])
|
||||
config[key] = map(config[key], x => x / 100);
|
||||
|
||||
append_vars(config, [ 'beacon_rate', 'supported_rates', 'basic_rates' ]);
|
||||
}
|
||||
|
||||
function device_htmode_append(config) {
|
||||
config.channel_offset = config.band == '6g' ? 1 : 0;
|
||||
|
||||
/* 802.11n */
|
||||
config.ieee80211n = 0;
|
||||
if (config.band != '6g') {
|
||||
if (config.htmode in [ 'VHT20', 'HT20', 'HE20', 'EHT20' ])
|
||||
config.ieee80211n = 1;
|
||||
if (config.htmode in [ 'HT40', 'HT40+', 'HT40-', 'VHT40', 'VHT80', 'VHT160', 'HE40', 'HE80', 'HE160', 'EHT40', 'EHT80', 'EHT160' ]) {
|
||||
config.ieee80211n = 1;
|
||||
if (!config.channel)
|
||||
config.ht_capab = '[HT40+]';
|
||||
else
|
||||
switch (config.hw_mode) {
|
||||
case 'a':
|
||||
switch (((config.channel / 4) + config.channel_offset) % 2) {
|
||||
case 0:
|
||||
config.ht_capab = '[HT40-]';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
config.ht_capab = '[HT40+]';
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
switch (config.htmode) {
|
||||
case 'HT40+':
|
||||
case 'HT40-':
|
||||
config.ht_capab = '[' + config.htmode + ']';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (config.channel < 7)
|
||||
config.ht_capab = '[HT40+]';
|
||||
else
|
||||
config.ht_capab = '[HT40-]';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.ieee80211n) {
|
||||
let ht_capab = phy_capabilities.ht_capa;
|
||||
|
||||
if (ht_capab & 0x1 && config.ldpc)
|
||||
config.ht_capab += '[LDPC]';
|
||||
if (ht_capab & 0x10 && config.greenfield)
|
||||
config.ht_capab += '[GF]';
|
||||
if (ht_capab & 0x20 && config.short_gi_20)
|
||||
config.ht_capab += '[SHORT-GI-20]';
|
||||
if (ht_capab & 0x40 && config.short_gi_40)
|
||||
config.ht_capab += '[SHORT-GI-40]';
|
||||
if (ht_capab & 0x80 && config.tx_stbc)
|
||||
config.ht_capab += '[TX-STBC]';
|
||||
if (ht_capab & 0x800 && config.max_amsdu)
|
||||
config.ht_capab += '[MAX-AMSDU-7935]';
|
||||
if (ht_capab & 0x1000 && config.dsss_cck_40)
|
||||
config.ht_capab += '[DSSS_CCK-40]';
|
||||
let rx_stbc = [ '', '[RX-STBC1]', '[RX-STBC12]', '[RX-STBC123]' ];
|
||||
config.ht_capab += rx_stbc[min(config.rx_stbc, (ht_capab >> 8) & 3)];
|
||||
|
||||
append_vars(config, [ 'ieee80211n', 'ht_coex', 'ht_capab' ]);
|
||||
}
|
||||
}
|
||||
|
||||
/* 802.11ac */
|
||||
config.ieee80211ac = 1;
|
||||
config.vht_oper_centr_freq_seg0_idx = 0;
|
||||
config.vht_oper_chwidth = 0;
|
||||
|
||||
switch (config.htmode) {
|
||||
case 'VHT20':
|
||||
case 'HE20':
|
||||
case 'EHT20':
|
||||
break;
|
||||
|
||||
case 'VHT40':
|
||||
case 'HE40':
|
||||
case 'EHT40':
|
||||
config.vht_oper_centr_freq_seg0_idx = config.channel + (((config.channel / 4) + config.channel_offset) % 2 ? 2 : -2);
|
||||
break;
|
||||
|
||||
case 'VHT80':
|
||||
case 'HE80':
|
||||
case 'EHT80':
|
||||
let delta = [ -6, 6, 2, -2 ];
|
||||
config.vht_oper_centr_freq_seg0_idx = config.channel + delta[((config.channel / 4) + config.channel_offset) % 4];
|
||||
config.vht_oper_chwidth = 1;
|
||||
break;
|
||||
|
||||
case 'VHT160':
|
||||
case 'HE160':
|
||||
case 'EHT160':
|
||||
let vht_oper_centr_freq_seg0_idx_map = [[ 64, 50 ], [ 128, 114 ], [ 177, 163 ]];
|
||||
if (config.band == '6g')
|
||||
vht_oper_centr_freq_seg0_idx_map = [
|
||||
[ 29, 15 ], [ 61, 47 ], [ 93, 79 ], [ 125, 111 ],
|
||||
[ 157, 143 ], [ 189, 175 ], [ 221, 207 ]];
|
||||
for (let k, v in vht_oper_centr_freq_seg0_idx_map)
|
||||
if (v[0] <= config.channel) {
|
||||
config.vht_oper_centr_freq_seg0_idx = v[1];
|
||||
break;
|
||||
}
|
||||
config.vht_oper_chwidth = 2;
|
||||
break;
|
||||
|
||||
default:
|
||||
config.ieee80211ac = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
config.eht_oper_chwidth = config.vht_oper_chwidth;
|
||||
config.eht_oper_centr_freq_seg0_idx = config.vht_oper_centr_freq_seg0_idx;
|
||||
|
||||
if (config.band == '6g') {
|
||||
config.ieee80211ac = 0;
|
||||
|
||||
switch(config.htmode) {
|
||||
case 'HE20':
|
||||
case 'EHT20':
|
||||
config.op_class = 131;
|
||||
break;
|
||||
|
||||
case 'EHT320':
|
||||
let eht_center_seg0_map = [
|
||||
[ 61, 31 ], [ 125, 95 ], [ 189, 159 ], [ 221, 191 ]
|
||||
];
|
||||
|
||||
for (let k, v in eht_center_seg0_map)
|
||||
if (v[0] <= config.channel) {
|
||||
config.eht_oper_centr_freq_seg0_idx = v[1];
|
||||
break;
|
||||
}
|
||||
config.op_class = 137;
|
||||
config.eht_oper_chwidth = 7;
|
||||
break;
|
||||
|
||||
case 'HE40':
|
||||
case 'HE80':
|
||||
case 'HE160':
|
||||
case 'EHT40':
|
||||
case 'EHT80':
|
||||
case 'EHT160':
|
||||
config.op_class = 132 + config.eht_oper_chwidth;
|
||||
break;
|
||||
}
|
||||
|
||||
append_vars(config, [ 'op_class' ]);
|
||||
}
|
||||
|
||||
if (config.ieee80211ac && config.hw_mode == 'a') {
|
||||
/* VHT capab */
|
||||
if (config.vht_oper_chwidth < 2) {
|
||||
config.vht160 = 0;
|
||||
config.short_gi_160 = 0;
|
||||
}
|
||||
|
||||
config.tx_queue_data2_burst = '2.0';
|
||||
|
||||
let vht_capab = phy_capabilities.vht_capa;
|
||||
|
||||
config.vht_capab = '';
|
||||
if (vht_capab & 0x10 && config.rxldpc)
|
||||
config.vht_capab += '[RXLDPC]';
|
||||
if (vht_capab & 0x20 && config.short_gi_80)
|
||||
config.vht_capab += '[SHORT-GI-80]';
|
||||
if (vht_capab & 0x40 && config.short_gi_160)
|
||||
config.vht_capab += '[SHORT-GI-160]';
|
||||
if (vht_capab & 0x80 && config.tx_stbc_2by1)
|
||||
config.vht_capab += '[TX-STBC-2BY1]';
|
||||
if (vht_capab & 0x800 && config.su_beamformer)
|
||||
config.vht_capab += '[SU-BEAMFORMER]';
|
||||
if (vht_capab & 0x1000 && config.su_beamformee)
|
||||
config.vht_capab += '[SU-BEAMFORMEE]';
|
||||
if (vht_capab & 0x80000 && config.mu_beamformer)
|
||||
config.vht_capab += '[MU-BEAMFORMER]';
|
||||
if (vht_capab & 0x100000 && config.mu_beamformee)
|
||||
config.vht_capab += '[MU-BEAMFORMEE]';
|
||||
if (vht_capab & 0x200000 && config.vht_txop_ps)
|
||||
config.vht_capab += '[VHT-TXOP-PS]';
|
||||
if (vht_capab & 0x400000 && config.htc_vht)
|
||||
config.vht_capab += '[HTC-VHT]';
|
||||
if (vht_capab & 0x10000000 && config.rx_antenna_pattern)
|
||||
config.vht_capab += '[RX-ANTENNA-PATTERN]';
|
||||
if (vht_capab & 0x20000000 && config.tx_antenna_pattern)
|
||||
config.vht_capab += '[TX-ANTENNA-PATTERN]';
|
||||
let rx_stbc = [ '', '[RX-STBC1]', '[RX-STBC12]', '[RX-STBC123]', '[RX-STBC-1234]' ];
|
||||
config.vht_capab += rx_stbc[min(config.rx_stbc, (vht_capab >> 8) & 7)];
|
||||
|
||||
if (vht_capab & 0x800 && config.su_beamformer)
|
||||
config.vht_capab += '[SOUNDING-DIMENSION' + min(((vht_capab >> 16) & 3) + 1, config.beamformer_antennas) + ']';
|
||||
if (vht_capab & 0x1000 && config.su_beamformee)
|
||||
config.vht_capab += '[BF-ANTENNA-' + min(((vht_capab >> 13) & 3) + 1, config.beamformer_antennas) + ']';
|
||||
|
||||
/* supported Channel widths */
|
||||
if (vht_capab & 0xc == 8 && config.vht160 <= 2)
|
||||
config.vht_capab += '[VHT160-80PLUS80]';
|
||||
else if (vht_capab & 0xc == 4 && config.vht160 <= 1)
|
||||
config.vht_capab += '[VHT160]';
|
||||
|
||||
/* maximum MPDU length */
|
||||
if (vht_capab & 3 > 1 && config.vht_max_mpdu > 11454)
|
||||
config.vht_capab += '[MAX-MPDU-11454]';
|
||||
else if (vht_capab & 3 && config.vht_max_mpdu > 7991)
|
||||
config.vht_capab += '[MAX-MPDU-7991]';
|
||||
|
||||
/* maximum A-MPDU length exponent */
|
||||
let max_a_mpdu_len_exp = (vht_capab >> 20) & 0x38;
|
||||
for (let exp = 7; exp; exp--)
|
||||
if (max_a_mpdu_len_exp >= (0x8 * exp) && exp <= config.vht_max_a_mpdu_len_exp) {
|
||||
config.vht_capab += '[MAX-A-MPDU-LEN-EXP' + exp + ']';
|
||||
break;
|
||||
}
|
||||
|
||||
/* whether or not the STA supports link adaptation using VHT variant */
|
||||
let vht_link_adapt = vht_capab & 0xC000000;
|
||||
if (vht_link_adapt >= 0xC000000 && config.vht_link_adapt > 3)
|
||||
config.vht_capab += '[VHT-LINK-ADAPT-3]';
|
||||
if (vht_link_adapt >= 0x8000000 && config.vht_link_adapt > 2)
|
||||
config.vht_capab += '[VHT-LINK-ADAPT-2]';
|
||||
|
||||
append_vars(config, [
|
||||
'ieee80211ac', 'vht_oper_chwidth', 'vht_oper_centr_freq_seg0_idx',
|
||||
'vht_capab'
|
||||
]);
|
||||
}
|
||||
|
||||
/* 802.11ax */
|
||||
if (wildcard(config.htmode, 'HE*') || wildcard(config.htmode, 'EHT*')) {
|
||||
let he_phy_cap = phy_capabilities.he_phy_cap;
|
||||
let he_mac_cap = phy_capabilities.he_mac_cap;
|
||||
|
||||
config.ieee80211ax = true;
|
||||
|
||||
if (config.hw_mode == 'a') {
|
||||
config.he_oper_chwidth = config.vht_oper_chwidth;
|
||||
config.he_oper_centr_freq_seg0_idx = config.vht_oper_centr_freq_seg0_idx;
|
||||
}
|
||||
|
||||
if (config.he_bss_color_enabled) {
|
||||
if (config.he_spr_non_srg_obss_pd_max_offset)
|
||||
config.he_spr_sr_control |= 1 << 2;
|
||||
if (!config.he_spr_psr_enabled)
|
||||
config.he_spr_sr_control |= 1;
|
||||
append_vars(config, [ 'he_bss_color', 'he_spr_non_srg_obss_pd_max_offset', 'he_spr_sr_control' ]);
|
||||
}
|
||||
|
||||
if (!(he_phy_cap[3] & 0x80))
|
||||
config.he_su_beamformer = false;
|
||||
if (!(he_phy_cap[4] & 0x1))
|
||||
config.he_su_beamformee = false;
|
||||
if (!(he_phy_cap[4] & 0x2))
|
||||
config.he_mu_beamformer = false;
|
||||
if (!(he_phy_cap[7] & 0x1))
|
||||
config.he_spr_psr_enabled = false;
|
||||
if (!(he_mac_cap[0] & 0x1))
|
||||
config.he_twt_required= false;
|
||||
|
||||
append_vars(config, [
|
||||
'ieee80211ax', 'he_oper_chwidth', 'he_oper_centr_freq_seg0_idx',
|
||||
'he_su_beamformer', 'he_su_beamformee', 'he_mu_beamformer', 'he_twt_required',
|
||||
'he_default_pe_duration', 'he_rts_threshold', 'he_mu_edca_qos_info_param_count',
|
||||
'he_mu_edca_qos_info_q_ack', 'he_mu_edca_qos_info_queue_request', 'he_mu_edca_qos_info_txop_request',
|
||||
'he_mu_edca_ac_be_aifsn', 'he_mu_edca_ac_be_aci', 'he_mu_edca_ac_be_ecwmin',
|
||||
'he_mu_edca_ac_be_ecwmax', 'he_mu_edca_ac_be_timer', 'he_mu_edca_ac_bk_aifsn',
|
||||
'he_mu_edca_ac_bk_aci', 'he_mu_edca_ac_bk_ecwmin', 'he_mu_edca_ac_bk_ecwmax',
|
||||
'he_mu_edca_ac_bk_timer', 'he_mu_edca_ac_vi_ecwmin', 'he_mu_edca_ac_vi_ecwmax',
|
||||
'he_mu_edca_ac_vi_aifsn', 'he_mu_edca_ac_vi_aci', 'he_mu_edca_ac_vi_timer',
|
||||
'he_mu_edca_ac_vo_aifsn', 'he_mu_edca_ac_vo_aci', 'he_mu_edca_ac_vo_ecwmin',
|
||||
'he_mu_edca_ac_vo_ecwmax', 'he_mu_edca_ac_vo_timer',
|
||||
]);
|
||||
}
|
||||
|
||||
if (wildcard(config.htmode, 'EHT*')) {
|
||||
config.ieee80211be = true;
|
||||
append_vars(config, [ 'ieee80211be' ]);
|
||||
|
||||
if (config.hw_mode == 'a')
|
||||
append_vars(config, [ 'eht_oper_chwidth', 'eht_oper_centr_freq_seg0_idx' ]);
|
||||
|
||||
if (config.band == "6g") {
|
||||
config.stationary_ap = true;
|
||||
append_vars(config, [ 'he_6ghz_reg_pwr_type', ]);
|
||||
}
|
||||
}
|
||||
|
||||
append_vars(config, [ 'tx_queue_data2_burst', 'stationary_ap' ]);
|
||||
}
|
||||
|
||||
function device_extended_features(data, flag) {
|
||||
return !!(data[flag / 8] | (1 << (flag % 8)));
|
||||
}
|
||||
|
||||
function device_capabilities(phy) {
|
||||
let idx = +substr(phy, 3, 1);;
|
||||
let phys = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, nl80211.const.NLM_F_DUMP, { wiphy: idx, split_wiphy_dump: true });
|
||||
|
||||
for (let phy in phys) {
|
||||
if (!phy || phy.wiphy != idx)
|
||||
continue;
|
||||
for (let band in phy.wiphy_bands) {
|
||||
if (!band)
|
||||
continue;
|
||||
phy_capabilities.ht_capa = band.ht_capa ?? 0;
|
||||
phy_capabilities.vht_capa = band.vht_capa ?? 0;
|
||||
for (let iftype in band.iftype_data) {
|
||||
if (!iftype.iftypes.ap)
|
||||
continue;
|
||||
phy_capabilities.he_mac_cap = iftype.he_cap_mac;
|
||||
phy_capabilities.he_phy_cap = iftype.he_cap_phy;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
phy_features.ftm_responder = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER);
|
||||
phy_features.radar_background = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_RADAR_BACKGROUND);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function generate(config) {
|
||||
if (!config.phy)
|
||||
die(`${config.path} is an unknown phy`);
|
||||
|
||||
device_capabilities(config.phy);
|
||||
|
||||
append('driver', 'nl80211');
|
||||
|
||||
set_device_defaults(config);
|
||||
|
||||
device_log_append(config);
|
||||
|
||||
device_country_code(config);
|
||||
|
||||
device_cell_density_append(config);
|
||||
|
||||
device_rates(config);
|
||||
|
||||
/* beacon */
|
||||
append_vars(config, [ 'beacon_int', 'beacon_rate', 'rnr_beacon', 'mbssid' ]);
|
||||
|
||||
/* wpa_supplicant co-exist */
|
||||
append_vars(config, [ 'noscan' ]);
|
||||
|
||||
/* airtime */
|
||||
append_vars(config, [ 'airtime_mode' ]);
|
||||
|
||||
/* assoc/thresholds */
|
||||
append_vars(config, [ 'rssi_reject_assoc_rssi', 'rssi_ignore_probe_request', 'iface_max_num_sta', 'no_probe_resp_if_max_sta' ]);
|
||||
|
||||
/* ACS / Radar*/
|
||||
if (!phy_features.radar_background || config.band != '5g')
|
||||
delete config.enable_background_radar;
|
||||
else
|
||||
set_default(config, 'enable_background_radar', phy_features.radar_background);
|
||||
|
||||
append_vars(config, [ 'acs_chan_bias', 'acs_exclude_dfs', 'enable_background_radar' ]);
|
||||
|
||||
/* TX Power */
|
||||
append_vars(config, [ 'min_tx_power' ]);
|
||||
|
||||
/* hwmode, channel, op_class, ... */
|
||||
append_vars(config, [ 'hw_mode', 'channel', 'rts_threshold', 'chanlist' ]);
|
||||
if (config.hw_mode in [ 'a', 'g' ] && config.require_mode in [ 'n', 'ac', 'ax' ]) {
|
||||
let require_mode = { n: 'require_ht', ac: 'require_vht', ax: 'require_he' };
|
||||
|
||||
config.legacy_rates = false;
|
||||
append(require_mode[config.require_mode], 1);
|
||||
}
|
||||
device_htmode_append(config);
|
||||
|
||||
/* 6G power mode */
|
||||
if (config.band != '6g')
|
||||
append_vars(config, [ 'reg_power_type' ]);
|
||||
|
||||
/* raw options */
|
||||
for (let raw in config.hostapd_options)
|
||||
append_raw(raw);
|
||||
}
|
||||
|
||||
let iface_idx = 0;
|
||||
function setup_interface(interface, config, vlans, stas, phy_features, fixup) {
|
||||
config = { ...config, fixup };
|
||||
|
||||
config.idx = iface_idx++;
|
||||
ap.generate(interface, config, vlans, stas, phy_features);
|
||||
}
|
||||
|
||||
export function setup(data) {
|
||||
let file_name = `/var/run/hostapd-${data.phy}${data.vif_phy_suffix}.conf`;
|
||||
|
||||
flush_config();
|
||||
|
||||
if (fs.stat(file_name))
|
||||
fs.rename(file_name, file_name + '.prev');
|
||||
|
||||
data.config.phy = data.phy;
|
||||
|
||||
generate(data.config);
|
||||
|
||||
if (data.config.num_global_macaddr)
|
||||
append('\n#num_global_macaddr', data.config.num_global_macaddr);
|
||||
|
||||
for (let k, interface in data.interfaces) {
|
||||
if (interface.config.mode != 'ap')
|
||||
continue;
|
||||
|
||||
interface.config.network_bridge = interface.bridge;
|
||||
interface.config.network_ifname = interface['bridge-ifname'];
|
||||
|
||||
let owe = interface.config.encryption == 'owe' && interface.config.owe_transition;
|
||||
|
||||
setup_interface(k, interface.config, interface.vlans, interface.stas, phy_features, owe ? 'owe' : null );
|
||||
if (owe)
|
||||
setup_interface(k, interface.config, interface.vlans, interface.stas, phy_features, 'owe-transition');
|
||||
}
|
||||
|
||||
let config = dump_config(file_name);
|
||||
|
||||
let msg = {
|
||||
phy: data.phy,
|
||||
radio: data.config.radio,
|
||||
config: file_name,
|
||||
prev_config: file_name + '.prev'
|
||||
};
|
||||
let ret = global.ubus.call('hostapd', 'config_set', msg);
|
||||
|
||||
if (ret)
|
||||
netifd.add_process('/usr/sbin/hostapd', ret.pid, true, true);
|
||||
else
|
||||
netifd.setup_failed('HOSTAPD_START_FAILED');
|
||||
};
|
@ -0,0 +1,194 @@
|
||||
'use strict';
|
||||
|
||||
import { append_value, log } from 'wifi.common';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export function parse_encryption(config) {
|
||||
let encryption = split(config.encryption, '+', 2);
|
||||
|
||||
config.wpa_pairwise = (config.hwmode == 'ad') ? 'GCMP' : 'CCMP';
|
||||
|
||||
switch(encryption[1]){
|
||||
case 'tkip+aes':
|
||||
case 'tkip+ccmp':
|
||||
case 'aes+tkip':
|
||||
case 'ccmp+tkip':
|
||||
config.wpa_pairwise = 'CCMP TKIP';
|
||||
break;
|
||||
|
||||
case 'ccmp256':
|
||||
config.wpa_pairwise = 'CCMP-256';
|
||||
break;
|
||||
|
||||
case 'aes':
|
||||
case 'ccmp':
|
||||
config.wpa_pairwise = 'CCMP';
|
||||
break;
|
||||
|
||||
case 'tkip':
|
||||
config.wpa_pairwise = 'TKIP';
|
||||
break;
|
||||
|
||||
case 'gcmp256':
|
||||
config.wpa_pairwise = 'GCMP-256';
|
||||
break;
|
||||
|
||||
case 'gcmp':
|
||||
config.wpa_pairwise = 'GCMP';
|
||||
break;
|
||||
|
||||
default:
|
||||
if (config.encryption == 'wpa3-192')
|
||||
config.wpa_pairwise = 'GCMP-256';
|
||||
break;
|
||||
}
|
||||
|
||||
config.wpa = 0;
|
||||
for (let k, v in { 'wpa2*': 2, 'wpa3*': 2, '*psk2*': 2, 'psk3*': 2, 'sae*': 2,
|
||||
'owe*': 2, 'wpa*mixed*': 3, '*psk*mixed*': 3, 'wpa*': 1, '*psk*': 1, })
|
||||
if (wildcard(config.encryption, k)) {
|
||||
config.wpa = v;
|
||||
break;
|
||||
}
|
||||
if (!config.wpa)
|
||||
config.wpa_pairwise = null;
|
||||
|
||||
config.auth_type = encryption[0] ?? 'none';
|
||||
switch(config.auth_type) {
|
||||
case 'owe':
|
||||
config.auth_type = 'owe';
|
||||
break;
|
||||
|
||||
case 'wpa3-192':
|
||||
config.auth_type = 'eap192';
|
||||
break;
|
||||
|
||||
case 'wpa3-mixed':
|
||||
config.auth_type = 'eap-eap2';
|
||||
break;
|
||||
|
||||
case 'wpa3':
|
||||
config.auth_type = 'eap2';
|
||||
break;
|
||||
|
||||
case 'sae-mixed':
|
||||
config.auth_type = 'psk-sae';
|
||||
break;
|
||||
|
||||
case 'wpa':
|
||||
case 'wpa2':
|
||||
config.auth_type = 'eap';
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export function wpa_key_mgmt(config) {
|
||||
if (!config.wpa)
|
||||
return;
|
||||
|
||||
switch(config.auth_type) {
|
||||
case 'psk':
|
||||
case 'psk2':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-PSK');
|
||||
if (config.wpa >= 2 && config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-PSK');
|
||||
if (config.ieee80211w)
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256');
|
||||
break;
|
||||
|
||||
case 'eap':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP');
|
||||
if (config.wpa >= 2 && config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
|
||||
if (config.ieee80211w)
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP--SHA256');
|
||||
break;
|
||||
|
||||
case 'eap192':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SUITE-B-192');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-EAP-SHA384');
|
||||
break;
|
||||
|
||||
case 'eap-eap2':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP');
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SHA256');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
|
||||
break;
|
||||
|
||||
case 'eap2':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SHA256');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
|
||||
break;
|
||||
|
||||
case 'sae':
|
||||
append_value(config, 'wpa_key_mgmt', 'SAE');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-SAE');
|
||||
break;
|
||||
|
||||
case 'psk-sae':
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-PSK');
|
||||
append_value(config, 'wpa_key_mgmt', 'SAE');
|
||||
if (config.ieee80211w)
|
||||
append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256');
|
||||
if (config.ieee80211r) {
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-PSK');
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-SAE');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'owe':
|
||||
append_value(config, 'wpa_key_mgmt', 'OWE');
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.fils) {
|
||||
switch(config.auth_type) {
|
||||
case 'eap192':
|
||||
append_value(config, 'wpa_key_mgmt', 'FILS-SHA384');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-FILS-SHA384');
|
||||
break;
|
||||
|
||||
case 'eap-eap2':
|
||||
case 'eap2':
|
||||
case 'eap':
|
||||
append_value(config, 'wpa_key_mgmt', 'FILS-SHA256');
|
||||
if (config.ieee80211r)
|
||||
append_value(config, 'wpa_key_mgmt', 'FT-FILS-SHA256');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
config.key_mgmt = config.wpa_key_mgmt;
|
||||
};
|
||||
|
||||
function macaddr_random() {
|
||||
let f = open("/dev/urandom", "r");
|
||||
let addr = f.read(6);
|
||||
|
||||
addr = map(split(addr, ""), (v) => ord(v));
|
||||
addr[0] &= ~1;
|
||||
addr[0] |= 2;
|
||||
|
||||
return join(":", map(addr, (v) => sprintf("%02x", v)));
|
||||
}
|
||||
|
||||
let mac_idx = 0;
|
||||
export function prepare(data, phy, num_global_macaddr) {
|
||||
if (!data.macaddr) {
|
||||
let pipe = fs.popen(`ucode /usr/share/hostap/wdev.uc ${phy} get_macaddr id=${mac_idx} num_global=${num_global_macaddr} mbssid=${data.mbssid ?? 0}`);
|
||||
|
||||
data.macaddr = trim(pipe.read("all"), '\n');
|
||||
pipe.close();
|
||||
|
||||
data.default_macaddr = true;
|
||||
mac_idx++;
|
||||
} else if (data.macaddr == 'random')
|
||||
data.macaddr = macaddr_random();
|
||||
|
||||
log(`Preparing interface: ${data.ifname} with MAC: ${data.macaddr}`);
|
||||
};
|
@ -0,0 +1,49 @@
|
||||
'use strict';
|
||||
|
||||
import { log } from 'wifi.common';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const CMD_UP = 0;
|
||||
const CMD_SET_DATA = 1;
|
||||
const CMD_PROCESS_ADD = 2;
|
||||
const CMD_PROCESS_KILL_ALL = 3;
|
||||
const CMD_SET_RETRY = 4;
|
||||
|
||||
export function notify(command, params, data) {
|
||||
params ??= {};
|
||||
data ??= {};
|
||||
|
||||
global.ubus.call('network.wireless', 'notify', { command, device: global.radio, ...params, data });
|
||||
};
|
||||
|
||||
export function set_up() {
|
||||
notify(CMD_UP);
|
||||
};
|
||||
|
||||
export function set_data(data) {
|
||||
notify(CMD_SET_DATA, null, data);
|
||||
};
|
||||
|
||||
export function add_process(exe, pid, required, keep) {
|
||||
exe = fs.realpath(exe);
|
||||
|
||||
notify(CMD_PROCESS_ADD, null, { pid, exe, required, keep });
|
||||
};
|
||||
|
||||
export function set_retry(retry) {
|
||||
notify(CMD_SET_RETRY, null, { retry });
|
||||
};
|
||||
|
||||
export function set_vif(interface, ifname) {
|
||||
notify(CMD_SET_DATA, { interface }, { ifname });
|
||||
};
|
||||
|
||||
export function set_vlan(interface, ifname, vlan) {
|
||||
notify(CMD_SET_DATA, { interface, vlan }, { ifname });
|
||||
};
|
||||
|
||||
export function setup_failed(reason) {
|
||||
log(`Device setup failed: ${reason}`);
|
||||
printf('%s\n', reason);
|
||||
set_retry(false);
|
||||
};
|
@ -0,0 +1,237 @@
|
||||
'use strict';
|
||||
|
||||
import { append, append_raw, append_vars, network_append, network_append_raw, network_append_vars,
|
||||
set_default, dump_network, flush_network } from 'wifi.common';
|
||||
import * as netifd from 'wifi.netifd';
|
||||
import * as iface from 'wifi.iface';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function set_fixed_freq(data, config) {
|
||||
if (!data.frequency)
|
||||
return;
|
||||
|
||||
set_default(config, 'fixed_freq', 1);
|
||||
set_default(config, 'frequency', data.frequency);
|
||||
|
||||
if (data.htmode in [ 'VHT80', 'HE80' ])
|
||||
set_default(config, 'max_oper_chwidth', 1);
|
||||
else if (data.htmode in [ 'VHT160', 'HE160' ])
|
||||
set_default(config, 'max_oper_chwidth', 2);
|
||||
else if (data.htmode in [ 'VHT20', 'VHT40', 'HE20', 'HE40' ])
|
||||
set_default(config, 'max_oper_chwidth', 0);
|
||||
else
|
||||
set_default(config, 'disable_vht', true);
|
||||
|
||||
if (data.htmode in [ 'NOHT' ])
|
||||
set_default(config, 'disable_ht', true);
|
||||
else if (data.htmode in [ 'HT20', 'VHT20', 'HE20' ])
|
||||
set_default(config, 'disable_ht40', true);
|
||||
else if (data.htmode in [ 'VHT40', 'VHT80', 'VHT160', 'HE40', 'HE80', 'HE160' ])
|
||||
set_default(config, 'ht40', true);
|
||||
|
||||
if (wildcard(data.htmode, 'VHT*'))
|
||||
set_default(config, 'vht', 1);
|
||||
}
|
||||
|
||||
export function ratestr(rate) {
|
||||
if (rate == null)
|
||||
return rate;
|
||||
|
||||
let rem = (rate / 100) % 10;
|
||||
rate = int(rate / 1000);
|
||||
if (rem > 0)
|
||||
rate += "." + rem;
|
||||
|
||||
return "" + rate;
|
||||
};
|
||||
|
||||
export function ratelist(rates) {
|
||||
if (length(rates) < 1)
|
||||
return null;
|
||||
|
||||
return join(",", map(rates, (rate) => ratestr(rate)));
|
||||
};
|
||||
|
||||
function setup_sta(data, config) {
|
||||
iface.parse_encryption(config);
|
||||
|
||||
if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ])
|
||||
set_default(config, 'ieee80211w', 2);
|
||||
else if (config.auth_type in [ 'psk-sae' ])
|
||||
set_default(config, 'ieee80211w', 1);
|
||||
|
||||
set_default(config, 'ieee80211r', 0);
|
||||
set_default(config, 'multi_ap', 0);
|
||||
set_default(config, 'default_disabled', 0);
|
||||
|
||||
//multiap_flag_file="${_config}.is_multiap"
|
||||
config.scan_ssid = 1;
|
||||
|
||||
switch(config.mode) {
|
||||
case 'sta':
|
||||
set_default(config, 'multi_ap_backhaul_sta', config.multi_ap);
|
||||
break;
|
||||
|
||||
case 'adhoc':
|
||||
config.ap_scan = 2;
|
||||
config.scan_ssid = 0;
|
||||
network_append('mode', 1);
|
||||
set_fixed_freq(data, config);
|
||||
break;
|
||||
|
||||
case 'mesh':
|
||||
config.ssid = config.mesh_id;
|
||||
config.scan_ssid = null;
|
||||
network_append('mode', 5);
|
||||
|
||||
set_fixed_freq(data, config);
|
||||
|
||||
if (config.encryption && config.encryption != 'none')
|
||||
config.key_mgmt = 'SAE';
|
||||
|
||||
config.ieee80211w = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if (config.mode != 'mesh' ) {
|
||||
switch(config.wpa) {
|
||||
case 1:
|
||||
config.proto = 'WPA';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
config.proto = 'RSN';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch(config.auth_type) {
|
||||
case 'none':
|
||||
break;
|
||||
|
||||
case 'owe':
|
||||
iface.wpa_key_mgmt(config);
|
||||
break;
|
||||
|
||||
case 'psk':
|
||||
case 'psk2':
|
||||
case 'sae':
|
||||
case 'psk-sae':
|
||||
if (config.mode != 'mesh')
|
||||
iface.wpa_key_mgmt(config);
|
||||
|
||||
if (config.mode == 'mesh' || config.auth_type == 'sae')
|
||||
config.sae_password = `"${config.key}"`;
|
||||
else
|
||||
config.psk = `"${config.key}"`;
|
||||
|
||||
break;
|
||||
|
||||
case 'eap':
|
||||
case 'eap2':
|
||||
case 'eap192':
|
||||
iface.wpa_key_mgmt(config);
|
||||
set_default(config, 'erp', config.fils);
|
||||
|
||||
if (config.ca_cert_usesystem && fs.stat('/etc/ssl/certs/ca-certificates.crt'))
|
||||
config.ca_cert = '/etc/ssl/certs/ca-certificates.crt';
|
||||
|
||||
switch(config.eap_type) {
|
||||
case 'fast':
|
||||
case 'peap':
|
||||
case 'ttls':
|
||||
set_default(config, 'auth', 'MSCHAPV2');
|
||||
if (config.auth == 'EAP-TLS') {
|
||||
if (config.ca_cert2_usesystem && fs.stat('/etc/ssl/certs/ca-certificates.crt'))
|
||||
config.ca_cert2 = '/etc/ssl/certs/ca-certificates.crt';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (config.wpa_pairwise == 'GCMP') {
|
||||
config.pairwise = 'GCMP';
|
||||
config.group = 'GCMP';
|
||||
}
|
||||
|
||||
config.basic_rate = ratelist(config.basic_rate);
|
||||
config.mcast_rate = ratestr(config.mcast_rate);
|
||||
config.ssid = `"${config.ssid}"`;
|
||||
|
||||
network_append_vars(config, [
|
||||
'scan_ssid', 'noscan', 'disabled', 'multi_ap_backhaul_sta',
|
||||
'ocv', 'key_mgmt', 'psk', 'sae_password', 'pairwise', 'group', 'bssid',
|
||||
'proto', 'mesh_fwding', 'mesh_rssi_threshold', 'frequency', 'fixed_freq',
|
||||
'disable_ht', 'disable_ht40', 'disable_vht', 'vht', 'max_oper_chwidth',
|
||||
'ht40', 'ssid', 'beacon_int', 'ieee80211w', 'basic_rate', 'mcast_rate',
|
||||
'bssid_blacklist', 'bssid_whitelist', 'erp', 'ca_cert', 'identity',
|
||||
'anonymous_identity', 'client_cert', 'private_key', 'private_key_passwd',
|
||||
'subject_match', 'altsubject_match', 'domain_match', 'domain_suffix_match',
|
||||
'ca_cert2', 'client_cert2', 'private_key2', 'private_key2_passwd', 'password'
|
||||
]);
|
||||
}
|
||||
|
||||
export function generate(config_list, data, interface) {
|
||||
flush_network();
|
||||
|
||||
if (interface.bridge &&
|
||||
(interface.config.mode == 'adhoc' ||
|
||||
(interface.config.mode == 'sta' && !interface.config.wds && !interface.config.multi_ap))){
|
||||
netifd.setup_failed('BRIDGE_NOT_ALLOWED');
|
||||
return 1;
|
||||
}
|
||||
|
||||
interface.config.country = data.config.country;
|
||||
interface.config.beacon_int = data.config.beacon_int;
|
||||
|
||||
setup_sta(data.config, interface.config);
|
||||
|
||||
let file_name = `/var/run/wpa-supplicant-${interface.config.ifname}.conf`;
|
||||
if (fs.stat(file_name))
|
||||
fs.rename(file_name, file_name + '.prev');
|
||||
dump_network(file_name);
|
||||
|
||||
let config = {
|
||||
mode: interface.config.mode,
|
||||
ctrl: '/var/run/wpa_supplicant',
|
||||
iface: interface.config.ifname,
|
||||
config: file_name,
|
||||
'4addr': !!interface.config.wds,
|
||||
powersave: false
|
||||
};
|
||||
|
||||
if (!interface.config.default_macaddr)
|
||||
config.macaddr = interface.config.macaddr;
|
||||
|
||||
if (interface.config.wds)
|
||||
config.bridge = interface.bridge;
|
||||
|
||||
push(config_list, config);
|
||||
|
||||
return config;
|
||||
};
|
||||
|
||||
export function setup(config, data) {
|
||||
let ret = global.ubus.call('wpa_supplicant', 'config_set', {
|
||||
phy: data.phy,
|
||||
radio: data.config.radio,
|
||||
config,
|
||||
defer: true,
|
||||
num_global_macaddr: data.config.num_global_macaddr,
|
||||
});
|
||||
|
||||
if (ret)
|
||||
netifd.add_process('/usr/sbin/wpa_supplicant', ret.pid, true, true);
|
||||
else
|
||||
netifd.setup_failed('SUPPLICANT_START_FAILED');
|
||||
};
|
||||
|
||||
|
||||
export function start(data) {
|
||||
global.ubus.call('wpa_supplicant', 'config_set', {
|
||||
phy: data.phy,
|
||||
radio: data.config.radio,
|
||||
num_global_macaddr: data.config.num_global_macaddr,
|
||||
});
|
||||
};
|
@ -0,0 +1,121 @@
|
||||
'use strict';
|
||||
|
||||
import { log } from 'wifi.common';
|
||||
import * as fs from 'fs';
|
||||
|
||||
const schemas = {
|
||||
device: json(fs.readfile('/usr/share/schema/wireless.wifi-device.json')).properties,
|
||||
iface: json(fs.readfile('/usr/share/schema/wireless.wifi-iface.json')).properties,
|
||||
vlan: json(fs.readfile('/usr/share/schema/wireless.wifi-vlan.json')).properties,
|
||||
station: json(fs.readfile('/usr/share/schema/wireless.wifi-station.json')).properties,
|
||||
};
|
||||
|
||||
const types = {
|
||||
"array": 1,
|
||||
"string": 3,
|
||||
"number": 5,
|
||||
"boolean": 7,
|
||||
};
|
||||
|
||||
function dump_option(schema, key) {
|
||||
let _key = (schema[key].type == 'alias') ? schema[key].default : key;
|
||||
|
||||
return [
|
||||
key,
|
||||
types[schema[_key].type]
|
||||
];
|
||||
}
|
||||
|
||||
export function dump_options() {
|
||||
let dump = {
|
||||
"name": "mac80211",
|
||||
};
|
||||
|
||||
for (let k, v in schemas) {
|
||||
dump[k] = [];
|
||||
for (let option in v)
|
||||
push(dump[k], dump_option(v, option));
|
||||
};
|
||||
|
||||
printf('%J\n', dump);
|
||||
|
||||
return 0;
|
||||
};
|
||||
|
||||
function abort(msg) {
|
||||
log(msg);
|
||||
die();
|
||||
}
|
||||
|
||||
function validate_value(schema, key, value) {
|
||||
switch(schema.type) {
|
||||
case 'number':
|
||||
value = +value;
|
||||
if (schema.minimum && value < schema.minimum)
|
||||
abort(`${key}: ${value} is lower than the minimum value`);
|
||||
if (schema.maximum && value > schema.maximum)
|
||||
abort(`${key}: ${value} is larger than the maximum value`);
|
||||
if (schema.enum && !(value in schema.enum))
|
||||
abort(`${key}: ${value} has to be one of ${schema.enum}`);
|
||||
break;
|
||||
|
||||
case 'boolean':
|
||||
value = !!+value;
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
if (schema.enum && !(value in schema.enum))
|
||||
abort(`${key}: ${value} has to be one of ${schema.enum}`);
|
||||
break;
|
||||
|
||||
case 'array':
|
||||
if (type(value) != 'array')
|
||||
value = [ value ];
|
||||
if (schema.items?.type)
|
||||
for (let k, v in value)
|
||||
value[k] = validate_value(schema.items, key, v);
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export function validate(schema, dict) {
|
||||
schema = schemas[schema];
|
||||
|
||||
/* complain about anything that is not in the schema */
|
||||
for (let k, v in dict) {
|
||||
if (substr(k, 0, 1) == '.')
|
||||
continue;
|
||||
if (schema[k])
|
||||
continue;
|
||||
log(`${k} is not present in the schema`);
|
||||
}
|
||||
|
||||
/* convert all aliases */
|
||||
for (let k, v in dict) {
|
||||
if (schema[k]?.type != 'alias')
|
||||
continue;
|
||||
if (schema[k].default == null)
|
||||
abort(`${k} alias does not have a default value`);
|
||||
|
||||
dict[schema[k].default] = v;
|
||||
delete dict[k];
|
||||
}
|
||||
|
||||
/* set defaults */
|
||||
for (let k, v in schema) {
|
||||
if (schema[k]?.type == 'alias')
|
||||
continue;
|
||||
if (dict[k] != null || schema[k].default == null)
|
||||
continue;
|
||||
dict[k] = schema[k].default;
|
||||
}
|
||||
|
||||
/* validate value constraints */
|
||||
for (let k, v in dict) {
|
||||
if (!schema[k])
|
||||
continue;
|
||||
dict[k] = validate_value(schema[k], k, v);
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user