From: Felix Fietkau Date: Sat, 2 Aug 2025 14:45:39 +0000 (+0200) Subject: netifd: update to Git HEAD (2025-08-02) X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=96fa769937cfce5a076f4425c4b6dc1fd12a77a4;p=thirdparty%2Fopenwrt.git netifd: update to Git HEAD (2025-08-02) 3a7878065829 system-dummy: add missing vrf functions 471d9d6abb6d CMakeLists.txt: bump minimum required version c3a0255e2150 scripts: fix dummy mode on systems where libubox is in /usr/local 7a3b281230e4 update example mac80211 script and wireless config d9f2dd2614f2 wireless: replace with ucode scripts 74c22601baad wireless: add MLO support to example scripts Signed-off-by: Felix Fietkau --- diff --git a/package/network/config/netifd/Makefile b/package/network/config/netifd/Makefile index 86a2713e813..790619b6a3a 100644 --- a/package/network/config/netifd/Makefile +++ b/package/network/config/netifd/Makefile @@ -5,9 +5,9 @@ PKG_RELEASE:=1 PKG_SOURCE_PROTO:=git PKG_SOURCE_URL=$(PROJECT_GIT)/project/netifd.git -PKG_SOURCE_DATE:=2025-05-23 -PKG_SOURCE_VERSION:=7901e66c5f273bceee8981bc8a0c8b0e60945f60 -PKG_MIRROR_HASH:=8b85ec64e446ae065b1466c520b2d3aae329b6167221e425af903777278f557e +PKG_SOURCE_DATE:=2025-08-02 +PKG_SOURCE_VERSION:=74c22601baad83cc9bc0fddb98f15d7abaa52c67 +PKG_MIRROR_HASH:=3841d17a0e59bab8dbcf0433bbc326f5b7b9b54dbb79b8759c20bdb5f0340227 PKG_MAINTAINER:=Felix Fietkau PKG_LICENSE:=GPL-2.0 diff --git a/package/network/config/netifd/files/lib/netifd/main.uc b/package/network/config/netifd/files/lib/netifd/main.uc new file mode 100644 index 00000000000..6c2e38fd6a0 --- /dev/null +++ b/package/network/config/netifd/files/lib/netifd/main.uc @@ -0,0 +1,80 @@ +import * as uci from "uci"; +import * as uloop from "uloop"; +import * as libubus from "ubus"; +import { access, dirname } from "fs"; + +function ex_handler(e) +{ + netifd.log(netifd.L_WARNING, `Exception: ${e}\n${e.stacktrace[0].context}\n`); +} + +uloop.guard(ex_handler); +libubus.guard(ex_handler); + +let ubus = netifd.ubus = libubus.connect(); +let wireless; + +function uci_ctx() +{ + let savedir = netifd.dummy_mode ? "./tmp" : null; + let ctx = uci.cursor(netifd.config_path, savedir, null, { + strict: false + }); + return ctx; +} + +function config_init() +{ + let ctx = uci_ctx(); + + if (wireless) + wireless.config_init(ctx); +} + +function config_start() +{ + if (wireless) + wireless.config_start(); +} + +function check_interfaces() +{ + if (wireless) + wireless.check_interfaces(); +} + +function hotplug(name, add) +{ + if (wireless) + wireless.hotplug(name, add); +} + +function ex_wrap(cb) +{ + let fn = cb; + return (...args) => { + try { + return fn(...args); + } catch (e) { + netifd.log(netifd.L_WARNING, `${e}\n${e.stacktrace[0].context}`); + } + }; +} + +netifd.cb = { + hotplug: ex_wrap(hotplug), + config_init: ex_wrap(config_init), + config_start: ex_wrap(config_start), + check_interfaces: ex_wrap(check_interfaces), +}; + +const wireless_module = dirname(sourcepath()) + "/wireless.uc"; +if (access(wireless_module, "r")) { + try { + wireless = loadfile(wireless_module)(); + } catch (e) { + netifd.log(netifd.L_WARNING, `Error loading wireless module: ${e}\n${e.stacktrace[0].context}\n`); + } +} else { + netifd.log(netifd.L_WARNING, `Wireless module not found\n`); +} diff --git a/package/network/config/netifd/files/lib/netifd/utils.uc b/package/network/config/netifd/files/lib/netifd/utils.uc new file mode 100644 index 00000000000..84db69d2fae --- /dev/null +++ b/package/network/config/netifd/files/lib/netifd/utils.uc @@ -0,0 +1,131 @@ +'use strict'; + +import { glob, basename, realpath, chdir, mkstemp } from "fs"; + +export const TYPE_ARRAY = 1; +export const TYPE_STRING = 3; +export const TYPE_INT = 5; +export const TYPE_BOOL = 7; + +export function parse_bool(val) +{ + switch (val) { + case "1": + case "true": + return true; + case "0": + case "false": + return false; + } +}; + +export function parse_array(val) +{ + if (type(val) != "array") + val = split(val, /\s+/); + return val; +}; + +function __type_parsers() +{ + let ret = []; + + ret[TYPE_ARRAY] = parse_array; + ret[TYPE_STRING] = function(val) { + return val; + }; + ret[TYPE_INT] = function(val) { + return +val; + }; + ret[TYPE_BOOL] = parse_bool; + + return ret; +} +export const type_parser = __type_parsers(); + +export function handler_load(path, cb) +{ + for (let script in glob(path + "/*.sh")) { + script = basename(script); + + let f = mkstemp(); + let prev_dir = realpath("."); + chdir(path); + system(`./${script} "" "dump" >&${f.fileno()}`); + chdir(prev_dir); + f.seek(); + while (!f.error()) { + let data = trim(f.read("line")); + try { + data = json(data); + } catch (e) { + continue; + } + + if (type(data) != "object") + continue; + + cb(script, data); + } + f.close(); + } +}; + +export function handler_attributes(data, extra, validate) +{ + let ret = { ...extra }; + for (let cur in data) { + let name_data = split(cur[0], ":", 2); + let name = name_data[0]; + ret[name] = cur[1]; + if (validate && name_data[1]) + validate[name] = name_data[1]; + } + return ret; +}; + +export function parse_attribute_list(data, spec) +{ + let ret = {}; + + for (let name, type_id in spec) { + if (!(name in data)) + continue; + + let val = data[name]; + let parser = type_parser[type_id]; + if (parser) + val = parser(val); + ret[name] = val; + } + + return ret; +}; + +export function is_equal(val1, val2) { + let t1 = type(val1); + + if (t1 != type(val2)) + return false; + + if (t1 == "array") { + if (length(val1) != length(val2)) + return false; + + for (let i = 0; i < length(val1); i++) + if (!is_equal(val1[i], val2[i])) + return false; + + return true; + } else if (t1 == "object") { + for (let key in val1) + if (!is_equal(val1[key], val2[key])) + return false; + for (let key in val2) + if (val1[key] == null) + return false; + return true; + } else { + return val1 == val2; + } +}; diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc new file mode 100644 index 00000000000..b0faef1a0a6 --- /dev/null +++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc @@ -0,0 +1,637 @@ +'use strict'; +import * as libubus from "ubus"; +import * as uloop from "uloop"; +import { is_equal } from "./utils.uc"; +import { access } from "fs"; + +const NOTIFY_CMD_UP = 0; +const NOTIFY_CMD_SET_DATA = 1; +const NOTIFY_CMD_PROCESS_ADD = 2; +const NOTIFY_CMD_SET_RETRY = 4; + +const DEFAULT_RETRY = 3; +const DEFAULT_SCRIPT_TIMEOUT = 30 * 1000; + +export const mlo_name = "#mlo"; + +let mlo_wdev; +let wdev_cur; +let wdev_handler = {}; +let wdev_script_task, wdev_script_timeout; +let handler_timer; + +function delete_wdev(name) +{ + delete netifd.wireless.devices[name]; + gc(); +} + +function handle_link(dev, data, up) +{ + let config = data.config; + let bridge_isolate; + let ap = false; + if (dev == data.ifname) + ap = data.type == "vlan" || + (data.type == "vif" && config.mode == "ap"); + + let dev_data = { + isolate: !!config.bridge_isolate, + wireless: true, + wireless_ap: ap, + }; + + if (ap && config.multicast_to_unicast != null) + dev_data.multicast_to_unicast = config.multicast_to_unicast; + + if (data.type == "vif" && config.mode == "ap") { + dev_data.wireless_proxyarp = !!config.proxy_arp; + dev_data.wireless_isolate = !!config.isolate; + } + + if (up) + netifd.device_set(dev, dev_data); + + for (let net in config.network) + netifd.interface_handle_link({ + name: net, + ifname: dev, + vlan: config.network_vlan, + link_ext: true, + up, + }); +} + +function wdev_mlo_fixup(config) +{ + if (!mlo_wdev) + return; + + for (let name, iface in config.interfaces) { + let config = iface.config; + + if (config.mode != "link") + continue; + + let mlo_config = mlo_wdev.handler_data[iface.name]; + if (mlo_config && mlo_config.ifname) + config.ifname = mlo_config.ifname; + } +} + +function wdev_config_init(wdev) +{ + let data = wdev.data; + let config = data.config; + let interfaces = {}; + + let vif_idx = 0; + for (let vif in data.vif) { + let vlan_idx = 0, sta_idx = 0; + let vlans = {}, stas = {}; + + if (wdev.disabled_vifs[vif.name]) + continue; + for (let vlan in vif.vlan) { + let vlan_name = sprintf("%02d", ++vlan_idx); + let cur_vlan = vlans[vlan_name] = { + name: vlan.name, + config: vlan.config, + }; + + if (wdev.disabled_vifs[vif.name]) + continue; + for (let net in vlan.config.network) + if (netifd.interface_get_bridge(net, cur_vlan)) + break; + } + + for (let sta in vif.sta) { + let sta_name = sprintf("%02d", ++sta_idx); + stas[sta_name] = { + name: sta.name, + config: sta.config, + }; + } + + let vif_name = sprintf("%02d", ++vif_idx); + let iface = interfaces[vif_name] = { + name: vif.name, + config: vif.config, + vlans, stas, + }; + + for (let net in vif.config.network) + if (netifd.interface_get_bridge(net, iface)) + break; + } + + wdev.handler_config = { + config, + interfaces, + }; + + let prev = wdev.handler_data; + wdev.handler_data = {}; + + if (prev && prev[wdev.name]) + wdev.handler_data[wdev.name] = prev[wdev.name]; +} + +function wdev_setup_cb(wdev) +{ + if (wdev.state != "setup") + return; + + if (wdev.retry > 0) + wdev.retry--; + else + wdev.retry_setup_failed = true; + + wdev.teardown(); +} + +function wdev_teardown_cb(wdev) +{ + for (let section, data in wdev.handler_data) { + if (data.ifname) + handle_link(data.ifname, data, false); + } + + wdev.handler_data = {}; + wdev.state = "down"; + + if (wdev.delete) { + delete_wdev(wdev.data.name); + return; + } + + wdev.setup(); +} + +function run_handler_cb(wdev, cb) +{ + if (wdev != wdev_cur.wdev) + return; + + wdev.dbg("complete " + wdev_cur.op); + if (wdev_script_timeout) + wdev_script_timeout.cancel(); + wdev_script_timeout = null; + wdev_script_task = null; + wdev_cur = null; + handler_timer.set(1); + cb(wdev); +} + +function run_handler_timeout(wdev, cb) +{ + wdev_script_task.cancel(); + run_handler_cb(wdev, cb); +} + +function handler_sort_fn(a, b) +{ + return wdev_handler[a].time - wdev_handler[b].time +} + +function __run_next_handler_name() +{ + if (wdev_handler[mlo_name]) + return mlo_name; + + return sort(keys(wdev_handler), handler_sort_fn)[0]; +} + +function __run_next_handler() +{ + let name = __run_next_handler_name(); + if (!name) + return; + + wdev_cur = wdev_handler[name]; + delete wdev_handler[name]; + + let wdev = wdev_cur.wdev; + let op = wdev_cur.op; + let cb = wdev_cur.cb; + + wdev.dbg("run " + op); + if (name != mlo_name) + wdev_mlo_fixup(wdev.handler_config); + wdev.handler_config.data = wdev.handler_data[wdev.name]; + wdev_script_task = netifd.process({ + cb: () => run_handler_cb(wdev, cb), + dir: netifd.wireless.path, + argv: [ './' + wdev.script, wdev.data.config.type, op, wdev.name, "" + wdev.handler_config ], + log_prefix: wdev.name, + }); + + if (!wdev_script_task) + return run_handler_cb(wdev, cb); + + wdev_script_timeout = uloop.timer(DEFAULT_SCRIPT_TIMEOUT, + () => run_handler_timeout(wdev, cb) + ); +} + +function run_next_handler() +{ + while (!wdev_cur && length(wdev_handler) > 0) + __run_next_handler(); +} + +function run_handler(wdev, op, cb) +{ + wdev.dbg("queue " + op); + wdev_handler[wdev.name] = { + op, wdev, cb, + time: time() + }; + + run_next_handler(); +} + +function wdev_proc_reset(wdev) +{ + if (wdev.proc_timer) { + wdev.proc_timer.cancel(); + delete wdev.proc_timer; + } + + wdev.procs = []; +} + +function __wdev_proc_check(wdev, proc) +{ + if (netifd.process_check(proc.pid, proc.exe)) + return; + + wdev.dbg(`process ${proc.exe}(${proc.pid}) no longer active`); + wdev.teardown(); + return true; +} + +function wdev_proc_check(wdev) +{ + for (let proc in wdev.procs) + if (__wdev_proc_check(wdev, proc)) + break; +} + +function wdev_proc_add(wdev, data) +{ + if (!data.pid || !data.exe) + return; + + push(wdev.procs, data); + + if (!wdev.proc_timer) + wdev.proc_timer = uloop.interval(1000, () => wdev_proc_check(wdev)); +} + + +function setup() +{ + if (this.state != "up" && this.state != "down") + return; + + this.dbg("setup, state=" + this.state); + if (!this.autostart || this.retry_setup_failed || this.data.config.disabled) + return; + + wdev_proc_reset(this); + delete this.config_change; + this.state = "setup"; + run_handler(this, "setup", wdev_setup_cb); +} + +function teardown() +{ + delete this.cancel_setup; + + this.dbg("teardown, state=" + this.state); + if (this.state == "teardown" || this.state == "down") + return; + + wdev_proc_reset(this); + this.state = "teardown"; + run_handler(this, "teardown", wdev_teardown_cb); +} + +function wdev_update_disabled_vifs(wdev) +{ + let cache = wdev.ifindex_cache; + let prev_disabled = wdev.disabled_vifs; + let disabled = wdev.disabled_vifs = {}; + let changed; + + let vifs = []; + for (let vif in wdev.data.vif) + push(vifs, vif, ...vif.vlan); + + for (let vif in vifs) { + let enabled, ifindex; + + for (let net in vif.config.network) { + let state = netifd.interface_get_enabled(net); + if (!state) + continue; + + if (state.enabled) + enabled = true; + else if (enabled == null) + enabled = false; + if (state.ifindex) + ifindex = state.ifindex; + } + + let name = vif.name; + if (enabled == false) + disabled[wdev] = true; + else if (ifindex != cache[name]) + changed = true; + + if (ifindex) + cache[name] = ifindex; + else + delete cache[name]; + } + + if (changed || !is_equal(prev_disabled, disabled)) + wdev.config_change = true; + + return wdev.config_change; +} + +function wdev_reset(wdev) +{ + wdev.retry = DEFAULT_RETRY; + delete wdev.retry_setup_failed; +} + +function update(data) +{ + if (is_equal(this.data, data)) + return; + + if (data) { + this.data = data; + this.ifindex_cache = {}; + delete this.retry_setup_failed; + delete this.delete; + } + + wdev_reset(this); + this.config_change = true; + this.check(); +} + +function start() +{ + if (this.delete || this.data.config.disabled) + return; + + this.dbg("start, state=" + this.state); + this.autostart = true; + wdev_reset(this); + + if (this.state != "down") + return; + + if (wdev_update_disabled_vifs(this)) + wdev_config_init(this); + this.setup(); +} + +function stop() +{ + this.dbg("stop, state=" + this.state); + this.autostart = false; + + switch (this.state) { + case "setup": + this.cancel_setup = true; + break; + case "up": + this.teardown(); + break; + } +} + +function check() +{ + if (!wdev_update_disabled_vifs(this)) + return; + + wdev_config_init(this); + this.setup(); +} + +function wdev_mark_up(wdev) +{ + wdev.dbg("mark up, state=" + wdev.state); + if (wdev.state != "setup") + return; + + if (wdev.name == mlo_name) + mlo_wdev = wdev; + + if (wdev.config_change) { + wdev.setup(); + return; + } + + for (let section, data in wdev.handler_data) { + if (data.ifname) + handle_link(data.ifname, data, true); + } + wdev.state = "up"; + + return 0; +} + +function wdev_set_data(wdev, vif, vlan, data) +{ + let config = wdev.handler_config; + let cur = wdev; + let cur_type = "device"; + if (!config) + return libubus.STATUS_INVALID_ARGUMENT; + + if (vif) { + cur = vif = config.interfaces[vif]; + if (!vif) + return libubus.STATUS_NOT_FOUND; + cur_type = "vif"; + } + + if (vlan) { + if (!vif) + return libubus.STATUS_INVALID_ARGUMENT; + + cur = vlan = vif.vlans[vlan]; + if (!vlan) + return libubus.STATUS_NOT_FOUND; + + cur_type = "vlan"; + } + + wdev.handler_data[cur.name] = { + ...cur, + ...data, + type: cur_type, + config: cur.config, + }; + + return 0; +} + +function notify(req) +{ + let vif = req.args.interface; + let vlan = req.args.vlan; + let data = req.args.data; + + switch (req.args.command) { + case NOTIFY_CMD_UP: + if (vif || vlan || this.state != "setup") + return libubus.STATUS_INVALID_ARGUMENT; + + return wdev_mark_up(this); + case NOTIFY_CMD_SET_DATA: + return wdev_set_data(this, vif, vlan, data); + case NOTIFY_CMD_PROCESS_ADD: + if (this.state != "setup" && this.state != "up") + return 0; + + wdev_proc_add(this, data); + return 0; + case NOTIFY_CMD_SET_RETRY: + if (data.retry != null) + this.retry = data.retry; + else + this.retry = DEFAULT_RETRY; + return 0; + default: + return libubus.STATUS_INVALID_ARGUMENT; + } +} + +function hotplug(name, add) +{ + let dev = name; + let m = match(name, /(.+)\.sta.+/); + if (m) + name = m[1]; + + for (let section, data in this.handler_data) { + if (data.ifname != name || + data.type != "vif" && data.type != "vlan") + continue; + + handle_link(dev, data, up); + } +} + +function get_status_data(wdev, vif) +{ + let hdata = wdev.handler_data[vif.name]; + let data = { + section: vif.name, + config: vif.config + }; + if (hdata && hdata.ifname) + data.ifname = hdata.ifname; + return data; +} + +function get_status_vlans(wdev, vif) +{ + let vlans = []; + for (let vlan in vif.vlan) + push(vlans, get_status_data(wdev, vlan)); + return vlans; +} + +function get_status_stations(wdev, vif) +{ + let vlans = []; + for (let vlan in vif.sta) + push(vlans, get_status_data(wdev, vlan)); + return vlans; +} + +function status() +{ + let interfaces = []; + for (let vif in this.data.vif) { + let vlans = get_status_vlans(this, vif); + let stations = get_status_stations(this, vif); + let data = get_status_data(this, vif); + push(interfaces, { + ...data, + vlans, stations + }); + } + return { + up: this.state == "up", + pending: this.state == "setup" || this.state == "teardown", + autostart: this.autostart, + disabled: !!this.data.config.disabled, + retry_setup_failed: !!this.retry_setup_failed, + config: this.data.config, + interfaces + }; +} + +function destroy() +{ + this.dbg("destroy"); + this.autostart = false; + this.delete = true; + if (this.state != "down") { + this.stop(); + return; + } + + delete_wdev(this.data.name); +} + +function dbg(msg) +{ + netifd.log(netifd.L_DEBUG, `wireless: ${this.name}: ${msg}\n`); +} + +const wdev_proto = { + update, + destroy, + start, + stop, + setup, + status, + teardown, + check, + notify, + hotplug, + dbg, +}; + +export function new(data, script, driver) +{ + let wdev = { + name: data.name, + script, data, + procs: [], + vifs: {}, + disabled_vifs: {}, + ifindex_cache: {}, + + autostart: true, + state: "down", + }; + wdev_update_disabled_vifs(wdev); + wdev_config_init(wdev); + handler_timer = uloop.timer(1, run_next_handler); + return proto(wdev, wdev_proto); +}; diff --git a/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc new file mode 100644 index 00000000000..de67bd5d94c --- /dev/null +++ b/package/network/config/wifi-scripts/files/lib/netifd/wireless.uc @@ -0,0 +1,450 @@ +'use strict'; + +import * as libubus from "ubus"; +import { realpath } from "fs"; +import { + handler_load, handler_attributes, + parse_attribute_list, parse_bool, parse_array, + TYPE_ARRAY, TYPE_STRING, TYPE_INT, TYPE_BOOL +} from "./utils.uc"; +import * as wdev from "./wireless-device.uc"; + +let ubus = netifd.ubus; +let wireless = netifd.wireless = { + handlers: {}, + devices: {}, + path: realpath(netifd.main_path + "/wireless"), +}; + +function update_config(new_devices) +{ + for (let name, dev in wireless.devices) + if (!new_devices[name]) + dev.destroy(); + + for (let name, dev in new_devices) { + let cur_dev = wireless.devices[name]; + if (cur_dev) { + cur_dev.update(dev); + continue; + } + + let handler = wireless.handlers[dev.config.type]; + cur_dev = wdev.new(dev, handler.script); + if (!cur_dev) + continue; + + wireless.devices[name] = cur_dev; + } +} + +function config_init(uci) +{ + let config = uci.get_all("wireless"); + + let handlers = {}; + let devices = {}; + let vifs = {}; + let mlo_device; + + let sections = { + device: {}, + iface: {}, + vlan: {}, + station: {}, + }; + let radio_idx = {}; + + for (let name, data in config) { + let type = data[".type"]; + if (parse_bool(data.disabled) && type != "wifi-device") + continue; + + if (substr(type, 0, 5) != "wifi-") + continue; + + let list = sections[substr(type, 5)]; + if (list) + list[name] = data; + + if (type == "wifi-iface" && parse_bool(data.mlo)) + mlo_device = true; + } + + if (mlo_device) { + devices[wdev.mlo_name] = { + name: wdev.mlo_name, + config: { + type: "mac80211", + }, + vif: [], + }; + handlers[wdev.mlo_name] = wireless.handlers.mac80211; + } + + for (let name, data in sections.device) { + if (!data.type) + continue; + + let handler = wireless.handlers[data.type]; + if (!handler) + continue; + + if (data.radio != null) + radio_idx[name] = +data.radio; + + let config = parse_attribute_list(data, handler.device); + devices[name] = { + name, + config, + + vif: [], + }; + handlers[name] = handler; + } + + for (let name, data in sections.iface) { + let dev_names = parse_array(data.device); + let mlo_vif = parse_bool(data.mlo); + let radios = map(dev_names, (v) => radio_idx[v]); + radios = filter(radios, (v) => v != null); + if (mlo_vif) + dev_names = [ wdev.mlo_name, ...dev_names ]; + for (let dev_name in dev_names) { + let dev = devices[dev_name]; + if (!dev) + continue; + + let handler = handlers[dev_name]; + if (!handler) + continue; + + let config = parse_attribute_list(data, handler.iface); + if (mlo_vif && dev_name != wdev.mlo_name) + config.mode = "link"; + config.radios = radios; + + let vif = { + name, config, + device: dev_name, + + vlan: [], + sta: [], + }; + push(dev.vif, vif); + + vifs[name] ??= []; + push(vifs[name], vif); + } + } + + for (let name, data in sections.vlan) { + if (!data.iface || !vifs[data.iface]) + continue; + + for (let vif in vifs[data.iface]) { + let dev = devices[vif.device]; + let handler = handlers[vif.device]; + if (!dev || !handler) + continue; + + let config = parse_attribute_list(data, handler.vlan); + + let vlan = { + name, + config + }; + push(vif.vlan, vlan); + } + } + + for (let name, data in sections.station) { + if (!data.iface || !vifs[data.iface]) + continue; + + for (let vif in vifs[data.iface]) { + let dev = devices[vif.device]; + let handler = handlers[vif.device]; + if (!dev || !handler) + continue; + + let config = parse_attribute_list(data, handler.station); + + let sta = { + name, + config + }; + push(vif.sta, sta); + } + } + + let udata = ubus.call({ + object: "service", + method: "get_data", + data: { + type: "wifi-iface" + }, + }); + + for (let svcname, svc in udata) { + for (let typename, data in svc) { + for (let radio, vifs in data) { + for (let name, vif in vifs) { + let devs = vif.device; + if (type(devs) != "array") + devs = [ devs ]; + let config = vif.config; + if (!config) + continue; + for (let device in devs) { + let dev = devices[device]; + if (!dev) + continue; + + let vif_data = { + name, device, config, + vlan: [], + sta: [] + }; + if (vif.vlans) + vif_data.vlans = vif.vlans; + if (vif.stations) + vif_data.sta = vif.stations; + vifs[name] ??= []; + push(vifs[name], vif_data); + push(dev.vif, vif_data); + } + } + } + } + } + + update_config(devices); +} + +function config_start() +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.start(); + +} + +function check_interfaces() +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.check(); +} + +function hotplug(name, add) +{ + for (let name, dev in wireless.devices) + if (dev.autostart) + dev.hotplug(name, add); +} + +const network_config_attr = { + network: TYPE_ARRAY, + network_vlan: TYPE_ARRAY, + bridge_isolate: TYPE_BOOL, + isolate: TYPE_BOOL, + proxy_arp: TYPE_BOOL, + multicast_to_unicast: TYPE_BOOL, +}; + +const default_config_attr = { + device: { + disabled: TYPE_BOOL, + type: TYPE_STRING, + }, + iface: { + ...network_config_attr, + device: TYPE_STRING, + mode: TYPE_STRING, + }, + station: { + iface: TYPE_STRING, + + mac: TYPE_STRING, + key: TYPE_STRING, + vid: TYPE_STRING, + }, + vlan: { + ...network_config_attr, + iface: TYPE_STRING, + name: TYPE_STRING, + vid: TYPE_STRING, + }, +}; + +const wdev_args = { + device: "" +}; + +function wdev_call(req, cb) +{ + let dev = req.args.device; + if (dev) { + dev = wireless.devices[dev]; + if (!dev) + return libubus.STATUS_NOT_FOUND; + + return cb(dev); + } + + for (let name, dev in wireless.devices) { + if (name == wdev.mlo_name) + continue; + cb(dev); + } + + return 0; +} + +function attr_validate(attr_type, validate) +{ + if (validate) + return validate; + switch (attr_type) { + case TYPE_STRING: + return "string"; + case TYPE_ARRAY: + return "list(string)"; + case TYPE_INT: + return "uinteger"; + case TYPE_BOOL: + return "bool"; + } +} + +function get_validate_info(ret, handler) +{ + for (let kind in default_config_attr) { + let cur = ret[kind == "iface" ? "interface" : kind] = {}; + let validate = handler[kind + "_validate"]; + + for (let attr, attr_type in handler[kind]) { + let val = attr_validate(attr_type, validate[attr]); + if (val != null) + cur[attr] = val; + } + } + + return ret; +} + +const ubus_obj = { + up: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.start(); + + return wdev_call(req, (dev) => { + dev.start(); + return 0; + }); + } + }, + down: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.config_change = true; + + return wdev_call(req, (dev) => { + dev.stop(); + return 0; + }); + } + }, + reconf: { + args: wdev_args, + call: function(req) { + let mlo_dev = wireless.devices[wdev.mlo_name]; + if (mlo_dev) + mlo_dev.update(); + + return wdev_call(req, (dev) => { + dev.update(); + return 0; + }); + } + }, + status: { + args: wdev_args, + call: function(req) { + let ret = {}; + let err = wdev_call(req, (dev) => { + ret[dev.data.name] = dev.status(); + return 0; + }); + if (err != 0) + return err; + + return ret; + } + }, + notify: { + args: { + ...wdev_args, + command: 0, + interface: "", + vlan: "", + data: {}, + }, + call: function(req) { + let dev = req.args.device; + if (!dev) + return libubus.STATUS_INVALID_ARGUMENT; + + dev = wireless.devices[dev]; + if (!dev) + return libubus.STATUS_NOT_FOUND; + + return dev.notify(req); + } + }, + get_validate: { + args: wdev_args, + call: function(req) { + let ret = {}; + let err = wdev_call(req, (dev) => { + let dev_type = dev.data.config.type; + let cur = ret[dev.data.name] = {}; + get_validate_info(cur, wireless.handlers[dev_type]); + return 0; + }); + if (err != 0) + return err; + + return ret; + } + }, +}; + + +handler_load(wireless.path, (script, data) => { + if (!data.name) + return; + + let handler = wireless.handlers[data.name] = { + script, + }; + for (let kind, attr in default_config_attr) { + let validate = handler[kind + "_validate"] = {}; + handler[kind] = handler_attributes(data[kind], attr, validate); + } +}); + +wireless.obj = ubus.publish("network.wireless", ubus_obj); + +return { + hotplug, + config_init, + config_start, + check_interfaces, +};