wifi-scripts: add ucode based scripts

Add an ucode based re-implementation of the shell script based wifi code.

The new code is jsonschema driven. The code has been refactored into several
files making it easier to follow.

The new scripts are also way faster than the previous sh implementation.

The new code is currently opt-in via WIFI_SCRIPTS_UCODE and defaults to
EXPERIMENTAL.

Signed-off-by: John Crispin <john@phrozen.org>
This commit is contained in:
John Crispin
2024-10-17 15:12:55 +02:00
parent 0210279888
commit 218f3884d2
14 changed files with 4051 additions and 1 deletions

View File

@@ -0,0 +1,480 @@
'use strict';
import * as libuci from 'uci';
import * as fs from 'fs';
import { append, append_raw, append_value, append_vars, comment, push_config, set_default, touch_file } from 'wifi.common';
import * as netifd from 'wifi.netifd';
import * as iface from 'wifi.iface';
function iface_setup(config) {
switch(config.fixup) {
case 'owe':
config.ignore_broadcast_ssid = true;
config.ssid = config.ssid + 'OWE';
break;
case 'owe-transition':
let ifname = config.ifname;
config.ifname = config.owe_transition_ifname;
config.owe_transition_ifname = ifname;
config.owe_transition_ssid = config.ssid + 'OWE';
config.encryption = 'none';
config.ignore_broadcast_ssid = false;
iface.prepare(config);
break;
}
comment('Setup interface: ' + config.ifname);
config.bridge = config.network_bridge;
config.snoop_iface = config.network_ifname;
if (!config.wds)
config.wds_bridge = null;
else
config.wds_sta = true;
if (!config.idx)
append('interface', config.ifname);
else
append('bss', config.ifname);
if (config.multicast_to_unicast || config.proxy_arp)
config.ap_isolate = 1;
append('bssid', config.macaddr);
append_vars(config, [
'ctrl_interface', 'ap_isolate', 'max_num_sta', 'ap_max_inactivity', 'airtime_bss_weight',
'airtime_bss_limit', 'airtime_sta_weight', 'bss_load_update_period', 'chan_util_avg_period',
'disassoc_low_ack', 'skip_inactivity_poll', 'ignore_broadcast_ssid', 'uapsd_advertisement_enabled',
'utf8_ssid', 'multi_ap', 'ssid', 'tdls_prohibit', 'bridge', 'wds_sta', 'wds_bridge',
'snoop_iface', 'vendor_elements', 'nas_identifier', 'radius_acct_interim_interval',
'ocv', 'multicast_to_unicast', 'preamble', 'wmm_enabled', 'proxy_arp', 'per_sta_vif', 'mbo',
'bss_transition', 'wnm_sleep_mode', 'wnm_sleep_mode_no_keys', 'qos_map_set', 'max_listen_int',
'dtim_period',
]);
}
function iface_authentication_server(config) {
for (let server in config.auth_server_addr) {
append('auth_server_addr', server);
append_vars(config, [ 'auth_server_port', 'auth_server_shared_secret' ]);
}
append_vars(config, [ 'radius_auth_req_attr' ]);
}
function iface_accounting_server(config) {
for (let server in config.acct_server_addr) {
append('acct_server_addr', server);
append_vars(config, [ 'acct_server_port', 'acct_server_shared_secret' ]);
}
append_vars(config, [ 'radius_acct_req_attr' ]);
}
function iface_auth_type(config) {
iface.parse_encryption(config);
if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ]) {
config.ieee80211w = 2;
config.sae_require_mfp = 1;
config.sae_pwe = 2;
}
if (config.auth_type in [ 'psk-sae', 'eap-eap2' ]) {
config.ieee80211w = 1;
config.sae_require_mfp = 1;
config.sae_pwe = 2;
}
if (config.own_ip_addr)
config.dynamic_own_ip_addr = null;
if (!config.wpa)
config.wpa_disable_eapol_key_retries = null;
switch(config.auth_type) {
case 'none':
case 'owe':
config.wps_possible = 1;
config.wps_state = 1;
if (config.owe_transition_ssid)
config.owe_transition_ssid = `"${config.owe_transition_ssid}"`;
append_vars(config, [
'owe_transition_ssid', 'owe_transition_bssid', 'owe_transition_ifname',
]);
break;
case 'psk':
case 'psk2':
case 'sae':
case 'psk-sae':
config.vlan_possible = 1;
config.wps_possible = 1;
if (config.auth_type == 'psk' && config.ppsk) {
iface_authentication_server(config);
config.macaddr_acl = 2;
config.wpa_psk_radius = 2;
} else if (length(config.key) == 64) {
config.wpa_psk = key;
} else if (length(config.key) >= 8) {
config.wpa_passphrase = config.key;
} else if (!config.wpa_psk_file) {
netifd.setup_failed('INVALID_WPA_PSK');
}
set_default(config, 'wpa_psk_file', `/var/run/hostapd-${config.ifname}.psk`);
touch_file(config.wpa_psk_file);
set_default(config, 'dynamic_vlan', 0);
break;
case 'eap':
case 'eap2':
case 'eap-eap2':
case 'eap192':
config.vlan_possible = 1;
if (config.fils) {
set_default(config, 'erp_domain', substr(digest.md5(config.ssid), 0, 4));
set_default(config, 'fils_realm', config.erp_domain);
set_default(config, 'erp_send_reauth_start', 1);
set_default(config, 'fils_cache_id', substr(digest.md5(config.fils_realm), 0, 4));
}
if (!config.eap_server) {
iface_authentication_server(config);
iface_accounting_server(config);
}
if (config.radius_das_client && config.radius_das_secret) {
set_default(config, 'radius_das_port', 3799);
set_default(config, 'radius_das_client', `${config.radius_das_client} ${config.radius_das_secret}`);
}
set_default(config, 'eapol_version', config.wpa & 1);
if (!config.eapol_version)
config.eapol_version = null;
append('eapol_key_index_workaround', '1');
append('ieee8021x', '1');
break;
}
append_vars(config, [
'sae_require_mfp', 'sae_pwe', 'time_advertisement', 'time_zone',
'wpa_group_rekey', 'wpa_ptk_rekey', 'wpa_gmk_rekey', 'wpa_strict_rekey',
'macaddr_acl', 'wpa_psk_radius', 'wpa_psk', 'wpa_passphrase', 'wpa_psk_file',
'eapol_version', 'dynamic_vlan', 'radius_request_cui', 'eap_reauth_period',
'radius_das_client', 'radius_das_port', 'own_ip_addr', 'dynamic_own_ip_addr',
'wpa_disable_eapol_key_retries', 'auth_algs', 'wpa', 'wpa_pairwise',
'erp_domain', 'fils_realm', 'erp_send_reauth_start', 'fils_cache_id'
]);
}
function iface_ppsk(config) {
if (!(config.auth_type in [ 'none', 'owe', 'psk', 'sae', 'psk-sae', 'wep' ]) || !config.auth_server_addr)
return;
iface_authentication_server(config);
append('macaddr_acl', '2');
}
function iface_wps(config) {
push_config(config, 'config_methods', 'wps_pushbutton', 'push_button');
push_config(config, 'config_methods', 'wps_label', 'label');
if (config.multi_ap == 1)
config.wps_possible = false;
if (config.wps_possible && length(config.config_methods)) {
config.eap_server = 1;
set_default(config, 'wps_state', 2);
if (config.ext_registrar && config.network_bridge)
set_default(config, 'upnp_iface', config.network_bridge);
if (config.multi_ap && config.multi_ap_backhaul_ssid) {
append_vars(config, [ 'multi_ap_backhaul_ssid' ]);
if (length(config.multi_ap_backhaul_key) == 64)
append('multi_ap_backhaul_wpa_psk', config.multi_ap_backhaul_key);
else if (length(config.multi_ap_backhaul_key) > 8)
append('multi_ap_backhaul_wpa_passphrase', config.multi_ap_backhaul_key);
else
netifd.setup_failed('INVALID_WPA_PSK');
}
append_vars(config, [
'wps_state', 'device_type', 'device_name', 'config_methods', 'wps_independent', 'eap_server',
'ap_pin', 'ap_setup_locked', 'upnp_iface'
]);
}
}
function iface_rrm(config) {
set_default(config, 'rrm_neighbor_report', config.ieee80211k);
set_default(config, 'rrm_beacon_report', config.ieee80211k);
append_vars(config, [
'rrm_neighbor_report', 'rrm_beacon_report', 'rnr', 'ftm_responder',
]);
}
function iface_ftm(config, phy_features) {
if (!phy_features.ftm_responder || !config.ftm_responder)
return;
append_vars(config, [
'ftm_responder', 'lci', 'civic'
]);
}
function iface_macfilter(config) {
let path = `/var/run/hostapd-${config.ifname}.maclist`;
switch(config.macfilter) {
case 'allow':
append('accept_mac_file', path);
append('macaddr_acl', 1);
config.vlan_possible = 1;
break;
case 'deny':
append('deny_mac_file', path);
append('macaddr_acl', 0);
break;
default:
return;
}
let file = fs.open(path, 'w');
if (!file) {
warn(`Failed to open ${path}`);
return;
}
if (config.maclist)
file.write(join('\n', config.maclist));
let macfile = fs.readfile(config.macfile);
if (macfile)
file.write(macfile);
file.close();
}
function iface_vlan(interface, config, vlans) {
let path = `/var/run/hostapd-${config.ifname}.vlan`;
let file = fs.open(path, 'w');
for (let k, vlan in vlans)
if (vlan.config.name && vlan.config.vid) {
let ifname = `${config.ifname}-${vlan.config.name}`;
file.write(`${vlan.config.vid} ${ifname}\n`);
netifd.set_vlan(interface, k, ifname);
}
file.close();
set_default(config, 'vlan_file', path);
append_vars(config, [ 'vlan_file' ]);
if (!config.vlan_possible || !config.dynamic_vlan)
return;
config.vlan_no_bridge = !config.vlan_bridge;
append_vars(config, [
'dynamic_vlan', 'vlan_naming', 'vlan_bridge', 'vlan_no_bridge',
'vlan_tagged_interface'
]);
}
function iface_stations(config, stas) {
if (!length(stas))
return;
let path = `/var/run/hostapd-${config.ifname}.psk`;
let file = fs.open(path, 'w');
for (let k, sta in stas)
if (sta.config.mac && sta.config.key) {
let station = `${sta.config.mac} ${sta.config.key}\n`;
if (sta.config.vid)
station = `vlanid=${sta.config.vid} ` + station;
file.write(station);
}
file.close();
set_default(config, 'wpa_psk_file', path);
}
function iface_eap_server(config) {
if (!config.eap_server)
return;
set_default(config, 'eap_server', true);
set_default(config, 'eap_server_erp', true);
append_vars(config, [
'eap_server', 'eap_server_erp', 'eap_user_file', 'ca_cert', 'server_cert',
'private_key', 'private_key_passwd', 'server_id',
]);
}
function iface_roaming(config) {
if (!config.ieee80211r || config.wpa < 2)
return;
set_default(config, 'mobility_domain', substr(digest.md5(config.ssid), 0, 4));
set_default(config, 'ft_psk_generate_local', config.auth_type == 'psk');
set_default(config, 'ft_iface', config.network_ifname);
if (config.ft_psk_generate_local) {
if (!config.r0kh || !config.r1kh) {
if (!config.auth_secret && !config.key)
netifd.setup_failed('FT_KEY_CANT_BE_DERIVED');
let ft_key = digest.md5(`${mobility_domain}/${auth_secret ?? key}`);
set_default(config, 'r0kh', 'ff:ff:ff:ff:ff:ff,*,' + ft_key);
set_default(config, 'r1kh', '00:00:00:00:00:00,00:00:00:00:00:00,' + ft_key);
}
append_vars(config, [
'r0kh', 'r1kh', 'r1_key_holder', 'r0_key_lifetime', 'pmk_r1_push'
]);
}
append_vars(config, [
'mobility_domain', 'ft_psk_generate_local', 'ft_over_ds', 'reassociation_deadline',
'ft_iface'
]);
}
function iface_mfp(config) {
if (!config.ieee80211w || config.wpa < 2) {
append('ieee80211w', 0);
return;
}
if (config.auth_type == 'eap192')
config.group_mgmt_cipher = 'BIP-GMAC-256';
else
config.group_mgmt_cipher = config.ieee80211w_mgmt_cipher ?? 'AES-128-CMAC';
append_vars(config, [
'ieee80211w', 'group_mgmt_cipher', 'assoc_sa_query_max_timeout', 'assoc_sa_query_retry_timeout'
]);
}
function iface_key_caching(config) {
if (config.wpa < 2)
return;
if (config.network_bridge && config.rsn_preauth) {
set_default(config, 'okc', true);
config.rsn_preauth_interfaces = config.network_bridge;
append_vars(config, [
'rsn_preauth', 'rsn_preauth_interfaces'
]);
} else {
set_default(config, 'okc', (config.auth_type in [ 'sae', 'psk-sae', 'owe' ]));
}
if (!config.okc && !config.fils)
config.disable_pmksa_caching = 1;
append_vars(config, [
'okc', 'disable_pmksa_caching'
]);
}
function iface_hs20(config) {
if (!config.hs20)
return;
let uci = libuci.cursor();
let icons = uci.get_all('wireless');
for (let k, icon in icons)
if (icon['.type'] == 'hs20-icon')
append('hs20_icon', `${icon.width}:${icon.heigth}:${icon.lang}:${icon.type}:${k}:${icon.path}`);
append_vars(config, [
'hs20', 'disable_dgaf', 'osen', 'anqp_domain_id', 'hs20_deauth_req_timeout', 'osu_ssid',
'hs20_wan_metrics', 'hs20_operating_class', 'hs20_t_c_filename', 'hs20_t_c_timestamp',
'hs20_t_c_server_url', 'hs20_oper_friendly_name', 'hs20_conn_capab', 'osu_provider',
'operator_icon'
]);
}
function iface_interworking(config) {
if (!config.iw_enabled)
return;
config.interworking = true;
if (config.domain_name)
config.domain_name = join(',', config.domain_name);
if (config.anqp_3gpp_cell_net)
config.domain_name = join(',', config.anqp_3gpp_cell_net);
append_vars(config, [
'interworking', 'internet', 'asra', 'uesa', 'access_network_type', 'hessid', 'venue_group',
'venue_type', 'network_auth_type', 'gas_address3', 'roaming_consortium', 'anqp_elem', 'nai_realm',
'venue_name', 'venue_url', 'domain_name', 'anqp_3gpp_cell_net',
]);
}
export function generate(interface, config, vlans, stas, phy_features) {
config.ctrl_interface = '/var/run/hostapd';
iface_stations(config, stas);
iface_setup(config);
iface_auth_type(config);
iface_accounting_server(config);
iface_ppsk(config);
iface_wps(config);
iface_rrm(config);
iface_ftm(config, phy_features);
iface_macfilter(config);
iface_vlan(interface, config, vlans);
iface_eap_server(config);
iface_roaming(config);
iface_mfp(config);
iface_key_caching(config);
iface_hs20(config);
iface_interworking(config);
iface.wpa_key_mgmt(config);
append_vars(config, [
'wpa_key_mgmt'
]);
/* raw options */
for (let raw in config.hostapd_options)
append_raw(raw);
if (config.default_macaddr)
append_raw('#default_macaddr');
};

View File

@@ -0,0 +1,124 @@
'use strict';
import * as libubus from 'ubus';
import * as fs from 'fs';
global.ubus = libubus.connect();
let config_data = '';
let network_data = '';
export function log(msg) {
printf(`wifi-scripts: ${msg}\n`);
};
export function append_raw(value) {
config_data += value + '\n';
};
export function append(key, value) {
if (value == null)
return;
switch (type(value)) {
case 'array':
value = join(' ', value);
break;
case 'bool':
value = value ? 1 : 0;
break;
}
append_raw(key + '=' + value);
};
export function append_vars(dict, keys) {
for (let key in keys)
append(key, dict[key]);
};
export function network_append_raw(value) {
network_data += value + '\n';
};
export function network_append(key, value) {
if (value == null)
return;
switch (type(value)) {
case 'array':
value = join(' ', value);
break;
case 'bool':
value = value ? 1 : 0;
break;
}
network_append_raw('\t' + key + '=' + value);
};
export function network_append_vars(dict, keys) {
for (let key in keys)
network_append(key, dict[key]);
};
export function set_default(dict, key, value) {
if (dict[key] == null)
dict[key] = value;
};
export function push_config(dict, key, option, value) {
if (!dict[option])
return;
dict[key] ??= [];
push(dict[key], value);
};
export function touch_file(filename) {
let file = fs.open(filename, "a");
if (file)
file.close();
else
log('Failed to touch ' + filename);
};
export function append_value(config, key, value) {
if (!config[key])
config[key] = value;
else
config[key] += ' ' + value;
};
export function comment(comment) {
append_raw('\n# ' + comment);
};
export function dump_config(file) {
if (file)
fs.writefile(file, config_data);
return config_data;
};
export function dump_network(file) {
config_data += 'network={\n';
config_data += network_data;;
config_data += '}\n';
if (file)
fs.writefile(file, config_data);
printf('%s\n', config_data);
return config_data;
};
export function flush_config() {
config_data = '';
};
export function flush_network() {
config_data = '';
network_data = '';
};

View File

@@ -0,0 +1,577 @@
'use strict';
import { append, append_raw, append_vars, dump_config, flush_config, set_default } from 'wifi.common';
import { validate } from 'wifi.validate';
import * as netifd from 'wifi.netifd';
import * as iface from 'wifi.iface';
import * as nl80211 from 'nl80211';
import * as ap from 'wifi.ap';
import * as fs from 'fs';
const NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER = 33;
const NL80211_EXT_FEATURE_RADAR_BACKGROUND = 61;
let phy_features = {};
let phy_capabilities = {};
/* make sure old style UCI and hwmode and newer band properties are correctly resolved */
function set_device_defaults(config) {
/* validate the hw mode */
if (config.hwmode in [ '11a', '11b', '11g', '11ad' ])
config.hw_mode = substr(config.hwmode, 2);
else if (config.channel > 14)
config.hw_mode = 'a';
else
config.hw_mode = 'g';
/* validate band */
if (config.band == '2g')
config.hw_mode = 'g';
else if (config.band in [ '5g', '6g', '60g' ])
config.hw_mode = 'a';
else
switch (config.hw_mode) {
case 'a':
config.band = '5g';
break;
case 'ad':
config.band = '60g';
break;
default:
config.band = '2g';
break;
}
}
/* setup sylog / stdout */
function device_log_append(config) {
let log_mask = 0;
for (let k in [ 'log_mlme', 'log_iapp', 'log_driver', 'log_wpa', 'log_radius', 'log_8021x', 'log_80211' ]) {
log_mask <<= 1;
log_mask |= config[k] ? 1 : 0;
}
append('logger_syslog', log_mask);
append('logger_syslog_level', config.log_level);
append('logger_stdout', log_mask);
append('logger_stdout_level', config.log_level);
}
/* setup country code */
function device_country_code(config) {
let status = global.ubus.call('network.wireless', 'status');
for (let name, radio in status) {
if (!radio.config.country)
continue;
config.country_code = radio.config.country;
}
if (!exists(config, 'country_code'))
return;
if (config.hw_mode != 'a')
delete config.ieee80211h;
append_vars(config, [ 'country_code', 'country3', 'ieee80211h' ]);
if (config.ieee80211d)
append_vars(config, [ 'ieee80211d', 'local_pwr_constraint', 'spectrum_mgmt_required' ]);
}
/* setup cell density */
function device_cell_density_append(config) {
switch (config.hw_mode) {
case 'b':
if (config.cell_density == 1) {
config.supported_rates = [ 5500, 11000 ];
config.basic_rates = [ 5500, 11000 ];
} else if (config.cell_density > 2) {
config.supported_rates = [ 11000 ];
config.basic_rates = [ 11000 ];
}
;;
case 'g':
if (config.cell_density in [ 0, 1 ]) {
if (!config.legacy_rates) {
config.supported_rates = [ 6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 6000, 12000, 24000 ];
} else if (config.cell_density == 1) {
config.supported_rates = [ 5500, 6000, 9000, 11000, 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 5500, 11000 ];
}
} else if (config.cell_density == 2 || (config.cell_density > 3 && config.legacy_rates)) {
if (!config.legacy_rates) {
config.supported_rates = [ 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 12000, 24000 ];
} else {
config.supported_rates = [ 11000, 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 11000 ];
}
} else if (config.cell_density > 2) {
config.supported_rates = [ 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 24000 ];
}
;;
case 'a':
switch (config.cell_density) {
case 1:
config.supported_rates = [ 6000, 9000, 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 6000, 12000, 24000 ];
break;
case 2:
config.supported_rates = [ 12000, 18000, 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 12000, 24000 ];
break;
case 3:
config.supported_rates = [ 24000, 36000, 48000, 54000 ];
config.basic_rates = [ 24000 ];
break;
}
}
}
function device_rates(config) {
for (let key in [ 'supported_rates', 'basic_rates' ])
config[key] = map(config[key], x => x / 100);
append_vars(config, [ 'beacon_rate', 'supported_rates', 'basic_rates' ]);
}
function device_htmode_append(config) {
config.channel_offset = config.band == '6g' ? 1 : 0;
/* 802.11n */
config.ieee80211n = 0;
if (config.band != '6g') {
if (config.htmode in [ 'VHT20', 'HT20', 'HE20', 'EHT20' ])
config.ieee80211n = 1;
if (config.htmode in [ 'HT40', 'HT40+', 'HT40-', 'VHT40', 'VHT80', 'VHT160', 'HE40', 'HE80', 'HE160', 'EHT40', 'EHT80', 'EHT160' ]) {
config.ieee80211n = 1;
if (!config.channel)
config.ht_capab = '[HT40+]';
else
switch (config.hw_mode) {
case 'a':
switch (((config.channel / 4) + config.channel_offset) % 2) {
case 0:
config.ht_capab = '[HT40-]';
break;
case 1:
config.ht_capab = '[HT40+]';
break;
}
break;
default:
switch (config.htmode) {
case 'HT40+':
case 'HT40-':
config.ht_capab = '[' + config.htmode + ']';
break;
default:
if (config.channel < 7)
config.ht_capab = '[HT40+]';
else
config.ht_capab = '[HT40-]';
break;
}
}
}
if (config.ieee80211n) {
let ht_capab = phy_capabilities.ht_capa;
if (ht_capab & 0x1 && config.ldpc)
config.ht_capab += '[LDPC]';
if (ht_capab & 0x10 && config.greenfield)
config.ht_capab += '[GF]';
if (ht_capab & 0x20 && config.short_gi_20)
config.ht_capab += '[SHORT-GI-20]';
if (ht_capab & 0x40 && config.short_gi_40)
config.ht_capab += '[SHORT-GI-40]';
if (ht_capab & 0x80 && config.tx_stbc)
config.ht_capab += '[TX-STBC]';
if (ht_capab & 0x800 && config.max_amsdu)
config.ht_capab += '[MAX-AMSDU-7935]';
if (ht_capab & 0x1000 && config.dsss_cck_40)
config.ht_capab += '[DSSS_CCK-40]';
let rx_stbc = [ '', '[RX-STBC1]', '[RX-STBC12]', '[RX-STBC123]' ];
config.ht_capab += rx_stbc[min(config.rx_stbc, (ht_capab >> 8) & 3)];
append_vars(config, [ 'ieee80211n', 'ht_coex', 'ht_capab' ]);
}
}
/* 802.11ac */
config.ieee80211ac = 1;
config.vht_oper_centr_freq_seg0_idx = 0;
config.vht_oper_chwidth = 0;
switch (config.htmode) {
case 'VHT20':
case 'HE20':
case 'EHT20':
break;
case 'VHT40':
case 'HE40':
case 'EHT40':
config.vht_oper_centr_freq_seg0_idx = config.channel + (((config.channel / 4) + config.channel_offset) % 2 ? 2 : -2);
break;
case 'VHT80':
case 'HE80':
case 'EHT80':
let delta = [ -6, 6, 2, -2 ];
config.vht_oper_centr_freq_seg0_idx = config.channel + delta[((config.channel / 4) + config.channel_offset) % 4];
config.vht_oper_chwidth = 1;
break;
case 'VHT160':
case 'HE160':
case 'EHT160':
let vht_oper_centr_freq_seg0_idx_map = [[ 64, 50 ], [ 128, 114 ], [ 177, 163 ]];
if (config.band == '6g')
vht_oper_centr_freq_seg0_idx_map = [
[ 29, 15 ], [ 61, 47 ], [ 93, 79 ], [ 125, 111 ],
[ 157, 143 ], [ 189, 175 ], [ 221, 207 ]];
for (let k, v in vht_oper_centr_freq_seg0_idx_map)
if (v[0] <= config.channel) {
config.vht_oper_centr_freq_seg0_idx = v[1];
break;
}
config.vht_oper_chwidth = 2;
break;
default:
config.ieee80211ac = 0;
break;
}
config.eht_oper_chwidth = config.vht_oper_chwidth;
config.eht_oper_centr_freq_seg0_idx = config.vht_oper_centr_freq_seg0_idx;
if (config.band == '6g') {
config.ieee80211ac = 0;
switch(config.htmode) {
case 'HE20':
case 'EHT20':
config.op_class = 131;
break;
case 'EHT320':
let eht_center_seg0_map = [
[ 61, 31 ], [ 125, 95 ], [ 189, 159 ], [ 221, 191 ]
];
for (let k, v in eht_center_seg0_map)
if (v[0] <= config.channel) {
config.eht_oper_centr_freq_seg0_idx = v[1];
break;
}
config.op_class = 137;
config.eht_oper_chwidth = 7;
break;
case 'HE40':
case 'HE80':
case 'HE160':
case 'EHT40':
case 'EHT80':
case 'EHT160':
config.op_class = 132 + config.eht_oper_chwidth;
break;
}
append_vars(config, [ 'op_class' ]);
}
if (config.ieee80211ac && config.hw_mode == 'a') {
/* VHT capab */
if (config.vht_oper_chwidth < 2) {
config.vht160 = 0;
config.short_gi_160 = 0;
}
config.tx_queue_data2_burst = '2.0';
let vht_capab = phy_capabilities.vht_capa;
config.vht_capab = '';
if (vht_capab & 0x10 && config.rxldpc)
config.vht_capab += '[RXLDPC]';
if (vht_capab & 0x20 && config.short_gi_80)
config.vht_capab += '[SHORT-GI-80]';
if (vht_capab & 0x40 && config.short_gi_160)
config.vht_capab += '[SHORT-GI-160]';
if (vht_capab & 0x80 && config.tx_stbc_2by1)
config.vht_capab += '[TX-STBC-2BY1]';
if (vht_capab & 0x800 && config.su_beamformer)
config.vht_capab += '[SU-BEAMFORMER]';
if (vht_capab & 0x1000 && config.su_beamformee)
config.vht_capab += '[SU-BEAMFORMEE]';
if (vht_capab & 0x80000 && config.mu_beamformer)
config.vht_capab += '[MU-BEAMFORMER]';
if (vht_capab & 0x100000 && config.mu_beamformee)
config.vht_capab += '[MU-BEAMFORMEE]';
if (vht_capab & 0x200000 && config.vht_txop_ps)
config.vht_capab += '[VHT-TXOP-PS]';
if (vht_capab & 0x400000 && config.htc_vht)
config.vht_capab += '[HTC-VHT]';
if (vht_capab & 0x10000000 && config.rx_antenna_pattern)
config.vht_capab += '[RX-ANTENNA-PATTERN]';
if (vht_capab & 0x20000000 && config.tx_antenna_pattern)
config.vht_capab += '[TX-ANTENNA-PATTERN]';
let rx_stbc = [ '', '[RX-STBC1]', '[RX-STBC12]', '[RX-STBC123]', '[RX-STBC-1234]' ];
config.vht_capab += rx_stbc[min(config.rx_stbc, (vht_capab >> 8) & 7)];
if (vht_capab & 0x800 && config.su_beamformer)
config.vht_capab += '[SOUNDING-DIMENSION' + min(((vht_capab >> 16) & 3) + 1, config.beamformer_antennas) + ']';
if (vht_capab & 0x1000 && config.su_beamformee)
config.vht_capab += '[BF-ANTENNA-' + min(((vht_capab >> 13) & 3) + 1, config.beamformer_antennas) + ']';
/* supported Channel widths */
if (vht_capab & 0xc == 8 && config.vht160 <= 2)
config.vht_capab += '[VHT160-80PLUS80]';
else if (vht_capab & 0xc == 4 && config.vht160 <= 1)
config.vht_capab += '[VHT160]';
/* maximum MPDU length */
if (vht_capab & 3 > 1 && config.vht_max_mpdu > 11454)
config.vht_capab += '[MAX-MPDU-11454]';
else if (vht_capab & 3 && config.vht_max_mpdu > 7991)
config.vht_capab += '[MAX-MPDU-7991]';
/* maximum A-MPDU length exponent */
let max_a_mpdu_len_exp = (vht_capab >> 20) & 0x38;
for (let exp = 7; exp; exp--)
if (max_a_mpdu_len_exp >= (0x8 * exp) && exp <= config.vht_max_a_mpdu_len_exp) {
config.vht_capab += '[MAX-A-MPDU-LEN-EXP' + exp + ']';
break;
}
/* whether or not the STA supports link adaptation using VHT variant */
let vht_link_adapt = vht_capab & 0xC000000;
if (vht_link_adapt >= 0xC000000 && config.vht_link_adapt > 3)
config.vht_capab += '[VHT-LINK-ADAPT-3]';
if (vht_link_adapt >= 0x8000000 && config.vht_link_adapt > 2)
config.vht_capab += '[VHT-LINK-ADAPT-2]';
append_vars(config, [
'ieee80211ac', 'vht_oper_chwidth', 'vht_oper_centr_freq_seg0_idx',
'vht_capab'
]);
}
/* 802.11ax */
if (wildcard(config.htmode, 'HE*') || wildcard(config.htmode, 'EHT*')) {
let he_phy_cap = phy_capabilities.he_phy_cap;
let he_mac_cap = phy_capabilities.he_mac_cap;
config.ieee80211ax = true;
if (config.hw_mode == 'a') {
config.he_oper_chwidth = config.vht_oper_chwidth;
config.he_oper_centr_freq_seg0_idx = config.vht_oper_centr_freq_seg0_idx;
}
if (config.he_bss_color_enabled) {
if (config.he_spr_non_srg_obss_pd_max_offset)
config.he_spr_sr_control |= 1 << 2;
if (!config.he_spr_psr_enabled)
config.he_spr_sr_control |= 1;
append_vars(config, [ 'he_bss_color', 'he_spr_non_srg_obss_pd_max_offset', 'he_spr_sr_control' ]);
}
if (!(he_phy_cap[3] & 0x80))
config.he_su_beamformer = false;
if (!(he_phy_cap[4] & 0x1))
config.he_su_beamformee = false;
if (!(he_phy_cap[4] & 0x2))
config.he_mu_beamformer = false;
if (!(he_phy_cap[7] & 0x1))
config.he_spr_psr_enabled = false;
if (!(he_mac_cap[0] & 0x1))
config.he_twt_required= false;
append_vars(config, [
'ieee80211ax', 'he_oper_chwidth', 'he_oper_centr_freq_seg0_idx',
'he_su_beamformer', 'he_su_beamformee', 'he_mu_beamformer', 'he_twt_required',
'he_default_pe_duration', 'he_rts_threshold', 'he_mu_edca_qos_info_param_count',
'he_mu_edca_qos_info_q_ack', 'he_mu_edca_qos_info_queue_request', 'he_mu_edca_qos_info_txop_request',
'he_mu_edca_ac_be_aifsn', 'he_mu_edca_ac_be_aci', 'he_mu_edca_ac_be_ecwmin',
'he_mu_edca_ac_be_ecwmax', 'he_mu_edca_ac_be_timer', 'he_mu_edca_ac_bk_aifsn',
'he_mu_edca_ac_bk_aci', 'he_mu_edca_ac_bk_ecwmin', 'he_mu_edca_ac_bk_ecwmax',
'he_mu_edca_ac_bk_timer', 'he_mu_edca_ac_vi_ecwmin', 'he_mu_edca_ac_vi_ecwmax',
'he_mu_edca_ac_vi_aifsn', 'he_mu_edca_ac_vi_aci', 'he_mu_edca_ac_vi_timer',
'he_mu_edca_ac_vo_aifsn', 'he_mu_edca_ac_vo_aci', 'he_mu_edca_ac_vo_ecwmin',
'he_mu_edca_ac_vo_ecwmax', 'he_mu_edca_ac_vo_timer',
]);
}
if (wildcard(config.htmode, 'EHT*')) {
config.ieee80211be = true;
append_vars(config, [ 'ieee80211be' ]);
if (config.hw_mode == 'a')
append_vars(config, [ 'eht_oper_chwidth', 'eht_oper_centr_freq_seg0_idx' ]);
if (config.band == "6g") {
config.stationary_ap = true;
append_vars(config, [ 'he_6ghz_reg_pwr_type', ]);
}
}
append_vars(config, [ 'tx_queue_data2_burst', 'stationary_ap' ]);
}
function device_extended_features(data, flag) {
return !!(data[flag / 8] | (1 << (flag % 8)));
}
function device_capabilities(phy) {
let idx = +substr(phy, 3, 1);;
let phys = nl80211.request(nl80211.const.NL80211_CMD_GET_WIPHY, nl80211.const.NLM_F_DUMP, { wiphy: idx, split_wiphy_dump: true });
for (let phy in phys) {
if (!phy || phy.wiphy != idx)
continue;
for (let band in phy.wiphy_bands) {
if (!band)
continue;
phy_capabilities.ht_capa = band.ht_capa ?? 0;
phy_capabilities.vht_capa = band.vht_capa ?? 0;
for (let iftype in band.iftype_data) {
if (!iftype.iftypes.ap)
continue;
phy_capabilities.he_mac_cap = iftype.he_cap_mac;
phy_capabilities.he_phy_cap = iftype.he_cap_phy;
}
break;
}
phy_features.ftm_responder = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_ENABLE_FTM_RESPONDER);
phy_features.radar_background = device_extended_features(phy.extended_features, NL80211_EXT_FEATURE_RADAR_BACKGROUND);
break;
}
}
function generate(config) {
if (!config.phy)
die(`${config.path} is an unknown phy`);
device_capabilities(config.phy);
append('driver', 'nl80211');
set_device_defaults(config);
device_log_append(config);
device_country_code(config);
device_cell_density_append(config);
device_rates(config);
/* beacon */
append_vars(config, [ 'beacon_int', 'beacon_rate', 'rnr_beacon', 'mbssid' ]);
/* wpa_supplicant co-exist */
append_vars(config, [ 'noscan' ]);
/* airtime */
append_vars(config, [ 'airtime_mode' ]);
/* assoc/thresholds */
append_vars(config, [ 'rssi_reject_assoc_rssi', 'rssi_ignore_probe_request', 'iface_max_num_sta', 'no_probe_resp_if_max_sta' ]);
/* ACS / Radar*/
if (!phy_features.radar_background || config.band != '5g')
delete config.enable_background_radar;
else
set_default(config, 'enable_background_radar', phy_features.radar_background);
append_vars(config, [ 'acs_chan_bias', 'acs_exclude_dfs', 'enable_background_radar' ]);
/* TX Power */
append_vars(config, [ 'min_tx_power' ]);
/* hwmode, channel, op_class, ... */
append_vars(config, [ 'hw_mode', 'channel', 'rts_threshold', 'chanlist' ]);
if (config.hw_mode in [ 'a', 'g' ] && config.require_mode in [ 'n', 'ac', 'ax' ]) {
let require_mode = { n: 'require_ht', ac: 'require_vht', ax: 'require_he' };
config.legacy_rates = false;
append(require_mode[config.require_mode], 1);
}
device_htmode_append(config);
/* 6G power mode */
if (config.band != '6g')
append_vars(config, [ 'reg_power_type' ]);
/* raw options */
for (let raw in config.hostapd_options)
append_raw(raw);
}
let iface_idx = 0;
function setup_interface(interface, config, vlans, stas, phy_features, fixup) {
config = { ...config, fixup };
config.idx = iface_idx++;
ap.generate(interface, config, vlans, stas, phy_features);
}
export function setup(data) {
let file_name = `/var/run/hostapd-${data.phy}${data.vif_phy_suffix}.conf`;
flush_config();
if (fs.stat(file_name))
fs.rename(file_name, file_name + '.prev');
data.config.phy = data.phy;
generate(data.config);
if (data.config.num_global_macaddr)
append('\n#num_global_macaddr', data.config.num_global_macaddr);
for (let k, interface in data.interfaces) {
if (interface.config.mode != 'ap')
continue;
interface.config.network_bridge = interface.bridge;
interface.config.network_ifname = interface['bridge-ifname'];
let owe = interface.config.encryption == 'owe' && interface.config.owe_transition;
setup_interface(k, interface.config, interface.vlans, interface.stas, phy_features, owe ? 'owe' : null );
if (owe)
setup_interface(k, interface.config, interface.vlans, interface.stas, phy_features, 'owe-transition');
}
let config = dump_config(file_name);
let msg = {
phy: data.phy,
radio: data.config.radio,
config: file_name,
prev_config: file_name + '.prev'
};
let ret = global.ubus.call('hostapd', 'config_set', msg);
if (ret)
netifd.add_process('/usr/sbin/hostapd', ret.pid, true, true);
else
netifd.setup_failed('HOSTAPD_START_FAILED');
};

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.hwmode == 'ad') ? 'GCMP' : 'CCMP';
switch(encryption[1]){
case 'tkip+aes':
case 'tkip+ccmp':
case 'aes+tkip':
case 'ccmp+tkip':
config.wpa_pairwise = 'CCMP TKIP';
break;
case 'ccmp256':
config.wpa_pairwise = 'CCMP-256';
break;
case 'aes':
case 'ccmp':
config.wpa_pairwise = 'CCMP';
break;
case 'tkip':
config.wpa_pairwise = 'TKIP';
break;
case 'gcmp256':
config.wpa_pairwise = 'GCMP-256';
break;
case 'gcmp':
config.wpa_pairwise = 'GCMP';
break;
default:
if (config.encryption == 'wpa3-192')
config.wpa_pairwise = 'GCMP-256';
break;
}
config.wpa = 0;
for (let k, v in { 'wpa2*': 2, 'wpa3*': 2, '*psk2*': 2, 'psk3*': 2, 'sae*': 2,
'owe*': 2, 'wpa*mixed*': 3, '*psk*mixed*': 3, 'wpa*': 1, '*psk*': 1, })
if (wildcard(config.encryption, k)) {
config.wpa = v;
break;
}
if (!config.wpa)
config.wpa_pairwise = null;
config.auth_type = encryption[0] ?? 'none';
switch(config.auth_type) {
case 'owe':
config.auth_type = 'owe';
break;
case 'wpa3-192':
config.auth_type = 'eap192';
break;
case 'wpa3-mixed':
config.auth_type = 'eap-eap2';
break;
case 'wpa3':
config.auth_type = 'eap2';
break;
case 'sae-mixed':
config.auth_type = 'psk-sae';
break;
case 'wpa':
case 'wpa2':
config.auth_type = 'eap';
break;
}
};
export function wpa_key_mgmt(config) {
if (!config.wpa)
return;
switch(config.auth_type) {
case 'psk':
case 'psk2':
append_value(config, 'wpa_key_mgmt', 'WPA-PSK');
if (config.wpa >= 2 && config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-PSK');
if (config.ieee80211w)
append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256');
break;
case 'eap':
append_value(config, 'wpa_key_mgmt', 'WPA-EAP');
if (config.wpa >= 2 && config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
if (config.ieee80211w)
append_value(config, 'wpa_key_mgmt', 'WPA-EAP--SHA256');
break;
case 'eap192':
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SUITE-B-192');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-EAP-SHA384');
break;
case 'eap-eap2':
append_value(config, 'wpa_key_mgmt', 'WPA-EAP');
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SHA256');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
break;
case 'eap2':
append_value(config, 'wpa_key_mgmt', 'WPA-EAP-SHA256');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-EAP');
break;
case 'sae':
append_value(config, 'wpa_key_mgmt', 'SAE');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-SAE');
break;
case 'psk-sae':
append_value(config, 'wpa_key_mgmt', 'WPA-PSK');
append_value(config, 'wpa_key_mgmt', 'SAE');
if (config.ieee80211w)
append_value(config, 'wpa_key_mgmt', 'WPA-PSK-SHA256');
if (config.ieee80211r) {
append_value(config, 'wpa_key_mgmt', 'FT-PSK');
append_value(config, 'wpa_key_mgmt', 'FT-SAE');
}
break;
case 'owe':
append_value(config, 'wpa_key_mgmt', 'OWE');
break;
}
if (config.fils) {
switch(config.auth_type) {
case 'eap192':
append_value(config, 'wpa_key_mgmt', 'FILS-SHA384');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-FILS-SHA384');
break;
case 'eap-eap2':
case 'eap2':
case 'eap':
append_value(config, 'wpa_key_mgmt', 'FILS-SHA256');
if (config.ieee80211r)
append_value(config, 'wpa_key_mgmt', 'FT-FILS-SHA256');
break;
}
}
config.key_mgmt = config.wpa_key_mgmt;
};
function macaddr_random() {
let f = open("/dev/urandom", "r");
let addr = f.read(6);
addr = map(split(addr, ""), (v) => ord(v));
addr[0] &= ~1;
addr[0] |= 2;
return join(":", map(addr, (v) => sprintf("%02x", v)));
}
let mac_idx = 0;
export function prepare(data, phy, num_global_macaddr) {
if (!data.macaddr) {
let pipe = fs.popen(`ucode /usr/share/hostap/wdev.uc ${phy} get_macaddr id=${mac_idx} num_global=${num_global_macaddr} mbssid=${data.mbssid ?? 0}`);
data.macaddr = trim(pipe.read("all"), '\n');
pipe.close();
data.default_macaddr = true;
mac_idx++;
} else if (data.macaddr == 'random')
data.macaddr = macaddr_random();
log(`Preparing interface: ${data.ifname} with MAC: ${data.macaddr}`);
};

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,237 @@
'use strict';
import { append, append_raw, append_vars, network_append, network_append_raw, network_append_vars,
set_default, dump_network, flush_network } from 'wifi.common';
import * as netifd from 'wifi.netifd';
import * as iface from 'wifi.iface';
import * as fs from 'fs';
function set_fixed_freq(data, config) {
if (!data.frequency)
return;
set_default(config, 'fixed_freq', 1);
set_default(config, 'frequency', data.frequency);
if (data.htmode in [ 'VHT80', 'HE80' ])
set_default(config, 'max_oper_chwidth', 1);
else if (data.htmode in [ 'VHT160', 'HE160' ])
set_default(config, 'max_oper_chwidth', 2);
else if (data.htmode in [ 'VHT20', 'VHT40', 'HE20', 'HE40' ])
set_default(config, 'max_oper_chwidth', 0);
else
set_default(config, 'disable_vht', true);
if (data.htmode in [ 'NOHT' ])
set_default(config, 'disable_ht', true);
else if (data.htmode in [ 'HT20', 'VHT20', 'HE20' ])
set_default(config, 'disable_ht40', true);
else if (data.htmode in [ 'VHT40', 'VHT80', 'VHT160', 'HE40', 'HE80', 'HE160' ])
set_default(config, 'ht40', true);
if (wildcard(data.htmode, 'VHT*'))
set_default(config, 'vht', 1);
}
export function ratestr(rate) {
if (rate == null)
return rate;
let rem = (rate / 100) % 10;
rate = int(rate / 1000);
if (rem > 0)
rate += "." + rem;
return "" + rate;
};
export function ratelist(rates) {
if (length(rates) < 1)
return null;
return join(",", map(rates, (rate) => ratestr(rate)));
};
function setup_sta(data, config) {
iface.parse_encryption(config);
if (config.auth_type in [ 'sae', 'owe', 'eap2', 'eap192' ])
set_default(config, 'ieee80211w', 2);
else if (config.auth_type in [ 'psk-sae' ])
set_default(config, 'ieee80211w', 1);
set_default(config, 'ieee80211r', 0);
set_default(config, 'multi_ap', 0);
set_default(config, 'default_disabled', 0);
//multiap_flag_file="${_config}.is_multiap"
config.scan_ssid = 1;
switch(config.mode) {
case 'sta':
set_default(config, 'multi_ap_backhaul_sta', config.multi_ap);
break;
case 'adhoc':
config.ap_scan = 2;
config.scan_ssid = 0;
network_append('mode', 1);
set_fixed_freq(data, config);
break;
case 'mesh':
config.ssid = config.mesh_id;
config.scan_ssid = null;
network_append('mode', 5);
set_fixed_freq(data, config);
if (config.encryption && config.encryption != 'none')
config.key_mgmt = 'SAE';
config.ieee80211w = null;
break;
}
if (config.mode != 'mesh' ) {
switch(config.wpa) {
case 1:
config.proto = 'WPA';
break;
case 2:
config.proto = 'RSN';
break;
}
}
switch(config.auth_type) {
case 'none':
break;
case 'owe':
iface.wpa_key_mgmt(config);
break;
case 'psk':
case 'psk2':
case 'sae':
case 'psk-sae':
if (config.mode != 'mesh')
iface.wpa_key_mgmt(config);
if (config.mode == 'mesh' || config.auth_type == 'sae')
config.sae_password = `"${config.key}"`;
else
config.psk = `"${config.key}"`;
break;
case 'eap':
case 'eap2':
case 'eap192':
iface.wpa_key_mgmt(config);
set_default(config, 'erp', config.fils);
if (config.ca_cert_usesystem && fs.stat('/etc/ssl/certs/ca-certificates.crt'))
config.ca_cert = '/etc/ssl/certs/ca-certificates.crt';
switch(config.eap_type) {
case 'fast':
case 'peap':
case 'ttls':
set_default(config, 'auth', 'MSCHAPV2');
if (config.auth == 'EAP-TLS') {
if (config.ca_cert2_usesystem && fs.stat('/etc/ssl/certs/ca-certificates.crt'))
config.ca_cert2 = '/etc/ssl/certs/ca-certificates.crt';
}
break;
}
}
if (config.wpa_pairwise == 'GCMP') {
config.pairwise = 'GCMP';
config.group = 'GCMP';
}
config.basic_rate = ratelist(config.basic_rate);
config.mcast_rate = ratestr(config.mcast_rate);
config.ssid = `"${config.ssid}"`;
network_append_vars(config, [
'scan_ssid', 'noscan', 'disabled', 'multi_ap_backhaul_sta',
'ocv', 'key_mgmt', 'psk', 'sae_password', 'pairwise', 'group', 'bssid',
'proto', 'mesh_fwding', 'mesh_rssi_threshold', 'frequency', 'fixed_freq',
'disable_ht', 'disable_ht40', 'disable_vht', 'vht', 'max_oper_chwidth',
'ht40', 'ssid', 'beacon_int', 'ieee80211w', 'basic_rate', 'mcast_rate',
'bssid_blacklist', 'bssid_whitelist', 'erp', 'ca_cert', 'identity',
'anonymous_identity', 'client_cert', 'private_key', 'private_key_passwd',
'subject_match', 'altsubject_match', 'domain_match', 'domain_suffix_match',
'ca_cert2', 'client_cert2', 'private_key2', 'private_key2_passwd', 'password'
]);
}
export function generate(config_list, data, interface) {
flush_network();
if (interface.bridge &&
(interface.config.mode == 'adhoc' ||
(interface.config.mode == 'sta' && !interface.config.wds && !interface.config.multi_ap))){
netifd.setup_failed('BRIDGE_NOT_ALLOWED');
return 1;
}
interface.config.country = data.config.country;
interface.config.beacon_int = data.config.beacon_int;
setup_sta(data.config, interface.config);
let file_name = `/var/run/wpa-supplicant-${interface.config.ifname}.conf`;
if (fs.stat(file_name))
fs.rename(file_name, file_name + '.prev');
dump_network(file_name);
let config = {
mode: interface.config.mode,
ctrl: '/var/run/wpa_supplicant',
iface: interface.config.ifname,
config: file_name,
'4addr': !!interface.config.wds,
powersave: false
};
if (!interface.config.default_macaddr)
config.macaddr = interface.config.macaddr;
if (interface.config.wds)
config.bridge = interface.bridge;
push(config_list, config);
return config;
};
export function setup(config, data) {
let ret = global.ubus.call('wpa_supplicant', 'config_set', {
phy: data.phy,
radio: data.config.radio,
config,
defer: true,
num_global_macaddr: data.config.num_global_macaddr,
});
if (ret)
netifd.add_process('/usr/sbin/wpa_supplicant', ret.pid, true, true);
else
netifd.setup_failed('SUPPLICANT_START_FAILED');
};
export function start(data) {
global.ubus.call('wpa_supplicant', 'config_set', {
phy: data.phy,
radio: data.config.radio,
num_global_macaddr: data.config.num_global_macaddr,
});
};

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