 de447b93e0
			
		
	
	de447b93e0
	
	
	
		
			
			Allow toggling autostart even for disabled devices When switching from enabled to disabled, call teardown instead of setup Signed-off-by: Felix Fietkau <nbd@nbd.name>
		
			
				
	
	
		
			646 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ucode
		
	
	
	
	
	
			
		
		
	
	
			646 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Ucode
		
	
	
	
	
	
| '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 = {
 | |
| 		external: 2,
 | |
| 		check_vlan: false,
 | |
| 		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)
 | |
| 		return;
 | |
| 
 | |
| 	this.dbg("start, state=" + this.state);
 | |
| 	this.autostart = true;
 | |
| 	if (this.data.config.disabled)
 | |
| 		return;
 | |
| 
 | |
| 	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);
 | |
| 	if (this.data.config.disabled)
 | |
| 		this.teardown();
 | |
| 	else
 | |
| 		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, add);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| 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);
 | |
| };
 |