From 6423781254b9f3e52c6102fb2cbcd9f99f2445a3 Mon Sep 17 00:00:00 2001 From: Etienne Champetier Date: Fri, 27 Jun 2025 19:18:55 -0400 Subject: [PATCH] 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 --- net/mwan3/Makefile | 15 +- net/mwan3/files/usr/libexec/rpcd/mwan3 | 235 --------------------- net/mwan3/files/usr/share/rpcd/ucode/mwan3 | 202 ++++++++++++++++++ 3 files changed, 210 insertions(+), 242 deletions(-) delete mode 100755 net/mwan3/files/usr/libexec/rpcd/mwan3 create mode 100644 net/mwan3/files/usr/share/rpcd/ucode/mwan3 diff --git a/net/mwan3/Makefile b/net/mwan3/Makefile index 1631927a6b..5451063302 100644 --- a/net/mwan3/Makefile +++ b/net/mwan3/Makefile @@ -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 , \ Aaron Goodman 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 @@ -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 \ diff --git a/net/mwan3/files/usr/libexec/rpcd/mwan3 b/net/mwan3/files/usr/libexec/rpcd/mwan3 deleted file mode 100755 index 2ced4bb153..0000000000 --- a/net/mwan3/files/usr/libexec/rpcd/mwan3 +++ /dev/null @@ -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 "$@" diff --git a/net/mwan3/files/usr/share/rpcd/ucode/mwan3 b/net/mwan3/files/usr/share/rpcd/ucode/mwan3 new file mode 100644 index 0000000000..b5c2f4a4cb --- /dev/null +++ b/net/mwan3/files/usr/share/rpcd/ucode/mwan3 @@ -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 }; -- 2.30.2