Files
wlan-ap/feeds/ucentral/udevstats/files/udevstats.uc
Marek Kwaczynski 1ca8f18b9d udevstats: fix adding the same vlans to the config
Add filtering the same vlans before adding to
the vlan config. The issue was detected during
connection many WiFi client using dynamic vlans
on the network.

Fixes: WIFI-13538

Signed-off-by: Marek Kwaczynski <marek@shasta.cloud>
2024-04-12 17:55:36 +02:00

280 lines
5.1 KiB
Ucode

#!/usr/bin/ucode
'use strict';
let bpf = require("bpf");
let struct = require("struct");
let fs = require("fs");
let ubus = require("ubus");
let uloop = require("uloop");
let PRIO_VAL = 0x200;
bpf.set_debug_handler(function(level, msg) { print(`[${level}] ${msg}`); });
let bpf_mod = bpf.open_module("/lib/bpf/udevstats.o", {
"program-type": {
udevstats_in: bpf.BPF_PROG_TYPE_SCHED_CLS,
udevstats_out: bpf.BPF_PROG_TYPE_SCHED_CLS
}
});
assert(bpf_mod, `Could not load BPF module: ${bpf.error()}`);
bpf.set_debug_handler(null);
let map = bpf_mod.get_map("vlans");
assert(map, `Could not find vlan map in BPF module`);
let prog = {
ingress: bpf_mod.get_program("udevstats_in"),
egress: bpf_mod.get_program("udevstats_out")
};
assert(prog.ingress && prog.egress, "Missing BPF program");
function device_list_init() {
return {
ingress: {},
egress: {}
};
}
let old_hooks = device_list_init();
let hooks = device_list_init();
let old_vlans = {};
let vlans = {};
let vlan_config;
function device_update_start() {
old_hooks = hooks;
hooks = device_list_init();
}
function dev_ifindex(name) {
try {
return int(fs.readfile(`/sys/class/net/${name}/ifindex`));
} catch (e) {
return 0
}
}
function device_hook_get(name, tx) {
let ifindex;
let dev;
let type = tx ? "ingress" : "egress";
dev = hooks[type][name];
if (dev)
return dev;
dev = old_hooks[type][name];
if (dev) {
delete old_hooks[type][name];
} else {
dev = {
vlans: {}
};
}
ifindex = dev_ifindex(name);
if (!ifindex)
return null;
if (dev.ifindex != ifindex)
prog[type].tc_attach(name, type, PRIO_VAL);
dev.ifindex = ifindex;
dev.tx = tx;
hooks[type][name] = dev;
return dev;
}
function device_update_end() {
for (let type in [ "ingress", "egress" ])
for (let dev in old_hooks[type])
bpf.tc_detach(dev, type, PRIO_VAL);
old_hooks = device_list_init();
}
function vlan_update_start() {
old_vlans = vlans;
vlans = {};
device_update_start();
}
function vlan_update_end() {
device_update_end();
for (let key in old_vlans)
map.delete(b64dec(key));
}
function vlan_key(ifindex, vid, tx, ad)
{
return struct.pack("IH??", ifindex, vid, tx, ad);
}
function vlan_add(dev, vid, ad)
{
if (!dev.ifindex)
return;
let key = vlan_key(dev.ifindex, vid, dev.tx, ad);
let keystr = b64enc(key);
if (old_vlans[keystr])
delete old_vlans[keystr];
else
map.set(key, struct.pack("QQ", 0, 0));
vlans[keystr] = true;
}
function vlan_config_push(vlan_config, dev, vid)
{
let vlan_found = false;
for (let v in vlan_config[dev]) {
if (v[0] == vid) {
vlan_found = true;
break;
}
}
if (!vlan_found)
push(vlan_config[dev], [ vid, "rx", "tx"]);
}
function vlan_set_config(config)
{
vlan_config = config;
vlan_update_start();
for (let dev in config) {
for (let vlan in config[dev]) {
vlan = [...vlan];
let vid = shift(vlan);
for (let type in vlan) {
let tx;
if (type == "tx")
tx = true;
else if (type == "rx")
tx = false;
else
continue;
let hook = device_hook_get(dev, tx);
vlan_add(hook, vid, false);
}
}
}
vlan_update_end();
}
function vlan_dump_stats()
{
let stats = {};
for (let dev in vlan_config) {
stats[dev] = [];
let vlans = sort(vlan_config[dev], (a, b) => a[0] - b[0]);
for (let vlan in vlans) {
let vlan_stats = {
vid: vlan[0]
};
for (let tx in [ true, false ]) {
let hook = device_hook_get(dev, tx);
if (!hook.ifindex)
continue;
let stats = map.get(vlan_key(hook.ifindex, vlan[0], tx, false));
if (!stats)
continue;
stats = struct.unpack("QQ", stats);
vlan_stats[tx ? "tx" : "rx"] = {
packets: stats[0],
bytes: stats[1]
}
}
push(stats[dev], vlan_stats);
}
}
return stats;
}
function run_service() {
let uctx = ubus.connect();
uctx.publish("udevstats", {
config_set: {
call: function(req) {
if (!req.args.devices)
return ubus.STATUS_INVALID_ARGUMENT;
vlan_set_config(req.args.devices);
return 0;
},
args: {
"devices": {}
}
},
check_devices: {
call: function(req) {
if (vlan_config)
vlan_set_config(vlan_config);
return 0;
},
args: {}
},
add_device: {
call: function(req) {
if (!req.args.device || !req.args.vlan)
return ubus.STATUS_INVALID_ARGUMENT;
if (!vlan_config[req.args.device])
vlan_config[req.args.device] = [];
vlan_config_push(vlan_config, req.args.device, req.args.vlan);
vlan_set_config(vlan_config);
return 0;
},
args: {
"device": "wlan0",
"vlan": 100,
}
},
reset: {
call: function(req) {
let old_config = vlan_config;
vlan_set_config({});
if (old_config)
vlan_set_config(old_config);
return 0;
},
args: {},
},
dump: {
call: function(req) {
return vlan_dump_stats();
},
args: {}
}
});
try {
uloop.run();
} catch(e) {
warn(`Error: ${e}\n${e.stacktrace[0].context}`);
}
vlan_set_config({});
}
uloop.init();
run_service();
uloop.done();