'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); };