mwan3: reimplement rpcd plugin using ucode

On my "test" router (5 wans, 2 tracking ips per wan), before any rework,
prometheus-node-exporter-lua mwan3 average scraping time was 1230ms
(scraping only the interfaces), after optimizing the shell version,
average time was down to 485ms, with ucode we are now at 41ms.

Signed-off-by: Etienne Champetier <champetier.etienne@gmail.com>
This commit is contained in:
Etienne Champetier
2025-06-27 19:18:55 -04:00
committed by Florian Eckert
parent bfdbacca56
commit 6423781254
3 changed files with 210 additions and 242 deletions

View File

@@ -8,8 +8,8 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=mwan3
PKG_VERSION:=2.11.19
PKG_RELEASE:=5
PKG_VERSION:=2.12.0
PKG_RELEASE:=1
PKG_MAINTAINER:=Florian Eckert <fe@dev.tdt.de>, \
Aaron Goodman <aaronjg@alumni.stanford.edu>
PKG_LICENSE:=GPL-2.0
@@ -28,6 +28,7 @@ define Package/mwan3
+IPV6:ip6tables \
+iptables-mod-conntrack-extra \
+iptables-mod-ipopt \
+rpcd-mod-ucode \
+jshn
TITLE:=Multiwan hotplug script with connection tracking support
MAINTAINER:=Florian Eckert <fe@dev.tdt.de>
@@ -47,7 +48,7 @@ endef
define Package/mwan3/postinst
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ] && [ -x /etc/init.d/rpcd ]; then
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/rpcd restart
fi
exit 0
@@ -55,7 +56,7 @@ endef
define Package/mwan3/postrm
#!/bin/sh
if [ -z "$${IPKG_INSTROOT}" ] && [ -x /etc/init.d/rpcd ]; then
if [ -z "$${IPKG_INSTROOT}" ]; then
/etc/init.d/rpcd restart
fi
exit 0
@@ -91,9 +92,9 @@ define Package/mwan3/install
$(INSTALL_DATA) ./files/lib/mwan3/mwan3.sh \
$(1)/lib/mwan3/
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_BIN) ./files/usr/libexec/rpcd/mwan3 \
$(1)/usr/libexec/rpcd/
$(INSTALL_DIR) $(1)/usr/share/rpcd/ucode/
$(INSTALL_BIN) ./files/usr/share/rpcd/ucode/mwan3 \
$(1)/usr/share/rpcd/ucode/
$(INSTALL_DIR) $(1)/usr/sbin
$(INSTALL_BIN) ./files/usr/sbin/mwan3 \

View File

@@ -1,235 +0,0 @@
#!/bin/sh
. /lib/functions.sh
. /lib/functions/network.sh
. /usr/share/libubox/jshn.sh
. /lib/mwan3/common.sh
report_connected_v4() {
local address
if [ -n "$($IPT4 -S mwan3_connected_ipv4 2> /dev/null)" ]; then
for address in $($IPS -o save list mwan3_connected_ipv4 | grep add | cut -d " " -f 3); do
json_add_string "" "${address}"
done
fi
}
report_connected_v6() {
[ $NO_IPV6 -ne 0 ] && return
local address
if [ -n "$($IPT6 -S mwan3_connected_ipv6 2> /dev/null)" ]; then
for address in $($IPS -o save list mwan3_connected_ipv6 | grep add | cut -d " " -f 3); do
json_add_string "" "${address}"
done
fi
}
report_policies() {
local ipt="$1"
local policy="$2"
local percent total_weight weight iface
total_weight=$($ipt -S $policy | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | head -1 | awk '{print $3}')
for iface in $($ipt -S $policy | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | awk '{print $1}'); do
weight=$($ipt -S $policy | grep -v '.*--comment "out .*" .*$' | cut -s -d'"' -f2 | awk '$1 == "'$iface'"' | awk '{print $2}')
percent=$(($weight*100/$total_weight))
json_add_object
json_add_string interface "$iface"
json_add_int percent "$percent"
json_close_object
done
}
report_policies_v4() {
local policy
for policy in $($IPT4 -S | awk '{print $2}' | grep mwan3_policy_ | sort -u); do
json_add_array "${policy##*mwan3_policy_}"
report_policies "$IPT4" "$policy"
json_close_array
done
}
report_policies_v6() {
[ $NO_IPV6 -ne 0 ] && return
local policy
for policy in $($IPT6 -S | awk '{print $2}' | grep mwan3_policy_ | sort -u); do
json_add_array "${policy##*mwan3_policy_}"
report_policies "$IPT6" "$policy"
json_close_array
done
}
get_age() {
local time_p time_u
iface="$2"
readfile time_p "$MWAN3TRACK_STATUS_DIR/${iface}/TIME"
[ -z "${time_p}" ] || {
get_uptime time_n
export -n "$1=$((time_n-time_p))"
}
}
get_offline_time() {
local time_n time_d iface
iface="$2"
readfile time_d "$MWAN3TRACK_STATUS_DIR/${iface}/OFFLINE"
[ -z "${time_d}" ] || [ "${time_d}" = "0" ] || {
get_uptime time_n
export -n "$1=$((time_n-time_d))"
}
}
get_mwan3_status() {
local iface="${1}"
local iface_select="${2}"
local running="0"
local age=0
local online=0
local offline=0
local enabled time_p time_n time_u time_d status track_status up uptime temp
if [ "${iface}" != "${iface_select}" ] && [ "${iface_select}" != "" ]; then
return
fi
mwan3_get_mwan3track_status track_status "$1"
[ "$track_status" = "active" ] && running="1"
get_age age "$iface"
get_online_time online "$iface"
get_offline_time offline "$iface"
config_get_bool enabled "$iface" enabled 0
if [ -f "$MWAN3TRACK_STATUS_DIR/${iface}/STATUS" ]; then
network_get_uptime uptime "$iface"
[ -n "$uptime" ] && up=1 || up=0
readfile status "$MWAN3TRACK_STATUS_DIR/${iface}/STATUS"
else
uptime=0
up=0
status="unknown"
fi
json_add_object "${iface}"
json_add_int age "$age"
json_add_int online "${online}"
json_add_int offline "${offline}"
json_add_int uptime "${uptime}"
readfile temp "$MWAN3TRACK_STATUS_DIR/${iface}/SCORE"
json_add_int "score" "$temp"
readfile temp "$MWAN3TRACK_STATUS_DIR/${iface}/LOST"
json_add_int "lost" "$temp"
readfile temp "$MWAN3TRACK_STATUS_DIR/${iface}/TURN"
json_add_int "turn" "$temp"
json_add_string "status" "${status}"
json_add_boolean "enabled" "${enabled}"
json_add_boolean "running" "${running}"
json_add_string "tracking" "${track_status}"
json_add_boolean "up" "${up}"
json_add_array "track_ip"
for file in $MWAN3TRACK_STATUS_DIR/${iface}/TRACK_*; do
[ -z "${file#*/TRACK_OUTPUT}" ] && continue
[ -z "${file#*/TRACK_\*}" ] && continue
track="${file#*/TRACK_}"
json_add_object
json_add_string ip "${track}"
readfile temp "${file}"
json_add_string status "$temp"
readfile temp "$MWAN3TRACK_STATUS_DIR/${iface}/LATENCY_${track}"
json_add_int latency "$temp"
readfile temp "$MWAN3TRACK_STATUS_DIR/${iface}/LOSS_${track}"
json_add_int packetloss "$temp"
json_close_object
done
json_close_array
json_close_object
}
main () {
case "$1" in
list)
json_init
json_add_object "status"
json_add_string "section" "x"
json_add_string "interface" "x"
json_add_string "policies" "x"
json_close_object
json_dump
;;
call)
case "$2" in
status)
local section iface
read input;
json_load "$input"
json_get_var section section
json_get_var iface interface
config_load mwan3
json_init
case "$section" in
interfaces)
json_add_object interfaces
config_foreach get_mwan3_status interface "${iface}"
json_close_object
;;
connected)
json_add_object connected
json_add_array ipv4
report_connected_v4
json_close_array
json_add_array ipv6
report_connected_v6
json_close_array
json_close_object
;;
policies)
json_add_object policies
json_add_object ipv4
report_policies_v4
json_close_object
json_add_object ipv6
report_policies_v6
json_close_object
json_close_object
;;
*)
# interfaces
json_add_object interfaces
config_foreach get_mwan3_status interface
json_close_object
# connected
json_add_object connected
json_add_array ipv4
report_connected_v4
json_close_array
json_add_array ipv6
report_connected_v6
json_close_array
json_close_object
# policies
json_add_object policies
json_add_object ipv4
report_policies_v4
json_close_object
json_add_object ipv6
report_policies_v6
json_close_object
json_close_object
;;
esac
json_dump
;;
esac
;;
esac
}
main "$@"

View File

@@ -0,0 +1,202 @@
'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,
'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 };