mirror of
https://github.com/Telecominfraproject/wlan-ap.git
synced 2025-10-29 17:42:41 +00:00
This package uses eBPF to do traffic accounting ont he WAN port Fixes: WIFI-12183 Signed-off-by: John Crispin <john@phrozen.org>
250 lines
4.3 KiB
Ucode
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();
|