Initial commit
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled
Some checks failed
Build Kernel / Build all affected Kernels (push) Has been cancelled
Build all core packages / Build all core packages for selected target (push) Has been cancelled
Build and Push prebuilt tools container / Build and Push all prebuilt containers (push) Has been cancelled
Build Toolchains / Build Toolchains for each target (push) Has been cancelled
Build host tools / Build host tools for linux and macos based systems (push) Has been cancelled
Coverity scan build / Coverity x86/64 build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
'use strict';
|
||||
|
||||
import * as nl80211 from 'nl80211';
|
||||
import * as libubus from 'ubus';
|
||||
import { readfile, stat } from "fs";
|
||||
|
||||
let wifi_devices = json(readfile('/usr/share/wifi_devices.json'));
|
||||
let countries = json(readfile('/usr/share/iso3166.json'));
|
||||
let board_data = json(readfile('/etc/board.json'));
|
||||
|
||||
export let phys = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, nl80211.const.NLM_F_DUMP, { split_wiphy_dump: true });
|
||||
let interfaces = nl80211.request(nl80211.const.NL80211_CMD_GET_INTERFACE, nl80211.const.NLM_F_DUMP);
|
||||
|
||||
let ubus = libubus.connect();
|
||||
let wireless_status = ubus.call('network.wireless', 'status');
|
||||
|
||||
function find_phy(wiphy) {
|
||||
for (let k, phy in phys)
|
||||
if (phy.wiphy == wiphy)
|
||||
return phy;
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_noise(iface) {
|
||||
for (let phy in phys) {
|
||||
let channels = nl80211.request(nl80211.const.NL80211_CMD_GET_SURVEY, nl80211.const.NLM_F_DUMP, { dev: iface.ifname });
|
||||
for (let k, channel in channels)
|
||||
if (channel.survey_info.frequency == iface.wiphy_freq)
|
||||
return channel.survey_info.noise;
|
||||
}
|
||||
|
||||
return -100;
|
||||
}
|
||||
|
||||
function get_country(iface) {
|
||||
let reg = nl80211.request(nl80211.const.NL80211_CMD_GET_REG, 0, { dev: iface.ifname });
|
||||
|
||||
return reg.reg_alpha2 ?? '';
|
||||
}
|
||||
|
||||
function get_max_power(iface) {
|
||||
let phy = find_phy(iface.wiphy);
|
||||
|
||||
for (let k, band in phy.wiphy_bands)
|
||||
if (band)
|
||||
for (let freq in band.freqs)
|
||||
if (freq.freq == iface.wiphy_freq)
|
||||
return freq.max_tx_power;;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function get_hardware_id(iface) {
|
||||
let hw = {
|
||||
type: 'nl80211',
|
||||
id: 'Generic MAC80211',
|
||||
power_offset: 0,
|
||||
channel_offset: 0,
|
||||
};
|
||||
|
||||
let path = `/sys/class/ieee80211/phy${iface.wiphy}/device/`;
|
||||
if (stat(path + 'vendor')) {
|
||||
let data = [];
|
||||
for (let lookup in [ 'vendor', 'device', 'subsystem_vendor', 'subsystem_device' ])
|
||||
push(data, trim(readfile(path + lookup), '\n'));
|
||||
|
||||
for (let device in wifi_devices.pci) {
|
||||
let match = 0;
|
||||
for (let i = 0; i < 4; i++)
|
||||
if (lc(data[i]) == lc(device[i]))
|
||||
match++;
|
||||
if (match == 4) {
|
||||
hw.type = `${data[0]}:${data[1]} ${data[2]}:${data[3]}`;
|
||||
hw.power_offset = device[4];
|
||||
hw.channel_offset = device[5];
|
||||
hw.id = `${device[6]} ${device[7]}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let compatible = trim(readfile(`/sys/class/net/${iface.ifname}/device/of_node/compatible`), '\n');
|
||||
if (compatible && wifi_devices.compatible[compatible]) {
|
||||
hw.id = wifi_devices.compatible[compatible][0] + ' ' + wifi_devices.compatible[compatible][1];
|
||||
hw.compatible = compatible;
|
||||
hw.type = 'embedded';
|
||||
}
|
||||
|
||||
return hw;
|
||||
}
|
||||
|
||||
const iftypes = [
|
||||
'Unknown', 'Ad-Hoc', 'Client', 'Master', 'Master (VLAN)',
|
||||
'WDS', 'Monitor', 'Mesh Point', 'P2P Client', 'P2P Go',
|
||||
];
|
||||
|
||||
export let ifaces = {};
|
||||
for (let k, v in interfaces) {
|
||||
let iface = ifaces[v.ifname] = v;
|
||||
|
||||
iface.mode = iftypes[iface.iftype] ?? 'unknonw',
|
||||
iface.noise = get_noise(iface);
|
||||
iface.country = get_country(iface);
|
||||
iface.max_power = get_max_power(iface);
|
||||
iface.assoclist = nl80211.request(nl80211.const.NL80211_CMD_GET_STATION, nl80211.const.NLM_F_DUMP, { dev: v.ifname }) ?? [];
|
||||
iface.hardware = get_hardware_id(iface);
|
||||
|
||||
iface.bss_info = ubus.call('hostapd', 'bss_info', { iface: v.ifname });
|
||||
if (!iface.bss_info)
|
||||
iface.bss_info = ubus.call('wpa_supplicant', 'bss_info', { iface: v.ifname });
|
||||
}
|
||||
|
||||
for (let radio, data in wireless_status)
|
||||
for (let k, v in data.interfaces) {
|
||||
if (!v.ifname || !ifaces[v.ifname])
|
||||
continue;
|
||||
|
||||
ifaces[v.ifname].ssid = v.config.ssid || v.config.mesh_id;
|
||||
ifaces[v.ifname].radio = data.config;
|
||||
|
||||
let bss_info = ifaces[v.ifname].bss_info;
|
||||
let owe_transition_ifname = bss_info?.owe_transition_ifname;
|
||||
|
||||
if (v.config.owe_transition && ifaces[owe_transition_ifname]) {
|
||||
ifaces[v.ifname].owe_transition_ifname = owe_transition_ifname;
|
||||
ifaces[owe_transition_ifname].ssid = v.config.ssid;
|
||||
ifaces[owe_transition_ifname].radio = data.config;
|
||||
ifaces[owe_transition_ifname].owe_transition_ifname = v.ifname
|
||||
}
|
||||
}
|
||||
|
||||
function format_channel(freq) {
|
||||
if (freq < 1000)
|
||||
return 0;
|
||||
if (freq == 2484)
|
||||
return 14;
|
||||
if (freq == 5935)
|
||||
return 2;
|
||||
if (freq < 2484)
|
||||
return (freq - 2407) / 5;
|
||||
if (freq >= 4910 && freq <= 4980)
|
||||
return (freq - 4000) / 5;
|
||||
if (freq < 5950)
|
||||
return (freq - 5000) / 5;
|
||||
if (freq <= 45000)
|
||||
return (freq - 5950) / 5;
|
||||
if (freq >= 58320 && freq <= 70200)
|
||||
return (freq - 56160) / 2160;
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
function format_band(freq) {
|
||||
if (freq == 5935)
|
||||
return '6';
|
||||
if (freq < 2484)
|
||||
return '2.4';
|
||||
if (freq < 5950)
|
||||
return '5';
|
||||
if (freq <= 45000)
|
||||
return '6';
|
||||
|
||||
return '60';
|
||||
}
|
||||
|
||||
function format_frequency(freq) {
|
||||
return freq ? sprintf('%.03f', freq / 1000.0) : 'unknown';
|
||||
}
|
||||
|
||||
function format_rate(rate) {
|
||||
return rate ? sprintf('%.01f', rate / 10.0) : 'unknown';
|
||||
}
|
||||
|
||||
function format_mgmt_key(key) {
|
||||
switch(+key) {
|
||||
case 1:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
case 16:
|
||||
case 17:
|
||||
return '802.1x';
|
||||
|
||||
case 2:
|
||||
return 'WPA PSK';
|
||||
|
||||
case 4:
|
||||
return 'FT PSK';
|
||||
|
||||
case 6:
|
||||
return 'WPA PSK2';
|
||||
|
||||
case 8:
|
||||
return 'SAE';
|
||||
|
||||
case 18:
|
||||
return 'OWE';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function assoc_flags(data) {
|
||||
const assoc_mhz = {
|
||||
width_40: 40,
|
||||
width_80: 80,
|
||||
width_80p80: '80+80',
|
||||
width_160: 160,
|
||||
width_320: 320,
|
||||
width_10: 10,
|
||||
width_5: 5
|
||||
};
|
||||
|
||||
let mhz = 'unknown';
|
||||
for (let k, v in assoc_mhz)
|
||||
if (data[k])
|
||||
mhz = v;
|
||||
|
||||
const assoc_flags = {
|
||||
mcs: {
|
||||
mcs: 'MCS',
|
||||
},
|
||||
vht_mcs: {
|
||||
vht_mcs: 'VHT-MCS',
|
||||
vht_nss: 'VHT-NSS',
|
||||
},
|
||||
he_mcs: {
|
||||
he_mcs: 'HE-MCS',
|
||||
he_nss: 'HE-NSS',
|
||||
he_gi: 'HE-GI',
|
||||
he_dcm: 'HE-DCM',
|
||||
},
|
||||
eht_mcs: {
|
||||
eht_mcs: 'EHT-MCS',
|
||||
eht_nss: 'EHT-NSS',
|
||||
eht_gi: 'EHT-GI',
|
||||
},
|
||||
};
|
||||
|
||||
let flags = [];
|
||||
for (let k, v in assoc_flags) {
|
||||
if (!data[k])
|
||||
continue;
|
||||
|
||||
let first = 0;
|
||||
for (let name, flag in v) {
|
||||
if (data[name] == null)
|
||||
continue;
|
||||
push(flags, `${flag} ${data[name]}`);
|
||||
if (!first++)
|
||||
push(flags, `${mhz}MHz`);
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
function dbm2mw(dbm) {
|
||||
const LOG10_MAGIC = 1.25892541179;
|
||||
let res = 1.0;
|
||||
let ip = dbm / 10;
|
||||
let fp = dbm % 10;
|
||||
|
||||
for (let k = 0; k < ip; k++)
|
||||
res *= 10;
|
||||
for (let k = 0; k < fp; k++)
|
||||
res *= LOG10_MAGIC;
|
||||
|
||||
return int(res);
|
||||
}
|
||||
|
||||
function dbm2quality(dbm) {
|
||||
let quality = dbm;
|
||||
|
||||
if (quality < -110)
|
||||
quality = -110;
|
||||
else if (quality > -40)
|
||||
quality = -40;
|
||||
quality += 110;
|
||||
|
||||
return quality;
|
||||
}
|
||||
|
||||
function hwmodelist(name) {
|
||||
const mode = { 'HT*': 'n', 'VHT*': 'ac', 'HE*': 'ax' };
|
||||
let iface = ifaces[name];
|
||||
let phy = board_data.wlan?.['phy' + iface.wiphy];
|
||||
if (!phy)
|
||||
return '';
|
||||
let htmodes = phy.info.bands[uc(iface.radio.band)].modes;
|
||||
let list = [];
|
||||
if (iface.radio.band == '2g' && 'NOHT' in htmodes)
|
||||
push(list, 'g/b');
|
||||
for (let k, v in mode)
|
||||
for (let htmode in htmodes)
|
||||
if (wildcard(htmode, k))
|
||||
push(list, v);
|
||||
|
||||
return join('/', reverse(uniq(list)));
|
||||
}
|
||||
|
||||
export function assoclist(dev) {
|
||||
let stations = ifaces[dev].assoclist;
|
||||
let ret = {};
|
||||
|
||||
for (let station in stations) {
|
||||
let sta = {
|
||||
mac: uc(station.mac),
|
||||
signal: station.sta_info.signal_avg,
|
||||
noise: ifaces[dev].noise,
|
||||
snr: station.sta_info.signal_avg - ifaces[dev].noise,
|
||||
inactive_time: station.sta_info.inactive_time,
|
||||
rx: {
|
||||
bitrate: format_rate(station.sta_info.rx_bitrate.bitrate),
|
||||
bitrate_raw: station.sta_info.rx_bitrate.bitrate,
|
||||
packets: station.sta_info.rx_packets,
|
||||
flags: assoc_flags(station.sta_info.rx_bitrate),
|
||||
},
|
||||
tx: {
|
||||
bitrate: format_rate(station.sta_info.tx_bitrate.bitrate),
|
||||
bitrate_raw: station.sta_info.tx_bitrate.bitrate,
|
||||
packets: station.sta_info.tx_packets,
|
||||
flags: assoc_flags(station.sta_info.tx_bitrate),
|
||||
},
|
||||
expected_throughput: station.sta_info.expected_throughput ?? 'unknown',
|
||||
};
|
||||
ret[sta.mac] = sta;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
export function freqlist(name) {
|
||||
const freq_flags = {
|
||||
no_10mhz: 'NO_10MHZ',
|
||||
no_20mhz: 'NO_20MHZ',
|
||||
no_ht40_minus: 'NO_HT40-',
|
||||
no_ht40_plus: 'NO_HT40+',
|
||||
no_80mhz: 'NO_80MHZ',
|
||||
no_160mhz: 'NO_160MHZ',
|
||||
indoor_only: 'INDOOR_ONLY',
|
||||
no_ir: 'NO_IR',
|
||||
no_he: 'NO_HE',
|
||||
radar: 'RADAR_DETECTION',
|
||||
};
|
||||
|
||||
let iface = ifaces[name];
|
||||
let phy = find_phy(iface.wiphy);
|
||||
let channels = [];
|
||||
|
||||
for (let k, band in phy.wiphy_bands) {
|
||||
if (!band)
|
||||
continue;
|
||||
|
||||
let band_name = format_band(band.freqs[0].freq);
|
||||
for (let freq in band.freqs) {
|
||||
if (freq.disabled)
|
||||
continue;
|
||||
|
||||
let channel = {
|
||||
freq: format_frequency(freq.freq),
|
||||
band: band_name,
|
||||
channel: format_channel(freq.freq),
|
||||
flags: [],
|
||||
active: iface.wiphy_freq == freq.freq,
|
||||
};
|
||||
|
||||
for (let k, v in freq_flags)
|
||||
if (freq[k])
|
||||
push(channel.flags, v);
|
||||
|
||||
push(channels, channel);
|
||||
}
|
||||
}
|
||||
|
||||
return channels;
|
||||
};
|
||||
|
||||
export function info(name) {
|
||||
let order = [];
|
||||
for (let iface, data in ifaces)
|
||||
push(order, iface);
|
||||
|
||||
let list = [];
|
||||
for (let iface in sort(order)) {
|
||||
if (name && iface != name)
|
||||
continue;
|
||||
let data = ifaces[iface];
|
||||
let dev = {
|
||||
iface,
|
||||
ssid: data.ssid,
|
||||
mac: data.mac,
|
||||
mode: data.mode,
|
||||
channel: format_channel(data.wiphy_freq),
|
||||
freq: format_frequency(data.wiphy_freq),
|
||||
htmode: data.radio.htmode,
|
||||
center_freq1: format_channel(data.center_freq1) || 'unknown',
|
||||
center_freq2: format_channel(data.center_freq2) || 'unknown',
|
||||
txpower: data.wiphy_tx_power_level / 100,
|
||||
noise: data.noise,
|
||||
signal: 0,
|
||||
bitrate: 0,
|
||||
encryption: 'unknown',
|
||||
hwmode: hwmodelist(iface),
|
||||
phy: 'phy' + data.wiphy,
|
||||
vaps: 'no',
|
||||
hw_type: data.hardware.type,
|
||||
hw_id: data.hardware.id,
|
||||
power_offset: data.hardware.power_offset || 'none',
|
||||
channel_offset: data.hardware.channel_offset || 'none',
|
||||
};
|
||||
|
||||
let phy = find_phy(data.wiphy);
|
||||
for (let limit in phy.interface_combinations[0]?.limits)
|
||||
if (limit.types?.ap && limit.max > 1)
|
||||
dev.vaps = 'yes';
|
||||
|
||||
if (data.bss_info) {
|
||||
if (data.bss_info.wpa_key_mgmt && data.bss_info.wpa_pairwise)
|
||||
dev.encryption = `${replace(data.bss_info.wpa_key_mgmt, ' ', ' / ')} (${data.bss_info.wpa_pairwise})`;
|
||||
else if (data.owe_transition_ifname)
|
||||
dev.encryption = 'none (OWE transition)';
|
||||
else
|
||||
dev.encryption = 'none';
|
||||
}
|
||||
|
||||
let stations = assoclist(iface);
|
||||
for (let k, station in stations) {
|
||||
dev.signal += station.signal;
|
||||
dev.bitrate += station.tx.bitrate_raw;
|
||||
}
|
||||
dev.signal /= length(data.assoclist) || 1;
|
||||
dev.bitrate /= length(data.assoclist) || 1;
|
||||
dev.bitrate = format_rate(dev.bitrate);
|
||||
dev.quality = dbm2quality(dev.signal);
|
||||
|
||||
if (data.owe_transition_ifname)
|
||||
dev.owe_transition_ifname = data.owe_transition_ifname;
|
||||
|
||||
push(list, dev);
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
export function htmodelist(name) {
|
||||
let iface = ifaces[name];
|
||||
let phy = board_data.wlan?.['phy' + iface.wiphy];
|
||||
if (!phy)
|
||||
return [];
|
||||
|
||||
return filter(phy.info.bands[uc(iface.radio.band)].modes, (v) => v != 'NOHT');
|
||||
};
|
||||
|
||||
export function txpowerlist(name) {
|
||||
let iface = ifaces[name];
|
||||
let max_power = iface.max_power / 100;
|
||||
let match = iface.wiphy_tx_power_level / 100;
|
||||
let list = [];
|
||||
|
||||
for (let power = 0; power <= max_power; power++) {
|
||||
let txpower = {
|
||||
dbm: power,
|
||||
mw: dbm2mw(power),
|
||||
active: power == match,
|
||||
};
|
||||
push(list, txpower);
|
||||
}
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
export function countrylist(dev) {
|
||||
let iface = ifaces[dev];
|
||||
|
||||
let list = {
|
||||
active: iface.country,
|
||||
countries,
|
||||
};
|
||||
|
||||
return list;
|
||||
};
|
||||
|
||||
export function scan(dev) {
|
||||
const rsn_cipher = [ 'NONE', 'WEP-40', 'TKIP', 'WRAP', 'CCMP', 'WEP-104', 'AES-OCB', 'CKIP', 'GCMP', 'GCMP-256', 'CCMP-256' ];
|
||||
const ht_chan_offset = [ 'no secondary', 'above', '[reserved]', 'below' ];
|
||||
const vht_chan_width = [ '20 or 40 MHz', '80 MHz', '80+80 MHz', '160 MHz' ];
|
||||
const ht_chan_width = [ '20 MHz', '40 MHz or higher' ];
|
||||
const SCAN_FLAG_AP = (1<<2);
|
||||
|
||||
let params = {
|
||||
dev,
|
||||
scan_flags: SCAN_FLAG_AP,
|
||||
scan_ssids: [ '' ],
|
||||
};
|
||||
|
||||
let res = nl80211.request(nl80211.const.NL80211_CMD_TRIGGER_SCAN, 0, params);
|
||||
if (res === false) {
|
||||
printf("Unable to trigger scan: " + nl80211.error() + "\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
res = nl80211.waitfor([
|
||||
nl80211.const.NL80211_CMD_NEW_SCAN_RESULTS,
|
||||
nl80211.const.NL80211_CMD_SCAN_ABORTED
|
||||
], 5000);
|
||||
|
||||
if (!res) {
|
||||
printf("Netlink error while awaiting scan results: " + nl80211.error() + "\n");
|
||||
exit(1);
|
||||
} else if (res.cmd == nl80211.const.NL80211_CMD_SCAN_ABORTED) {
|
||||
printf("Scan aborted by kernel\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let scan = nl80211.request(nl80211.const.NL80211_CMD_GET_SCAN, nl80211.const.NLM_F_DUMP, { dev });
|
||||
|
||||
let cells = [];
|
||||
for (let k, bss in scan) {
|
||||
bss = bss.bss;
|
||||
let cell = {
|
||||
bssid: uc(bss.bssid),
|
||||
frequency: format_frequency(bss.frequency),
|
||||
band: format_band(bss.frequency),
|
||||
channel: format_channel(bss.frequency),
|
||||
dbm: bss.signal_mbm / 100,
|
||||
|
||||
};
|
||||
|
||||
if (bss.capability & (1 << 1))
|
||||
cell.mode = 'Ad-Hoc';
|
||||
else if (bss.capability & (1 << 0))
|
||||
cell.mode = 'Master';
|
||||
else
|
||||
cell.mode = 'Mesh Point';
|
||||
|
||||
cell.quality = dbm2quality(cell.dbm);
|
||||
|
||||
for (let ie in bss.information_elements)
|
||||
switch(ie.type) {
|
||||
case 0:
|
||||
case 114:
|
||||
cell.ssid = ie.data;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
cell.country = substr(ie.data, 0, 2);
|
||||
break;
|
||||
|
||||
case 48:
|
||||
cell.crypto = {
|
||||
group: rsn_cipher[ord(ie.data, 5)] ?? '',
|
||||
pair: [],
|
||||
key_mgmt: [],
|
||||
};
|
||||
|
||||
let offset = 6;
|
||||
let count = ord(ie.data, offset);
|
||||
offset += 2;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let key = rsn_cipher[ord(ie.data, offset + 3)];
|
||||
if (key)
|
||||
push(cell.crypto.pair, key);
|
||||
offset += 4;
|
||||
}
|
||||
|
||||
count = ord(ie.data, offset);
|
||||
offset += 2;
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
let key = format_mgmt_key(ord(ie.data, offset + 3));
|
||||
if (key)
|
||||
push(cell.crypto.key_mgmt, key);
|
||||
offset += 4;
|
||||
}
|
||||
break;
|
||||
|
||||
case 61:
|
||||
cell.ht = {
|
||||
primary_channel: ord(ie.data, 0),
|
||||
secondary_chan_off: ht_chan_offset[ord(ie.data, 1) & 0x3],
|
||||
chan_width: ht_chan_width[(ord(ie.data, 1) & 0x4) >> 2],
|
||||
};
|
||||
break;
|
||||
|
||||
case 192:
|
||||
cell.vht = {
|
||||
chan_width: vht_chan_width[ord(ie.data, 0)],
|
||||
center_chan_1: ord(ie.data, 1),
|
||||
center_chan_2: ord(ie.data, 2),
|
||||
};
|
||||
break;
|
||||
};
|
||||
|
||||
|
||||
|
||||
push(cells, cell);
|
||||
}
|
||||
|
||||
return cells;
|
||||
};
|
||||
@@ -0,0 +1,481 @@
|
||||
'use strict';
|
||||
|
||||
import * as libuci from 'uci';
|
||||
import { md5 } from 'digest';
|
||||
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(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(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;
|
||||
|
||||
set_default(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(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 = md5(`${config.mobility_domain}/${config.auth_secret ?? config.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,122 @@
|
||||
'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);
|
||||
|
||||
return config_data;
|
||||
};
|
||||
|
||||
export function flush_config() {
|
||||
config_data = '';
|
||||
};
|
||||
|
||||
export function flush_network() {
|
||||
config_data = '';
|
||||
network_data = '';
|
||||
};
|
||||
@@ -0,0 +1,578 @@
|
||||
'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.hw_mode in [ '11a', '11b', '11g', '11ad' ])
|
||||
config.hw_mode = substr(config.hw_mode, 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 = +fs.readfile(`/sys/class/ieee80211/${phy}/index`);
|
||||
phy = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, nl80211.const.NLM_F_DUMP, { wiphy: idx, split_wiphy_dump: true });
|
||||
if (!phy)
|
||||
return;
|
||||
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);
|
||||
}
|
||||
|
||||
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' ]);
|
||||
|
||||
/* 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);
|
||||
|
||||
if (config.ieee80211ax || config.ieee80211be)
|
||||
append_vars(config, [ 'mbssid' ]);
|
||||
|
||||
/* 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);
|
||||
if (data.config.macaddr_base)
|
||||
append('\n#macaddr_base', data.config.macaddr_base);
|
||||
|
||||
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.hw_mode == '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, macaddr_base) {
|
||||
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} macaddr_base=${macaddr_base}`);
|
||||
|
||||
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,241 @@
|
||||
'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_code;
|
||||
interface.config.beacon_int = data.config.beacon_int;
|
||||
|
||||
append_vars(interface.config, [ 'country', '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,
|
||||
macaddr_base: data.config.macaddr_base ?? "",
|
||||
});
|
||||
|
||||
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,
|
||||
macaddr_base: data.config.macaddr_base ?? "",
|
||||
});
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user