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

This commit is contained in:
domenico
2025-06-24 14:35:53 +02:00
commit c06fb25d1f
9263 changed files with 1750214 additions and 0 deletions

View File

@@ -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;
};

View File

@@ -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');
};

View File

@@ -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 = '';
};

View File

@@ -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');
};

View File

@@ -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}`);
};

View File

@@ -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);
};

View File

@@ -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 ?? "",
});
};

View File

@@ -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);
}
};