--- /dev/null
+'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);
+};
--- /dev/null
+'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,
+};