[openwrt/openwrt] netifd: update to Git HEAD (2025-08-02)
LEDE Commits
lede-commits at lists.infradead.org
Sat Aug 2 08:44:53 PDT 2025
nbd pushed a commit to openwrt/openwrt.git, branch main:
https://git.openwrt.org/96fa769937cfce5a076f4425c4b6dc1fd12a77a4
commit 96fa769937cfce5a076f4425c4b6dc1fd12a77a4
Author: Felix Fietkau <nbd at nbd.name>
AuthorDate: Sat Aug 2 16:45:39 2025 +0200
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 <nbd at nbd.name>
---
package/network/config/netifd/Makefile | 6 +-
.../network/config/netifd/files/lib/netifd/main.uc | 80 +++
.../config/netifd/files/lib/netifd/utils.uc | 131 +++++
.../files/lib/netifd/wireless-device.uc | 637 +++++++++++++++++++++
.../wifi-scripts/files/lib/netifd/wireless.uc | 450 +++++++++++++++
5 files changed, 1301 insertions(+), 3 deletions(-)
diff --git a/package/network/config/netifd/Makefile b/package/network/config/netifd/Makefile
index 86a2713e81..790619b6a3 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 <nbd at nbd.name>
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 0000000000..6c2e38fd6a
--- /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 0000000000..84db69d2fa
--- /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 0000000000..b0faef1a0a
--- /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 0000000000..de67bd5d94
--- /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,
+};
More information about the lede-commits
mailing list