mirror of
https://github.com/immortalwrt/immortalwrt.git
synced 2025-08-07 22:06:25 +08:00
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>
This commit is contained in:
@ -5,9 +5,9 @@ PKG_RELEASE:=1
|
||||
|
||||
PKG_SOURCE_PROTO:=git
|
||||
PKG_SOURCE_URL=$(PROJECT_GIT)/project/netifd.git
|
||||
PKG_SOURCE_DATE:=2025-05-23
|
||||
PKG_SOURCE_VERSION:=7901e66c5f273bceee8981bc8a0c8b0e60945f60
|
||||
PKG_MIRROR_HASH:=8b85ec64e446ae065b1466c520b2d3aae329b6167221e425af903777278f557e
|
||||
PKG_SOURCE_DATE:=2025-08-02
|
||||
PKG_SOURCE_VERSION:=74c22601baad83cc9bc0fddb98f15d7abaa52c67
|
||||
PKG_MIRROR_HASH:=3841d17a0e59bab8dbcf0433bbc326f5b7b9b54dbb79b8759c20bdb5f0340227
|
||||
PKG_MAINTAINER:=Felix Fietkau <nbd@nbd.name>
|
||||
|
||||
PKG_LICENSE:=GPL-2.0
|
||||
|
80
package/network/config/netifd/files/lib/netifd/main.uc
Normal file
80
package/network/config/netifd/files/lib/netifd/main.uc
Normal file
@ -0,0 +1,80 @@
|
||||
import * as uci from "uci";
|
||||
import * as uloop from "uloop";
|
||||
import * as libubus from "ubus";
|
||||
import { access, dirname } from "fs";
|
||||
|
||||
function ex_handler(e)
|
||||
{
|
||||
netifd.log(netifd.L_WARNING, `Exception: ${e}\n${e.stacktrace[0].context}\n`);
|
||||
}
|
||||
|
||||
uloop.guard(ex_handler);
|
||||
libubus.guard(ex_handler);
|
||||
|
||||
let ubus = netifd.ubus = libubus.connect();
|
||||
let wireless;
|
||||
|
||||
function uci_ctx()
|
||||
{
|
||||
let savedir = netifd.dummy_mode ? "./tmp" : null;
|
||||
let ctx = uci.cursor(netifd.config_path, savedir, null, {
|
||||
strict: false
|
||||
});
|
||||
return ctx;
|
||||
}
|
||||
|
||||
function config_init()
|
||||
{
|
||||
let ctx = uci_ctx();
|
||||
|
||||
if (wireless)
|
||||
wireless.config_init(ctx);
|
||||
}
|
||||
|
||||
function config_start()
|
||||
{
|
||||
if (wireless)
|
||||
wireless.config_start();
|
||||
}
|
||||
|
||||
function check_interfaces()
|
||||
{
|
||||
if (wireless)
|
||||
wireless.check_interfaces();
|
||||
}
|
||||
|
||||
function hotplug(name, add)
|
||||
{
|
||||
if (wireless)
|
||||
wireless.hotplug(name, add);
|
||||
}
|
||||
|
||||
function ex_wrap(cb)
|
||||
{
|
||||
let fn = cb;
|
||||
return (...args) => {
|
||||
try {
|
||||
return fn(...args);
|
||||
} catch (e) {
|
||||
netifd.log(netifd.L_WARNING, `${e}\n${e.stacktrace[0].context}`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
netifd.cb = {
|
||||
hotplug: ex_wrap(hotplug),
|
||||
config_init: ex_wrap(config_init),
|
||||
config_start: ex_wrap(config_start),
|
||||
check_interfaces: ex_wrap(check_interfaces),
|
||||
};
|
||||
|
||||
const wireless_module = dirname(sourcepath()) + "/wireless.uc";
|
||||
if (access(wireless_module, "r")) {
|
||||
try {
|
||||
wireless = loadfile(wireless_module)();
|
||||
} catch (e) {
|
||||
netifd.log(netifd.L_WARNING, `Error loading wireless module: ${e}\n${e.stacktrace[0].context}\n`);
|
||||
}
|
||||
} else {
|
||||
netifd.log(netifd.L_WARNING, `Wireless module not found\n`);
|
||||
}
|
131
package/network/config/netifd/files/lib/netifd/utils.uc
Normal file
131
package/network/config/netifd/files/lib/netifd/utils.uc
Normal file
@ -0,0 +1,131 @@
|
||||
'use strict';
|
||||
|
||||
import { glob, basename, realpath, chdir, mkstemp } from "fs";
|
||||
|
||||
export const TYPE_ARRAY = 1;
|
||||
export const TYPE_STRING = 3;
|
||||
export const TYPE_INT = 5;
|
||||
export const TYPE_BOOL = 7;
|
||||
|
||||
export function parse_bool(val)
|
||||
{
|
||||
switch (val) {
|
||||
case "1":
|
||||
case "true":
|
||||
return true;
|
||||
case "0":
|
||||
case "false":
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export function parse_array(val)
|
||||
{
|
||||
if (type(val) != "array")
|
||||
val = split(val, /\s+/);
|
||||
return val;
|
||||
};
|
||||
|
||||
function __type_parsers()
|
||||
{
|
||||
let ret = [];
|
||||
|
||||
ret[TYPE_ARRAY] = parse_array;
|
||||
ret[TYPE_STRING] = function(val) {
|
||||
return val;
|
||||
};
|
||||
ret[TYPE_INT] = function(val) {
|
||||
return +val;
|
||||
};
|
||||
ret[TYPE_BOOL] = parse_bool;
|
||||
|
||||
return ret;
|
||||
}
|
||||
export const type_parser = __type_parsers();
|
||||
|
||||
export function handler_load(path, cb)
|
||||
{
|
||||
for (let script in glob(path + "/*.sh")) {
|
||||
script = basename(script);
|
||||
|
||||
let f = mkstemp();
|
||||
let prev_dir = realpath(".");
|
||||
chdir(path);
|
||||
system(`./${script} "" "dump" >&${f.fileno()}`);
|
||||
chdir(prev_dir);
|
||||
f.seek();
|
||||
while (!f.error()) {
|
||||
let data = trim(f.read("line"));
|
||||
try {
|
||||
data = json(data);
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type(data) != "object")
|
||||
continue;
|
||||
|
||||
cb(script, data);
|
||||
}
|
||||
f.close();
|
||||
}
|
||||
};
|
||||
|
||||
export function handler_attributes(data, extra, validate)
|
||||
{
|
||||
let ret = { ...extra };
|
||||
for (let cur in data) {
|
||||
let name_data = split(cur[0], ":", 2);
|
||||
let name = name_data[0];
|
||||
ret[name] = cur[1];
|
||||
if (validate && name_data[1])
|
||||
validate[name] = name_data[1];
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
export function parse_attribute_list(data, spec)
|
||||
{
|
||||
let ret = {};
|
||||
|
||||
for (let name, type_id in spec) {
|
||||
if (!(name in data))
|
||||
continue;
|
||||
|
||||
let val = data[name];
|
||||
let parser = type_parser[type_id];
|
||||
if (parser)
|
||||
val = parser(val);
|
||||
ret[name] = val;
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
export function is_equal(val1, val2) {
|
||||
let t1 = type(val1);
|
||||
|
||||
if (t1 != type(val2))
|
||||
return false;
|
||||
|
||||
if (t1 == "array") {
|
||||
if (length(val1) != length(val2))
|
||||
return false;
|
||||
|
||||
for (let i = 0; i < length(val1); i++)
|
||||
if (!is_equal(val1[i], val2[i]))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
} else if (t1 == "object") {
|
||||
for (let key in val1)
|
||||
if (!is_equal(val1[key], val2[key]))
|
||||
return false;
|
||||
for (let key in val2)
|
||||
if (val1[key] == null)
|
||||
return false;
|
||||
return true;
|
||||
} else {
|
||||
return val1 == val2;
|
||||
}
|
||||
};
|
@ -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);
|
||||
};
|
450
package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
Normal file
450
package/network/config/wifi-scripts/files/lib/netifd/wireless.uc
Normal file
@ -0,0 +1,450 @@
|
||||
'use strict';
|
||||
|
||||
import * as libubus 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 * as wdev from "./wireless-device.uc";
|
||||
|
||||
let ubus = netifd.ubus;
|
||||
let wireless = netifd.wireless = {
|
||||
handlers: {},
|
||||
devices: {},
|
||||
path: realpath(netifd.main_path + "/wireless"),
|
||||
};
|
||||
|
||||
function update_config(new_devices)
|
||||
{
|
||||
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_device;
|
||||
|
||||
let sections = {
|
||||
device: {},
|
||||
iface: {},
|
||||
vlan: {},
|
||||
station: {},
|
||||
};
|
||||
let radio_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;
|
||||
|
||||
if (type == "wifi-iface" && parse_bool(data.mlo))
|
||||
mlo_device = true;
|
||||
}
|
||||
|
||||
if (mlo_device) {
|
||||
devices[wdev.mlo_name] = {
|
||||
name: wdev.mlo_name,
|
||||
config: {
|
||||
type: "mac80211",
|
||||
},
|
||||
vif: [],
|
||||
};
|
||||
handlers[wdev.mlo_name] = wireless.handlers.mac80211;
|
||||
}
|
||||
|
||||
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);
|
||||
if (mlo_vif)
|
||||
dev_names = [ wdev.mlo_name, ...dev_names ];
|
||||
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);
|
||||
if (mlo_vif && dev_name != wdev.mlo_name)
|
||||
config.mode = "link";
|
||||
config.radios = radios;
|
||||
|
||||
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) {
|
||||
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.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-iface"
|
||||
},
|
||||
});
|
||||
|
||||
for (let svcname, svc in udata) {
|
||||
for (let typename, data in svc) {
|
||||
for (let radio, vifs in data) {
|
||||
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);
|
||||
}
|
||||
|
||||
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(name, add)
|
||||
{
|
||||
for (let name, dev in wireless.devices)
|
||||
if (dev.autostart)
|
||||
dev.hotplug(name, 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,
|
||||
},
|
||||
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 libubus.STATUS_NOT_FOUND;
|
||||
|
||||
return cb(dev);
|
||||
}
|
||||
|
||||
for (let name, dev in wireless.devices) {
|
||||
if (name == wdev.mlo_name)
|
||||
continue;
|
||||
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) {
|
||||
let mlo_dev = wireless.devices[wdev.mlo_name];
|
||||
if (mlo_dev)
|
||||
mlo_dev.start();
|
||||
|
||||
return wdev_call(req, (dev) => {
|
||||
dev.start();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
down: {
|
||||
args: wdev_args,
|
||||
call: function(req) {
|
||||
let mlo_dev = wireless.devices[wdev.mlo_name];
|
||||
if (mlo_dev)
|
||||
mlo_dev.config_change = true;
|
||||
|
||||
return wdev_call(req, (dev) => {
|
||||
dev.stop();
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
},
|
||||
reconf: {
|
||||
args: wdev_args,
|
||||
call: function(req) {
|
||||
let mlo_dev = wireless.devices[wdev.mlo_name];
|
||||
if (mlo_dev)
|
||||
mlo_dev.update();
|
||||
|
||||
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 libubus.STATUS_INVALID_ARGUMENT;
|
||||
|
||||
dev = wireless.devices[dev];
|
||||
if (!dev)
|
||||
return libubus.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);
|
||||
|
||||
return {
|
||||
hotplug,
|
||||
config_init,
|
||||
config_start,
|
||||
check_interfaces,
|
||||
};
|
Reference in New Issue
Block a user