Files
wlan-ap/feeds/ucentral/udevstats/files/udevstats.uc
John Crispin 6129f525d5 udevstats: add new package
This package uses eBPF to do traffic accounting ont he WAN port

Fixes: WIFI-12183
Signed-off-by: John Crispin <john@phrozen.org>
2023-01-27 12:18:37 +01:00

250 lines
4.3 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_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: {}
},
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();