|
|
@@ -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);
|
|
|
+};
|