| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386 |
- let libubus = require("ubus");
- import { open, readfile } from "fs";
- import { wdev_remove, is_equal, vlist_new, phy_is_fullmac, phy_open, wdev_set_radio_mask, wdev_set_up } from "common";
- let ubus = libubus.connect(null, 60);
- hostapd.data.config = {};
- hostapd.data.pending_config = {};
- hostapd.data.file_fields = {
- vlan_file: true,
- wpa_psk_file: true,
- sae_password_file: true,
- rxkh_file: true,
- accept_mac_file: true,
- deny_mac_file: true,
- eap_user_file: true,
- ca_cert: true,
- server_cert: true,
- server_cert2: true,
- private_key: true,
- private_key2: true,
- dh_file: true,
- eap_sim_db: true,
- };
- hostapd.data.iface_fields = {
- ft_iface: true,
- upnp_iface: true,
- snoop_iface: true,
- bridge: true,
- iapp_interface: true,
- };
- hostapd.data.bss_info_fields = {
- // radio
- hw_mode: true,
- channel: true,
- ieee80211ac: true,
- ieee80211ax: true,
- // bss
- bssid: true,
- ssid: true,
- wpa: true,
- wpa_key_mgmt: true,
- wpa_pairwise: true,
- auth_algs: true,
- ieee80211w: true,
- owe_transition_ifname: true,
- };
- hostapd.data.mld = {};
- function iface_remove(cfg)
- {
- if (!cfg || !cfg.bss || !cfg.bss[0] || !cfg.bss[0].ifname)
- return;
- for (let bss in cfg.bss)
- if (!bss.mld_ap)
- wdev_remove(bss.ifname);
- }
- function iface_gen_config(config, start_disabled)
- {
- let str = `data:
- ${join("\n", config.radio.data)}
- channel=${config.radio.channel}
- `;
- for (let i = 0; i < length(config.bss); i++) {
- let bss = config.bss[i];
- let type = i > 0 ? "bss" : "interface";
- let nasid = bss.nasid ?? replace(bss.bssid, ":", "");
- let bssid = bss.bssid;
- if (bss.mld_ap)
- bssid += "\nmld_addr=" + bss.mld_bssid;
- str += `
- ${type}=${bss.ifname}
- bssid=${bssid}
- ${join("\n", bss.data)}
- nas_identifier=${nasid}
- `;
- if (start_disabled)
- str += `
- start_disabled=1
- `;
- }
- return str;
- }
- function iface_freq_info(iface, config, params)
- {
- let freq = params.frequency;
- if (!freq)
- return null;
- let sec_offset = params.sec_chan_offset;
- if (sec_offset != -1 && sec_offset != 1)
- sec_offset = 0;
- let width = 0;
- for (let line in config.radio.data) {
- if (!sec_offset && match(line, /^ht_capab=.*HT40/)) {
- sec_offset = null; // auto-detect
- continue;
- }
- let val = match(line, /^(vht_oper_chwidth|he_oper_chwidth)=(\d+)/);
- if (!val)
- continue;
- val = int(val[2]);
- if (val > width)
- width = val;
- }
- if (freq < 4000)
- width = 0;
- return hostapd.freq_info(freq, sec_offset, width);
- }
- function iface_add(phy, config, phy_status)
- {
- let config_inline = iface_gen_config(config, !!phy_status);
- let bss = config.bss[0];
- let ret = hostapd.add_iface(`bss_config=${phy}:${config_inline}`);
- if (ret < 0)
- return false;
- if (!phy_status)
- return true;
- let iface = hostapd.interfaces[phy];
- if (!iface)
- return false;
- let freq_info = iface_freq_info(iface, config, phy_status);
- return iface.start(freq_info) >= 0;
- }
- function iface_config_macaddr_list(config)
- {
- let macaddr_list = {};
- for (let name, mld in hostapd.data.mld)
- if (mld.macaddr)
- macaddr_list[mld.macaddr] = -1;
- for (let i = 0; i < length(config.bss); i++) {
- let bss = config.bss[i];
- if (!bss.default_macaddr)
- macaddr_list[bss.bssid] = i;
- }
- return macaddr_list;
- }
- function iface_update_supplicant_macaddr(phydev, config)
- {
- let macaddr_list = [];
- for (let name, mld in hostapd.data.mld)
- if (mld.macaddr)
- push(macaddr_list, mld.macaddr);
- for (let bss in config.bss)
- push(macaddr_list, bss.bssid);
- ubus.defer("wpa_supplicant", "phy_set_macaddr_list", {
- phy: phydev.name,
- radio: phydev.radio ?? -1,
- macaddr: macaddr_list
- });
- }
- function __iface_pending_next(pending, state, ret, data)
- {
- let config = pending.config;
- let phydev = pending.phydev;
- let phy = pending.phy;
- let bss = config.bss[0];
- if (pending.defer)
- pending.defer.abort();
- delete pending.defer;
- switch (state) {
- case "init":
- iface_update_supplicant_macaddr(phydev, config);
- return "create_bss";
- case "create_bss":
- if (!bss.mld_ap) {
- let err = phydev.wdev_add(bss.ifname, {
- mode: "ap",
- radio: phydev.radio,
- });
- if (err) {
- hostapd.printf(`Failed to create ${bss.ifname} on phy ${phy}: ${err}`);
- return null;
- }
- }
- pending.call("wpa_supplicant", "phy_status", {
- phy: phydev.phy,
- radio: phydev.radio ?? -1,
- });
- return "check_phy";
- case "check_phy":
- let phy_status = data;
- if (phy_status && phy_status.state == "COMPLETED") {
- if (iface_add(phy, config, phy_status))
- return "done";
- hostapd.printf(`Failed to bring up phy ${phy} ifname=${bss.ifname} with supplicant provided frequency`);
- }
- pending.call("wpa_supplicant", "phy_set_state", {
- phy: phydev.phy,
- radio: phydev.radio ?? -1,
- stop: true
- });
- return "wpas_stopped";
- case "wpas_stopped":
- if (!iface_add(phy, config))
- hostapd.printf(`hostapd.add_iface failed for phy ${phy} ifname=${bss.ifname}`);
- pending.call("wpa_supplicant", "phy_set_state", {
- phy: phydev.phy,
- radio: phydev.radio ?? -1,
- stop: false
- });
- return null;
- case "done":
- default:
- delete hostapd.data.pending_config[phy];
- break;
- }
- }
- function iface_pending_next(ret, data)
- {
- let pending = true;
- let cfg = this;
- while (pending) {
- try {
- this.next_state = __iface_pending_next(cfg, this.next_state, ret, data);
- if (!this.next_state) {
- __iface_pending_next(cfg, "done");
- return;
- }
- } catch(e) {
- hostapd.printf(`Exception: ${e}\n${e.stacktrace[0].context}`);
- return;
- }
- pending = !this.defer;
- }
- }
- function iface_pending_abort()
- {
- this.next_state = "done";
- this.next();
- }
- function iface_pending_ubus_call(obj, method, arg)
- {
- let ubus = hostapd.data.ubus;
- let pending = this;
- this.defer = ubus.defer(obj, method, arg, (ret, data) => { delete pending.defer; pending.next(ret, data) });
- }
- const iface_pending_proto = {
- next: iface_pending_next,
- call: iface_pending_ubus_call,
- abort: iface_pending_abort,
- };
- function iface_pending_init(phydev, config)
- {
- let phy = phydev.name;
- let pending = proto({
- next_state: "init",
- phydev: phydev,
- phy: phy,
- config: config,
- next: iface_pending_next,
- }, iface_pending_proto);
- hostapd.data.pending_config[phy] = pending;
- pending.next();
- }
- function iface_macaddr_init(phydev, config, macaddr_list)
- {
- let macaddr_data = {
- num_global: config.num_global_macaddr ?? 1,
- macaddr_base: config.macaddr_base,
- mbssid: config.mbssid ?? 0,
- };
- return phydev.macaddr_init(macaddr_list, macaddr_data);
- }
- function iface_restart(phydev, config, old_config)
- {
- let phy = phydev.name;
- let pending = hostapd.data.pending_config[phy];
- if (pending)
- pending.abort();
- hostapd.remove_iface(phy);
- iface_remove(old_config);
- iface_remove(config);
- if (!config.bss || !config.bss[0]) {
- hostapd.printf(`No bss for phy ${phy}`);
- return;
- }
- iface_macaddr_init(phydev, config, iface_config_macaddr_list(config));
- for (let i = 0; i < length(config.bss); i++) {
- let bss = config.bss[i];
- if (bss.default_macaddr)
- bss.bssid = phydev.macaddr_next();
- }
- iface_pending_init(phydev, config);
- }
- function array_to_obj(arr, key, start)
- {
- let obj = {};
- start ??= 0;
- for (let i = start; i < length(arr); i++) {
- let cur = arr[i];
- obj[cur[key]] = cur;
- }
- return obj;
- }
- function find_array_idx(arr, key, val)
- {
- for (let i = 0; i < length(arr); i++)
- if (arr[i][key] == val)
- return i;
- return -1;
- }
- function bss_reload_psk(bss, config, old_config)
- {
- if (is_equal(old_config.hash.wpa_psk_file, config.hash.wpa_psk_file))
- return;
- old_config.hash.wpa_psk_file = config.hash.wpa_psk_file;
- if (!is_equal(old_config, config))
- return;
- let ret = bss.ctrl("RELOAD_WPA_PSK");
- ret ??= "failed";
- hostapd.printf(`Reload WPA PSK file for bss ${config.ifname}: ${ret}`);
- }
- function normalize_rxkhs(txt)
- {
- const pat = {
- sep: "\x20",
- mac: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}",
- r0kh_id: "[\x21-\x7e]{1,48}",
- r1kh_id: "([[:xdigit:]]{2}:?){5}[[:xdigit:]]{2}",
- key: "[[:xdigit:]]{32,}",
- r0kh: function() {
- return "r0kh=" + this.mac + this.sep + this.r0kh_id;
- },
- r1kh: function() {
- return "r1kh=" + this.mac + this.sep + this.r1kh_id;
- },
- rxkh: function() {
- return "(" + this.r0kh() + "|" + this.r1kh() + ")" + this.sep + this.key;
- },
- };
- let rxkhs = filter(
- split(txt, "\n"), (line) => match(line, regexp("^" + pat.rxkh() + "$"))
- ) ?? [];
- rxkhs = map(rxkhs, function(k) {
- k = split(k, " ", 3);
- k[0] = lc(k[0]);
- if(match(k[0], /^r1kh/)) {
- k[1] = lc(k[1]);
- }
- if(!k[2] = hostapd.rkh_derive_key(k[2])) {
- return;
- }
- return join(" ", k);
- });
- return join("\n", sort(filter(rxkhs, length)));
- }
- function bss_reload_rxkhs(bss, config, old_config)
- {
- let bss_rxkhs = join("\n", sort(split(bss.ctrl("GET_RXKHS"), "\n")));
- let bss_rxkhs_hash = hostapd.sha1(bss_rxkhs);
- if (is_equal(config.hash.rxkh_file, bss_rxkhs_hash)) {
- if (is_equal(old_config.hash.rxkh_file, config.hash.rxkh_file))
- return;
- }
- old_config.hash.rxkh_file = config.hash.rxkh_file;
- if (!is_equal(old_config, config))
- return;
- let ret = bss.ctrl("RELOAD_RXKHS");
- ret ??= "failed";
- hostapd.printf(`Reload RxKH file for bss ${config.ifname}: ${ret}`);
- }
- function remove_file_fields(config)
- {
- return filter(config, (line) =>
- !match(line, /^\s*$/) &&
- !match(line, /^\s*#/) &&
- !hostapd.data.file_fields[split(line, "=")[0]]
- );
- }
- function bss_remove_file_fields(config)
- {
- let new_cfg = {};
- for (let key in config)
- new_cfg[key] = config[key];
- new_cfg.data = remove_file_fields(new_cfg.data);
- new_cfg.hash = {};
- for (let key in config.hash)
- new_cfg.hash[key] = config.hash[key];
- delete new_cfg.hash.wpa_psk_file;
- delete new_cfg.hash.sae_password_file;
- delete new_cfg.hash.vlan_file;
- return new_cfg;
- }
- function bss_ifindex_list(config)
- {
- config = filter(config, (line) => !!hostapd.data.iface_fields[split(line, "=")[0]]);
- return join(",", map(config, (line) => {
- try {
- let file = "/sys/class/net/" + split(line, "=")[1] + "/ifindex";
- let val = trim(readfile(file));
- return val;
- } catch (e) {
- return "";
- }
- }));
- }
- function bss_config_hash(config)
- {
- return hostapd.sha1(remove_file_fields(config) + bss_ifindex_list(config));
- }
- function bss_find_existing(config, prev_config, prev_hash)
- {
- let hash = bss_config_hash(config.data);
- for (let i = 0; i < length(prev_config.bss); i++) {
- if (!prev_hash[i] || hash != prev_hash[i])
- continue;
- prev_hash[i] = null;
- return i;
- }
- return -1;
- }
- function get_config_bss(name, config, idx)
- {
- if (!config.bss[idx]) {
- hostapd.printf(`Invalid bss index ${idx}`);
- return;
- }
- let ifname = config.bss[idx].ifname;
- if (!ifname) {
- hostapd.printf(`Could not find bss ${config.bss[idx].ifname}`);
- return;
- }
- let if_bss = hostapd.bss[name];
- if (!if_bss) {
- hostapd.printf(`Could not find interface ${name} bss list`);
- return;
- }
- return if_bss[ifname];
- }
- function iface_reload_config(name, phydev, config, old_config)
- {
- let phy = phydev.name;
- if (!old_config || !is_equal(old_config.radio, config.radio))
- return false;
- if (is_equal(old_config.bss, config.bss))
- return true;
- if (hostapd.data.pending_config[name])
- return false;
- if (!old_config.bss || !old_config.bss[0])
- return false;
- let iface = hostapd.interfaces[name];
- let iface_name = old_config.bss[0].ifname;
- if (!iface) {
- hostapd.printf(`Could not find previous interface ${iface_name}`);
- return false;
- }
- let first_bss = get_config_bss(name, old_config, 0);
- if (!first_bss) {
- hostapd.printf(`Could not find bss of previous interface ${iface_name}`);
- return false;
- }
- let macaddr_list = iface_config_macaddr_list(config);
- let bss_list = [];
- let bss_list_cfg = [];
- let prev_bss_hash = [];
- for (let bss in old_config.bss) {
- let hash = bss_config_hash(bss.data);
- push(prev_bss_hash, bss_config_hash(bss.data));
- }
- // Step 1: find (possibly renamed) interfaces with the same config
- // and store them in the new order (with gaps)
- for (let i = 0; i < length(config.bss); i++) {
- let prev;
- // For fullmac devices, the first interface needs to be preserved,
- // since it's treated as the master
- if (!i && phy_is_fullmac(phy)) {
- prev = 0;
- prev_bss_hash[0] = null;
- } else {
- prev = bss_find_existing(config.bss[i], old_config, prev_bss_hash);
- }
- if (prev < 0)
- continue;
- let cur_config = config.bss[i];
- let prev_config = old_config.bss[prev];
- if (prev_config.force_reload) {
- delete prev_config.force_reload;
- continue;
- }
- let prev_bss = get_config_bss(name, old_config, prev);
- if (!prev_bss)
- return false;
- // try to preserve MAC address of this BSS by reassigning another
- // BSS if necessary
- if (cur_config.default_macaddr &&
- !macaddr_list[prev_config.bssid]) {
- macaddr_list[prev_config.bssid] = i;
- cur_config.bssid = prev_config.bssid;
- }
- bss_list[i] = prev_bss;
- bss_list_cfg[i] = old_config.bss[prev];
- }
- if (config.mbssid && !bss_list_cfg[0]) {
- hostapd.printf("First BSS changed with MBSSID enabled");
- return false;
- }
- // Step 2: if none were found, rename and preserve the first one
- if (length(bss_list) == 0) {
- // can't change the bssid of the first bss
- if (config.bss[0].bssid != old_config.bss[0].bssid) {
- if (!config.bss[0].default_macaddr) {
- hostapd.printf(`BSSID of first interface changed: ${lc(old_config.bss[0].bssid)} -> ${lc(config.bss[0].bssid)}`);
- return false;
- }
- config.bss[0].bssid = old_config.bss[0].bssid;
- }
- let prev_bss = get_config_bss(name, old_config, 0);
- if (!prev_bss)
- return false;
- macaddr_list[config.bss[0].bssid] = 0;
- bss_list[0] = prev_bss;
- bss_list_cfg[0] = old_config.bss[0];
- prev_bss_hash[0] = null;
- }
- // Step 3: delete all unused old interfaces
- for (let i = 0; i < length(prev_bss_hash); i++) {
- if (!prev_bss_hash[i])
- continue;
- let prev_bss = get_config_bss(name, old_config, i);
- if (!prev_bss)
- return false;
- let ifname = old_config.bss[i].ifname;
- hostapd.printf(`Remove bss '${ifname}' on phy '${name}'`);
- prev_bss.delete();
- if (!old_config.bss[i].mld_ap)
- wdev_remove(ifname);
- }
- // Step 4: rename preserved interfaces, use temporary name on duplicates
- let rename_list = [];
- for (let i = 0; i < length(bss_list); i++) {
- if (!bss_list[i])
- continue;
- let old_ifname = bss_list_cfg[i].ifname;
- let new_ifname = config.bss[i].ifname;
- if (old_ifname == new_ifname)
- continue;
- if (hostapd.bss[name][new_ifname]) {
- new_ifname = "tmp_" + substr(hostapd.sha1(new_ifname), 0, 8);
- push(rename_list, i);
- }
- hostapd.printf(`Rename bss ${old_ifname} to ${new_ifname}`);
- if (!bss_list[i].rename(new_ifname)) {
- hostapd.printf(`Failed to rename bss ${old_ifname} to ${new_ifname}`);
- return false;
- }
- bss_list_cfg[i].ifname = new_ifname;
- }
- // Step 5: rename interfaces with temporary names
- for (let i in rename_list) {
- let new_ifname = config.bss[i].ifname;
- if (!bss_list[i].rename(new_ifname)) {
- hostapd.printf(`Failed to rename bss to ${new_ifname}`);
- return false;
- }
- bss_list_cfg[i].ifname = new_ifname;
- }
- // Step 6: assign BSSID for newly created interfaces
- macaddr_list = iface_macaddr_init(phydev, config, macaddr_list);
- for (let i = 0; i < length(config.bss); i++) {
- if (bss_list[i])
- continue;
- let bsscfg = config.bss[i];
- let mac_idx = macaddr_list[bsscfg.bssid];
- if (mac_idx < 0)
- macaddr_list[bsscfg.bssid] = i;
- if (mac_idx == i)
- continue;
- // statically assigned bssid of the new interface is in conflict
- // with the bssid of a reused interface. reassign the reused interface
- if (!bsscfg.default_macaddr) {
- // can't update bssid of the first BSS, need to restart
- if (!mac_idx < 0)
- return false;
- bsscfg = config.bss[mac_idx];
- }
- let addr = phydev.macaddr_next(i);
- if (!addr) {
- hostapd.printf(`Failed to generate mac address for phy ${name}`);
- return false;
- }
- bsscfg.bssid = addr;
- }
- let config_inline = iface_gen_config(config);
- // Step 7: fill in the gaps with new interfaces
- for (let i = 0; i < length(config.bss); i++) {
- let ifname = config.bss[i].ifname;
- let bss = bss_list[i];
- if (bss)
- continue;
- hostapd.printf(`Add bss ${ifname} on phy ${name}`);
- bss_list[i] = iface.add_bss(config_inline, i);
- if (!bss_list[i]) {
- hostapd.printf(`Failed to add new bss ${ifname} on phy ${name}`);
- return false;
- }
- }
- // Step 8: update interface bss order
- if (!iface.set_bss_order(bss_list)) {
- hostapd.printf(`Failed to update BSS order on phy '${name}'`);
- return false;
- }
- // Step 9: update config
- for (let i = 0; i < length(config.bss); i++) {
- if (!bss_list_cfg[i])
- continue;
- let ifname = config.bss[i].ifname;
- let bss = bss_list[i];
- if (is_equal(config.bss[i], bss_list_cfg[i]))
- continue;
- if (is_equal(bss_remove_file_fields(config.bss[i]),
- bss_remove_file_fields(bss_list_cfg[i]))) {
- hostapd.printf(`Update config data files for bss ${ifname}`);
- if (bss.set_config(config_inline, i, true) < 0) {
- hostapd.printf(`Could not update config data files for bss ${ifname}`);
- return false;
- } else {
- bss.ctrl("RELOAD_WPA_PSK");
- continue;
- }
- }
- bss_reload_psk(bss, config.bss[i], bss_list_cfg[i]);
- bss_reload_rxkhs(bss, config.bss[i], bss_list_cfg[i]);
- if (is_equal(config.bss[i], bss_list_cfg[i]))
- continue;
- hostapd.printf(`Reload config for bss '${config.bss[0].ifname}' on phy '${name}'`);
- if (bss.set_config(config_inline, i) < 0) {
- hostapd.printf(`Failed to set config for bss ${ifname}`);
- return false;
- }
- }
- return true;
- }
- function bss_check_mld(phydev, iface_name, bss)
- {
- if (!bss.ifname)
- return;
- let mld_data = hostapd.data.mld[bss.ifname];
- if (!mld_data || !mld_data.ifname || !mld_data.macaddr)
- return;
- bss.mld_bssid = mld_data.macaddr;
- mld_data.iface[iface_name] = true;
- if (mld_data.has_wdev)
- return true;
- hostapd.printf(`Create MLD interface ${bss.ifname} on phy ${phydev.name}, radio mask: ${mld_data.radio_mask}`);
- let err = phydev.wdev_add(bss.ifname, {
- mode: "ap",
- macaddr: mld_data.macaddr,
- radio_mask: mld_data.radio_mask,
- });
- wdev_set_up(bss.ifname, true);
- if (err) {
- hostapd.printf(`Failed to create MLD ${bss.ifname} on phy ${phydev.name}: ${err}`);
- delete mld_data.iface[iface_name];
- return;
- }
- mld_data.has_wdev = true;
- return true;
- }
- function iface_check_mld(phydev, name, config)
- {
- phydev = phy_open(phydev.phy);
- for (let mld_name, mld_data in hostapd.data.mld)
- delete mld_data.iface[name];
- for (let i = 0; i < length(config.bss); i++) {
- let bss = config.bss[i];
- if (!bss.mld_ap)
- continue;
- if (!bss_check_mld(phydev, name, bss)) {
- hostapd.printf(`Skip MLD interface ${name} on phy ${phydev.name}`);
- splice(config.bss, i--, 1);
- }
- }
- for (let mld_name, mld_data in hostapd.data.mld) {
- if (length(mld_data.iface) > 0)
- continue;
- hostapd.printf(`Remove MLD interface ${mld_name}`);
- wdev_remove(mld_name);
- delete mld_data.has_wdev;
- }
- }
- function iface_config_remove(name, old_config)
- {
- hostapd.remove_iface(name);
- return iface_remove(old_config);
- }
- function iface_set_config(name, config)
- {
- let old_config = hostapd.data.config[name];
- hostapd.data.config[name] = config;
- let phy = config.phy;
- let phydev = phy_open(phy, config.radio_idx);
- if (!phydev) {
- hostapd.printf(`Failed to open phy ${phy}`);
- return false;
- }
- config.orig_bss = [ ...config.bss ];
- iface_check_mld(phydev, name, config);
- if (!length(config.bss))
- return iface_config_remove(name, old_config);
- try {
- let ret = iface_reload_config(name, phydev, config, old_config);
- if (ret) {
- iface_update_supplicant_macaddr(phydev, config);
- hostapd.printf(`Reloaded settings for phy ${name}`);
- return 0;
- }
- } catch (e) {
- hostapd.printf(`Error reloading config: ${e}\n${e.stacktrace[0].context}`);
- }
- hostapd.printf(`Restart interface for phy ${name}`);
- let ret = iface_restart(phydev, config, old_config);
- return ret;
- }
- function config_add_bss(config, name)
- {
- let bss = {
- ifname: name,
- data: [],
- hash: {}
- };
- push(config.bss, bss);
- return bss;
- }
- function iface_load_config(phy, radio, filename)
- {
- if (radio < 0)
- radio = null;
- let config = {
- phy,
- radio_idx: radio,
- radio: {
- data: []
- },
- bss: [],
- orig_file: filename,
- };
- let f = open(filename, "r");
- if (!f)
- return config;
- let bss;
- let line;
- while ((line = rtrim(f.read("line"), "\n")) != null) {
- let val = split(line, "=", 2);
- if (!val[0])
- continue;
- if (val[0] == "interface") {
- bss = config_add_bss(config, val[1]);
- break;
- }
- if (val[0] == "channel") {
- config.radio.channel = val[1];
- continue;
- }
- if (val[0] == "#num_global_macaddr")
- config[substr(val[0], 1)] = int(val[1]);
- else if (val[0] == "#macaddr_base")
- config[substr(val[0], 1)] = val[1];
- else if (val[0] == "mbssid")
- config[val[0]] = int(val[1]);
- push(config.radio.data, line);
- }
- while ((line = rtrim(f.read("line"), "\n")) != null) {
- if (line == "#default_macaddr")
- bss.default_macaddr = true;
- let val = split(line, "=", 2);
- if (!val[0])
- continue;
- if (val[0] == "bssid") {
- bss.bssid = lc(val[1]);
- continue;
- }
- if (val[0] == "nas_identifier")
- bss.nasid = val[1];
- if (val[0] == "mld_ap")
- bss[val[0]] = int(val[1]);
- if (val[0] == "bss") {
- bss = config_add_bss(config, val[1]);
- continue;
- }
- if (hostapd.data.file_fields[val[0]]) {
- if (val[0] == "rxkh_file") {
- bss.hash[val[0]] = hostapd.sha1(normalize_rxkhs(readfile(val[1])));
- } else {
- bss.hash[val[0]] = hostapd.sha1(readfile(val[1]));
- }
- }
- push(bss.data, line);
- }
- f.close();
- return config;
- }
- function ex_wrap(func) {
- return (req) => {
- try {
- let ret = func(req);
- return ret;
- } catch(e) {
- hostapd.printf(`Exception in ubus function: ${e}\n${e.stacktrace[0].context}`);
- }
- return libubus.STATUS_UNKNOWN_ERROR;
- };
- }
- function phy_name(phy, radio)
- {
- if (!phy)
- return null;
- if (radio != null && radio >= 0)
- phy += "." + radio;
- return phy;
- }
- function bss_config(bss_name) {
- for (let phy, config in hostapd.data.config) {
- if (!config)
- continue;
- for (let bss in config.bss)
- if (bss.ifname == bss_name)
- return [ config, bss ];
- }
- }
- function mld_rename_bss(data, name)
- {
- if (data.ifname == name)
- return true;
- // TODO: handle rename gracefully
- return false;
- }
- function mld_add_bss(name, data, phy_list, i)
- {
- let config = data.config;
- if (!config.phy)
- return;
- wdev_remove(name);
- let phydev = phy_list[config.phy];
- if (!phydev) {
- phydev = phy_open(config.phy, 0);
- if (!phydev)
- return;
- let macaddr_list = {};
- let phy_config = hostapd.data.config[phy_name(config.phy, 0)];
- if (phy_config)
- macaddr_list = iface_config_macaddr_list(phy_config);
- iface_macaddr_init(phydev, data.config, macaddr_list);
- phy_list[config.phy] = phydev;
- }
- data.macaddr = config.macaddr;
- if (!data.macaddr) {
- data.macaddr = phydev.macaddr_next();
- data.default_macaddr = true;
- }
- let radio_mask = 0;
- for (let r in config.radios)
- if (r != null)
- radio_mask |= 1 << r;
- data.radio_mask = radio_mask;
- data.ifname = name;
- }
- function mld_find_matching_config(list, config)
- {
- for (let name, data in list)
- if (is_equal(data.config, config))
- return name;
- }
- function mld_reload_interface(name)
- {
- let config = hostapd.data.config[name];
- if (!config)
- return;
- config = { ...config };
- config.bss = config.orig_bss;
- iface_set_config(name, config);
- }
- function mld_set_config(config)
- {
- let prev_mld = { ...hostapd.data.mld };
- let new_mld = {};
- let phy_list = {};
- let new_config = !length(prev_mld);
- hostapd.printf(`Set MLD config: ${keys(config)}`);
- // find renamed/new interfaces
- for (let name, data in config) {
- let prev = mld_find_matching_config(prev_mld, data);
- if (prev) {
- let data = prev_mld[prev];
- if (mld_rename_bss(data, name)) {
- new_mld[name] = data;
- delete prev_mld[prev];
- continue;
- }
- }
- new_mld[name] = {
- config: data,
- iface: {},
- };
- }
- let reload_iface = {};
- for (let name, data in prev_mld) {
- delete hostapd.data.mld[name];
- if (!data.ifname)
- continue;
- for (let iface, bss_list in hostapd.bss) {
- if (!bss_list[name])
- continue;
- reload_iface[iface] = true;
- }
- }
- for (let name in reload_iface)
- mld_reload_interface(name);
- for (let name, data in prev_mld) {
- if (data.ifname)
- hostapd.printf(`Remove MLD interface ${name}`);
- wdev_remove(name);
- }
- // add new interfaces
- hostapd.data.mld = new_mld;
- for (let name, data in new_mld)
- mld_add_bss(name, data, phy_list);
- if (!new_config)
- return;
- hostapd.printf(`Reload all interfaces`);
- for (let name in hostapd.data.config)
- mld_reload_interface(name);
- }
- let main_obj = {
- reload: {
- args: {
- phy: "",
- radio: 0,
- },
- call: ex_wrap(function(req) {
- let phy_list = req.args.phy ? [ phy_name(req.args.phy, req.args.radio) ] : keys(hostapd.data.config);
- for (let phy_name in phy_list) {
- let phy = hostapd.data.config[phy_name];
- let config = iface_load_config(phy.phy, phy.radio_idx, phy.orig_file);
- iface_set_config(phy_name, config);
- }
- return 0;
- })
- },
- apsta_state: {
- args: {
- phy: "",
- radio: 0,
- up: true,
- frequency: 0,
- sec_chan_offset: 0,
- csa: true,
- csa_count: 0,
- },
- call: ex_wrap(function(req) {
- let phy = phy_name(req.args.phy, req.args.radio);
- if (req.args.up == null || !phy)
- return libubus.STATUS_INVALID_ARGUMENT;
- let config = hostapd.data.config[phy];
- if (!config || !config.bss || !config.bss[0] || !config.bss[0].ifname)
- return 0;
- let iface = hostapd.interfaces[phy];
- if (!iface)
- return 0;
- if (!req.args.up) {
- iface.stop();
- return 0;
- }
- if (!req.args.frequency)
- return libubus.STATUS_INVALID_ARGUMENT;
- let freq_info = iface_freq_info(iface, config, req.args);
- if (!freq_info)
- return libubus.STATUS_UNKNOWN_ERROR;
- let ret;
- if (req.args.csa) {
- freq_info.csa_count = req.args.csa_count ?? 10;
- ret = iface.switch_channel(freq_info);
- } else {
- ret = iface.start(freq_info);
- }
- if (!ret)
- return libubus.STATUS_UNKNOWN_ERROR;
- return 0;
- })
- },
- config_get_macaddr_list: {
- args: {
- phy: "",
- radio: 0,
- },
- call: ex_wrap(function(req) {
- let phy = phy_name(req.args.phy, req.args.radio);
- if (!phy)
- return libubus.STATUS_INVALID_ARGUMENT;
- let ret = {
- macaddr: [],
- };
- let config = hostapd.data.config[phy];
- if (!config)
- return ret;
- ret.macaddr = map(config.bss, (bss) => bss.bssid);
- return ret;
- })
- },
- mld_set: {
- args: {
- config: {}
- },
- call: ex_wrap(function(req) {
- if (!req.args.config)
- return libubus.STATUS_INVALID_ARGUMENT;
- mld_set_config(req.args.config);
- return {
- pid: hostapd.getpid()
- };
- })
- },
- config_reset: {
- args: {
- },
- call: ex_wrap(function(req) {
- for (let name in hostapd.data.config)
- iface_set_config(name);
- mld_set_config({});
- return 0;
- })
- },
- config_set: {
- args: {
- phy: "",
- radio: 0,
- config: "",
- prev_config: "",
- },
- call: ex_wrap(function(req) {
- let phy = req.args.phy;
- let radio = req.args.radio;
- let name = phy_name(phy, radio);
- let file = req.args.config;
- let prev_file = req.args.prev_config;
- if (!phy)
- return libubus.STATUS_INVALID_ARGUMENT;
- if (prev_file && !hostapd.data.config[name]) {
- let config = iface_load_config(phy, radio, prev_file);
- if (config)
- config.radio.data = [];
- hostapd.data.config[name] = config;
- }
- let config = iface_load_config(phy, radio, file);
- hostapd.printf(`Set new config for phy ${name}: ${file}`);
- iface_set_config(name, config);
- if (hostapd.data.auth_obj)
- hostapd.data.auth_obj.notify("reload", { phy, radio });
- return {
- pid: hostapd.getpid()
- };
- })
- },
- config_add: {
- args: {
- iface: "",
- config: "",
- },
- call: ex_wrap(function(req) {
- if (!req.args.iface || !req.args.config)
- return libubus.STATUS_INVALID_ARGUMENT;
- if (hostapd.add_iface(`bss_config=${req.args.iface}:${req.args.config}`) < 0)
- return libubus.STATUS_INVALID_ARGUMENT;
- return {
- pid: hostapd.getpid()
- };
- })
- },
- config_remove: {
- args: {
- iface: ""
- },
- call: ex_wrap(function(req) {
- if (!req.args.iface)
- return libubus.STATUS_INVALID_ARGUMENT;
- hostapd.remove_iface(req.args.iface);
- return 0;
- })
- },
- bss_info: {
- args: {
- iface: ""
- },
- call: ex_wrap(function(req) {
- if (!req.args.iface)
- return libubus.STATUS_INVALID_ARGUMENT;
- let config = bss_config(req.args.iface);
- if (!config)
- return libubus.STATUS_NOT_FOUND;
- let bss = config[1];
- config = config[0];
- let ret = {};
- for (let line in [ ...config.radio.data, ...bss.data ]) {
- let fields = split(line, "=", 2);
- let name = fields[0];
- if (hostapd.data.bss_info_fields[name])
- ret[name] = fields[1];
- }
- return ret;
- })
- },
- };
- hostapd.data.ubus = ubus;
- hostapd.data.obj = ubus.publish("hostapd", main_obj);
- let auth_obj = {};
- hostapd.data.auth_obj = ubus.publish("hostapd-auth", auth_obj);
- hostapd.udebug_set("hostapd", hostapd.data.ubus);
- function bss_event(type, name, data) {
- let ubus = hostapd.data.ubus;
- data ??= {};
- data.name = name;
- hostapd.data.obj.notify(`bss.${type}`, data, null, null, null, -1);
- ubus.call("service", "event", { type: `hostapd.${name}.${type}`, data: {} });
- }
- return {
- shutdown: function() {
- for (let phy in hostapd.data.config)
- iface_set_config(phy);
- hostapd.udebug_set(null);
- hostapd.ubus.disconnect();
- },
- bss_create: function(phy, name, obj) {
- phy = hostapd.data.config[phy];
- if (!phy)
- return;
- if (phy.radio_idx != null && phy.radio_idx >= 0)
- wdev_set_radio_mask(name, 1 << phy.radio_idx);
- },
- bss_add: function(phy, name, obj) {
- bss_event("add", name);
- },
- bss_reload: function(phy, name, obj, reconf) {
- bss_event("reload", name, { reconf: reconf != 0 });
- },
- bss_remove: function(phy, name, obj) {
- bss_event("remove", name);
- },
- sta_auth: function(iface, sta) {
- let msg = { iface, sta };
- let ret = {};
- let data_cb = (type, data) => {
- ret = { ...ret, ...data };
- };
- if (hostapd.data.auth_obj)
- hostapd.data.auth_obj.notify("sta_auth", msg, data_cb, null, null, 1000);
- return ret;
- },
- sta_connected: function(iface, sta, data) {
- let msg = { iface, sta, ...data };
- let ret = {};
- let data_cb = (type, data) => {
- ret = { ...ret, ...data };
- };
- if (hostapd.data.auth_obj)
- hostapd.data.auth_obj.notify("sta_connected", msg, data_cb, null, null, 1000);
- return ret;
- },
- };
|