Files
packages/net/mwan3/files/usr/share/rpcd/ucode/mwan3
Etienne Champetier 8ae00f92b3 mwan3: handle missing interface gracefully
`ubus.call(sprintf('network.interface.%s', ifname), 'status', {})`
returns null if the interface `ifname` doesn't exists (yet).

For pppoe interfaces using `option ipv6 auto`, a virtual interface suffixed `_6`
is automatically created once the connection is established,
but until then it doesn't exists.

Fixes: 6423781254 ("mwan3: reimplement rpcd plugin using ucode")

Signed-off-by: Etienne Champetier <champetier.etienne@gmail.com>
2025-08-03 14:24:52 -04:00

203 lines
4.8 KiB
Plaintext

'use strict';
import { popen, readfile } from 'fs';
import { cursor } from 'uci';
const ubus = require('ubus').connect();
function get_str_raw(iface, property) {
return readfile(sprintf('/var/run/mwan3track/%s/%s', iface, property));
}
function get_str(iface, property) {
return rtrim(get_str_raw(iface, property), '\n');
}
function get_int(iface, property) {
return int(get_str(iface, property));
}
function get_uptime() {
return int(split(readfile('/proc/uptime'), '.', 2)[0]);
}
function get_x_time(uptime, iface, property) {
let t = get_int(iface, property);
if (t > 0) {
t = uptime - t;
}
return t;
}
function ucibool(val) {
switch (val) {
case 'yes':
case 'on':
case 'true':
case 'enabled':
return true;
default:
return !!int(val);
}
}
function get_mwan3track_status(iface, uci_track_ips, procd) {
if (length(uci_track_ips) == 0) {
return 'disabled';
}
if (procd?.[sprintf('track_%s', iface)]?.running) {
const started = get_str(iface, 'STARTED');
switch (started) {
case '0':
return 'paused';
case '1':
return 'active';
default:
return 'down';
}
}
return 'down';
}
const connected_check_cmd = {
'4': 'iptables -t mangle -w -S mwan3_connected_ipv4',
'6': 'ip6tables -t mangle -w -S mwan3_connected_ipv6',
};
const ipset_save_re = regexp('^add mwan3_connected_ipv[46] (.*)\n$');
function get_connected_ips(version) {
const check = popen(connected_check_cmd[version], 'r');
check.read('all');
if (check.close() != 0) {
return [];
}
const ipset = popen(sprintf('ipset -o save list mwan3_connected_ipv%s', version), 'r');
const ips = [];
for (let line = ipset.read('line'); length(line); line = ipset.read('line')) {
const m = match(line, ipset_save_re);
if (length(m) == 2) {
push(ips, m[1]);
}
}
ipset.close();
return ips;
}
const policies_cmd = {
'4': 'iptables -t mangle -w -S',
'6': 'ip6tables -t mangle -w -S'
};
const policies_re = regexp('^-A mwan3_policy_([^ ]+) .*?--comment "([^"]+)"');
function get_policies(version) {
const ipt = popen(policies_cmd[version], 'r');
const policies = {};
for (let line = ipt.read('line'); length(line); line = ipt.read('line')) {
const m = match(line, policies_re);
if (m == null) {
continue;
}
const policy = m[1];
if (!exists(policies, policy)) {
policies[policy] = [];
}
const intfw = split(m[2], ' ', 3);
const weight = int(intfw[1]);
const total = int(intfw[2]);
if (weight >= 0 && total > 0) {
push(policies[policy], {
'interface': intfw[0],
'percent': weight / total * 100,
})
}
}
ipt.close();
return policies;
}
function interfaces_status(request) {
const uci = cursor();
const procd = ubus.call('service', 'list', { 'name': 'mwan3' })?.mwan3?.instances;
const interfaces = {};
uci.foreach('mwan3', 'interface', intf => {
const ifname = intf['.name'];
if (request.args.interface != null && request.args.interface != ifname) {
return;
}
const netstatus = ubus.call(sprintf('network.interface.%s', ifname), 'status', {});
const uptime = get_uptime();
const uci_track_ips = intf['track_ip'];
const track_status = get_mwan3track_status(ifname, uci_track_ips, procd);
const track_ips = [];
for (let ip in uci_track_ips) {
push(track_ips, {
'ip': ip,
'status': get_str(ifname, sprintf('TRACK_%s', ip)) || 'unknown',
'latency': get_int(ifname, sprintf('LATENCY_%s', ip)),
'packetloss': get_int(ifname, sprintf('LOSS_%s', ip)),
});
}
interfaces[ifname] = {
'age': get_x_time(uptime, ifname, 'TIME'),
'online': get_x_time(uptime, ifname, 'ONLINE'),
'offline': get_x_time(uptime, ifname, 'OFFLINE'),
'uptime': netstatus?.uptime || 0,
'score': get_int(ifname, 'SCORE'),
'lost': get_int(ifname, 'LOST'),
'turn': get_int(ifname, 'TURN'),
'status': get_str(ifname, 'STATUS') || 'unknown',
'enabled': ucibool(intf['enabled']),
'running': track_status == 'active',
'tracking': track_status,
'up': netstatus?.up || false,
'track_ip': track_ips,
};
});
return interfaces;
}
const methods = {
status: {
args: {
section: 'section',
interface: 'interface'
},
call: function (request) {
switch (request.args.section) {
case 'connected':
return {
'connected': {
'ipv4': get_connected_ips('4'),
'ipv6': get_connected_ips('6'),
},
};
case 'policies':
return {
'policies': {
'ipv4': get_policies('4'),
'ipv6': get_policies('6'),
},
};
case 'interfaces':
return {
'interfaces': interfaces_status(request),
};
default:
return {
'interfaces': interfaces_status(request),
'connected': {
'ipv4': get_connected_ips('4'),
'ipv6': get_connected_ips('6'),
},
'policies': {
'ipv4': get_policies('4'),
'ipv6': get_policies('6'),
},
};
}
}
}
};
return { 'mwan3': methods };