'use strict'; import * as ubus 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 { find_phy } from "wifi.utils"; import * as wdev from "./wireless-device.uc"; let wireless = netifd.wireless = { handlers: {}, devices: {}, mlo: {}, path: realpath(netifd.main_path + "/wireless"), }; function wpad_update_mlo(service, mode) { let config = {}; for (let ifname, data in wireless.mlo) { if (data.mode != mode) continue; data.phy = find_phy(data.radio_config[0], true); if (!data.phy) continue; config[ifname] = data; } ubus.call({ object: service, method: "mld_set", return: "ignore", data: { config }, }); } function hostapd_update_mlo() { wpad_update_mlo("hostapd", "ap"); } function supplicant_update_mlo() { wpad_update_mlo("wpa_supplicant", "sta"); } function update_config(new_devices, mlo_vifs) { wireless.mlo = mlo_vifs; hostapd_update_mlo(); supplicant_update_mlo(); 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_vifs = {}; let sections = { device: {}, iface: {}, vlan: {}, station: {}, }; let radio_idx = {}; let vif_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; } 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); let radio_config = map(dev_names, (v) => devices[v].config); let ifname; 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); config.radios = radios; if (mlo_vif && dev_name == dev_names[0]) { let mlo_config = { ...config }; mlo_config.radio_config = radio_config; ifname = config.ifname; if (!ifname) { let idx = vif_idx[config.mode] ?? 0; vif_idx[config.mode] = idx + 1; ifname = config.mode + "-mld" + idx; } mlo_vifs[ifname] = mlo_config; } if (ifname) config.ifname = ifname; if (dev_name != dev_names[0]) delete config.macaddr; if (config.radio_macaddr) { let idx = index(dev_names, dev_name); let macaddr = idx >= 0 ? config.radio_macaddr[idx] : null; if (macaddr) config.macaddr = macaddr; } 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) { for (let iface, iface_vifs in vifs) { if (data.iface && data.iface != iface) continue; for (let vif in iface_vifs) { 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-device" }, }); for (let svcname, svc in udata) { for (let insname, ins in svc) { for (let typename, data in ins) { for (let radio, config in data) { if (type(config) != "object") continue; let dev = devices[radio]; if (dev) { dev.config = { ...dev.config, ...config }; continue; } let handler = wireless.handlers[config.type]; if (!handler) continue; dev = devices[radio] = { name, config, vif: [], }; handlers[radio] = handler; } } } } udata = ubus.call({ object: "service", method: "get_data", data: { type: "wifi-iface" }, }); for (let svcname, svc in udata) { for (let insname, ins in svc) { for (let typename, data in ins) { for (let radio, vifs in data) { if (type(vifs) != "object") continue; 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, mlo_vifs); } 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(ifname, add) { for (let name, dev in wireless.devices) if (dev.autostart) dev.hotplug(ifname, 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, radio_macaddr: TYPE_ARRAY, }, 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 ubus.STATUS_NOT_FOUND; return cb(dev); } for (let name, dev in wireless.devices) 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) { hostapd_update_mlo(); return wdev_call(req, (dev) => { dev.start(); return 0; }); } }, down: { args: wdev_args, call: function(req) { return wdev_call(req, (dev) => { dev.stop(); return 0; }); } }, reconf: { args: wdev_args, call: function(req) { hostapd_update_mlo(); 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 ubus.STATUS_INVALID_ARGUMENT; dev = wireless.devices[dev]; if (!dev) return ubus.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); wireless.listener = ubus.listener("ubus.object.add", (event, msg) => { if (msg.path == "hostapd") hostapd_update_mlo(); else if (msg.path == "wpa_supplicant") supplicant_update_mlo(); }); return { hotplug, config_init, config_start, check_interfaces, };