Files
xiaomi-ax3600/package/network/config/wifi-scripts/files/lib/netifd/wireless-device.uc
Felix Fietkau 96fa769937 netifd: update to Git HEAD (2025-08-02)
3a7878065829 system-dummy: add missing vrf functions
471d9d6abb6d CMakeLists.txt: bump minimum required version
c3a0255e2150 scripts: fix dummy mode on systems where libubox is in /usr/local
7a3b281230e4 update example mac80211 script and wireless config
d9f2dd2614f2 wireless: replace with ucode scripts
74c22601baad wireless: add MLO support to example scripts

Signed-off-by: Felix Fietkau <nbd@nbd.name>
2025-08-02 16:46:31 +02:00

638 lines
11 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 = {
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);
};