mirror of
https://github.com/hzyitc/openwrt-redmi-ax3000.git
synced 2025-12-23 11:53:09 +00:00
When an MLO interface specifies multiple radios and the first radio is disabled, the MLO configuration was never created because the code only attempted to create it when processing the first device in the list (which gets skipped if disabled). Fix by creating the MLO config for the first enabled device instead of only when processing dev_names[0]. Reported-by: Michael-cy Lee (李峻宇) <Michael-cy.Lee@mediatek.com> Signed-off-by: Felix Fietkau <nbd@nbd.name>
555 lines
10 KiB
Ucode
555 lines
10 KiB
Ucode
'use strict';
|
|
|
|
import * as ubus 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 { find_phy } from "wifi.utils";
|
|
import * as wdev from "./wireless-device.uc";
|
|
|
|
let wireless = netifd.wireless = {
|
|
handlers: {},
|
|
devices: {},
|
|
mlo: {},
|
|
path: realpath(netifd.main_path + "/wireless"),
|
|
};
|
|
|
|
function wpad_update_mlo(service, mode)
|
|
{
|
|
let config = {};
|
|
|
|
for (let ifname, data in wireless.mlo) {
|
|
if (data.mode != mode)
|
|
continue;
|
|
|
|
data.phy = find_phy(data.radio_config[0], true);
|
|
if (!data.phy)
|
|
continue;
|
|
|
|
config[ifname] = data;
|
|
}
|
|
|
|
ubus.call({
|
|
object: service,
|
|
method: "mld_set",
|
|
return: "ignore",
|
|
data: { config },
|
|
});
|
|
}
|
|
|
|
function hostapd_update_mlo()
|
|
{
|
|
wpad_update_mlo("hostapd", "ap");
|
|
}
|
|
|
|
function supplicant_update_mlo()
|
|
{
|
|
wpad_update_mlo("wpa_supplicant", "sta");
|
|
}
|
|
|
|
function update_config(new_devices, mlo_vifs)
|
|
{
|
|
wireless.mlo = mlo_vifs;
|
|
hostapd_update_mlo();
|
|
supplicant_update_mlo();
|
|
|
|
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_vifs = {};
|
|
|
|
let sections = {
|
|
device: {},
|
|
iface: {},
|
|
vlan: {},
|
|
station: {},
|
|
};
|
|
let radio_idx = {};
|
|
let vif_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;
|
|
}
|
|
|
|
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);
|
|
let radio_config = map(dev_names, (v) => devices[v].config);
|
|
let ifname;
|
|
let mlo_created = false;
|
|
|
|
for (let dev_name in dev_names) {
|
|
let dev = devices[dev_name];
|
|
if (!dev || dev.config.disabled)
|
|
continue;
|
|
|
|
let handler = handlers[dev_name];
|
|
if (!handler)
|
|
continue;
|
|
|
|
let config = parse_attribute_list(data, handler.iface);
|
|
config.radios = radios;
|
|
|
|
if (mlo_vif && !mlo_created) {
|
|
let mlo_config = { ...config };
|
|
|
|
if (config.wds)
|
|
mlo_config['4addr'] = config.wds;
|
|
mlo_config.radio_config = radio_config;
|
|
ifname = config.ifname;
|
|
if (!ifname) {
|
|
let idx = vif_idx[config.mode] ?? 0;
|
|
vif_idx[config.mode] = idx + 1;
|
|
ifname = config.mode + "-mld" + idx;
|
|
}
|
|
|
|
mlo_vifs[ifname] = mlo_config;
|
|
mlo_created = true;
|
|
}
|
|
|
|
if (ifname)
|
|
config.ifname = ifname;
|
|
if (dev_name != dev_names[0])
|
|
delete config.macaddr;
|
|
if (config.radio_macaddr) {
|
|
let idx = index(dev_names, dev_name);
|
|
let macaddr = idx >= 0 ? config.radio_macaddr[idx] : null;
|
|
if (macaddr)
|
|
config.macaddr = macaddr;
|
|
}
|
|
|
|
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) {
|
|
for (let iface, iface_vifs in vifs) {
|
|
if (data.iface && data.iface != iface)
|
|
continue;
|
|
|
|
for (let vif in iface_vifs) {
|
|
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-device"
|
|
},
|
|
});
|
|
for (let svcname, svc in udata) {
|
|
for (let insname, ins in svc) {
|
|
for (let typename, data in ins) {
|
|
for (let radio, config in data) {
|
|
if (type(config) != "object")
|
|
continue;
|
|
|
|
let dev = devices[radio];
|
|
if (dev) {
|
|
dev.config = { ...dev.config, ...config };
|
|
continue;
|
|
}
|
|
|
|
let handler = wireless.handlers[config.type];
|
|
if (!handler)
|
|
continue;
|
|
|
|
dev = devices[radio] = {
|
|
name,
|
|
config,
|
|
|
|
vif: [],
|
|
};
|
|
handlers[radio] = handler;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
udata = ubus.call({
|
|
object: "service",
|
|
method: "get_data",
|
|
data: {
|
|
type: "wifi-iface"
|
|
},
|
|
});
|
|
|
|
for (let svcname, svc in udata) {
|
|
for (let insname, ins in svc) {
|
|
for (let typename, data in ins) {
|
|
for (let radio, vifs in data) {
|
|
if (type(vifs) != "object")
|
|
continue;
|
|
|
|
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, mlo_vifs);
|
|
}
|
|
|
|
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(ifname, add)
|
|
{
|
|
for (let name, dev in wireless.devices)
|
|
if (dev.autostart)
|
|
dev.hotplug(ifname, 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,
|
|
radio_macaddr: TYPE_ARRAY,
|
|
},
|
|
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 ubus.STATUS_NOT_FOUND;
|
|
|
|
return cb(dev);
|
|
}
|
|
|
|
for (let name, dev in wireless.devices)
|
|
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) {
|
|
hostapd_update_mlo();
|
|
|
|
return wdev_call(req, (dev) => {
|
|
dev.start();
|
|
return 0;
|
|
});
|
|
}
|
|
},
|
|
down: {
|
|
args: wdev_args,
|
|
call: function(req) {
|
|
return wdev_call(req, (dev) => {
|
|
dev.stop();
|
|
return 0;
|
|
});
|
|
}
|
|
},
|
|
retry: {
|
|
args: wdev_args,
|
|
call: function(req) {
|
|
hostapd_update_mlo();
|
|
|
|
return wdev_call(req, (dev) => {
|
|
dev.retry_setup();
|
|
return 0;
|
|
});
|
|
}
|
|
},
|
|
reconf: {
|
|
args: wdev_args,
|
|
call: function(req) {
|
|
hostapd_update_mlo();
|
|
|
|
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 ubus.STATUS_INVALID_ARGUMENT;
|
|
|
|
dev = wireless.devices[dev];
|
|
if (!dev)
|
|
return ubus.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);
|
|
wireless.listener = ubus.listener("ubus.object.add", (event, msg) => {
|
|
if (msg.path == "hostapd")
|
|
hostapd_update_mlo();
|
|
else if (msg.path == "wpa_supplicant")
|
|
supplicant_update_mlo();
|
|
});
|
|
|
|
return {
|
|
hotplug,
|
|
config_init,
|
|
config_start,
|
|
check_interfaces,
|
|
};
|