From ab26c76ed6fdbb086f8b48785c32665e39b1ccb3 Mon Sep 17 00:00:00 2001 From: Ricard Nacher Date: Wed, 10 Jun 2026 16:54:07 +0200 Subject: [PATCH] prometheus-node-exporter-ucode: add nat_traffic collector Add a nat_traffic collector for prometheus-node-exporter-ucode, ported from the equivalent Lua collector in prometheus-node-exporter-lua. Reads /proc/net/nf_conntrack and exports per-(src, dst) pair byte totals (both directions summed) as node_nat_traffic gauge metrics. Also includes a ucode-based unit test with a fixture file that can be run locally with: ucode test/nat_traffic.uc Signed-off-by: Ricard Nacher --- utils/prometheus-node-exporter-ucode/Makefile | 3 +- .../files/extra/nat_traffic.uc | 36 +++++++++ utils/prometheus-node-exporter-ucode/test.sh | 9 ++- .../test/fixtures/nf_conntrack | 4 + .../test/nat_traffic.uc | 77 +++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 utils/prometheus-node-exporter-ucode/files/extra/nat_traffic.uc create mode 100644 utils/prometheus-node-exporter-ucode/test/fixtures/nf_conntrack create mode 100644 utils/prometheus-node-exporter-ucode/test/nat_traffic.uc diff --git a/utils/prometheus-node-exporter-ucode/Makefile b/utils/prometheus-node-exporter-ucode/Makefile index 3cef0f63ac..d91b8663b5 100644 --- a/utils/prometheus-node-exporter-ucode/Makefile +++ b/utils/prometheus-node-exporter-ucode/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=prometheus-node-exporter-ucode PKG_VERSION:=2024.02.07 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_MAINTAINER:=Andre Heider PKG_LICENSE:=Apache-2.0 @@ -65,6 +65,7 @@ define Collector endef $(eval $(call Collector,dnsmasq,Dnsmasq collector,@dnsmasq)) +$(eval $(call Collector,nat_traffic,NAT traffic collector,)) $(eval $(call Collector,ltq-dsl,Lantiq/Intel/MaxLinear DSL collector,@ltq-dsl-app)) $(eval $(call Collector,netstat,netstat collector,)) $(eval $(call Collector,openwrt,OpenWrt collector,)) diff --git a/utils/prometheus-node-exporter-ucode/files/extra/nat_traffic.uc b/utils/prometheus-node-exporter-ucode/files/extra/nat_traffic.uc new file mode 100644 index 0000000000..e42924bb97 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/files/extra/nat_traffic.uc @@ -0,0 +1,36 @@ +let f = fs.open("/proc/net/nf_conntrack"); +if (!f) + return false; + +let nat = {}; +let nat_metric = gauge("node_nat_traffic"); + +let line; +while ((line = nextline(f)) != null) { + const fields = wsplit(line); + let src, dst; + let bytes = 0; + + for (let field in fields) { + if (src == null && substr(field, 0, 4) == "src=") + src = substr(field, 4); + else if (dst == null && substr(field, 0, 4) == "dst=") + dst = substr(field, 4); + else if (substr(field, 0, 6) == "bytes=") + bytes += +substr(field, 6); + } + + if (src == null || dst == null) + continue; + + if (nat[src] == null) + nat[src] = {}; + + nat[src][dst] = (nat[src][dst] || 0) + bytes; +} + +f.close(); + +for (let src in nat) + for (let dst in nat[src]) + nat_metric({ src, dst }, nat[src][dst]); diff --git a/utils/prometheus-node-exporter-ucode/test.sh b/utils/prometheus-node-exporter-ucode/test.sh index e72942b8e0..7ba638b012 100755 --- a/utils/prometheus-node-exporter-ucode/test.sh +++ b/utils/prometheus-node-exporter-ucode/test.sh @@ -1,3 +1,10 @@ #!/bin/sh -prometheus-node-exporter-ucode time +case "$1" in +prometheus-node-exporter-ucode-nat_traffic) + [ -f /proc/net/nf_conntrack ] && prometheus-node-exporter-ucode nat_traffic + ;; +*) + prometheus-node-exporter-ucode time + ;; +esac diff --git a/utils/prometheus-node-exporter-ucode/test/fixtures/nf_conntrack b/utils/prometheus-node-exporter-ucode/test/fixtures/nf_conntrack new file mode 100644 index 0000000000..d04e8567b5 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/test/fixtures/nf_conntrack @@ -0,0 +1,4 @@ +ipv4 2 tcp 6 431999 ESTABLISHED src=192.168.1.2 dst=1.2.3.4 sport=49152 dport=443 packets=10 bytes=1234 src=1.2.3.4 dst=192.168.1.2 sport=443 dport=49152 packets=8 bytes=5678 [ASSURED] mark=0 use=2 +ipv4 2 tcp 6 120 TIME_WAIT src=192.168.1.2 dst=1.2.3.4 sport=49153 dport=80 packets=5 bytes=100 src=1.2.3.4 dst=192.168.1.2 sport=80 dport=49153 packets=3 bytes=200 mark=0 use=2 +ipv4 2 tcp 6 431999 ESTABLISHED src=192.168.1.3 dst=1.2.3.4 sport=49154 dport=443 packets=2 bytes=300 src=1.2.3.4 dst=192.168.1.3 sport=443 dport=49154 packets=1 bytes=400 [ASSURED] mark=0 use=2 +ipv4 2 udp 17 30 src=192.168.1.2 dst=8.8.8.8 sport=12345 dport=53 packets=1 bytes=60 src=8.8.8.8 dst=192.168.1.2 sport=53 dport=12345 packets=1 bytes=120 mark=0 use=2 diff --git a/utils/prometheus-node-exporter-ucode/test/nat_traffic.uc b/utils/prometheus-node-exporter-ucode/test/nat_traffic.uc new file mode 100644 index 0000000000..38b1dc7189 --- /dev/null +++ b/utils/prometheus-node-exporter-ucode/test/nat_traffic.uc @@ -0,0 +1,77 @@ +'use strict'; + +import * as real_fs from "fs"; + +const fixture = "./test/fixtures/nf_conntrack"; +const collector_path = "./files/extra/nat_traffic.uc"; + +// Capture metric emissions +let metrics = {}; + +function gauge(name) { + let func; + func = function(labels, value) { + let key = name; + if (labels && length(labels)) { + let parts = []; + for (let k in labels) + push(parts, k + '="' + labels[k] + '"'); + key += "{" + join(",", parts) + "}"; + } + metrics[key] = value; + return func; + }; + return func; +} + +// Redirect /proc/net/nf_conntrack to fixture file +const fs = { + open: function(path) { + if (path == "/proc/net/nf_conntrack") + return real_fs.open(fixture); + return real_fs.open(path); + } +}; + +function wsplit(line) { + return split(line, /\s+/); +} + +function nextline(f) { + return rtrim(f.read("line"), "\n"); +} + +let func; +try { + func = loadfile(collector_path, { strict_declarations: true, raw_mode: true }); +} catch(e) { + die("Failed to load collector: " + e.message + "\n"); +} + +if (call(func, null, { fs, gauge, wsplit, nextline, config: {} }) == false) + die("Collector returned false — is the fixture file missing?\n"); + +// Assertions +let passed = 0; +let failed = 0; + +function assert_eq(key, expected) { + const actual = metrics[key]; + if (actual == expected) { + print("PASS: " + key + " == " + expected + "\n"); + passed++; + } else { + print("FAIL: " + key + ": expected " + expected + ", got " + actual + "\n"); + failed++; + } +} + +// line1 (1234+5678=6912) + line2 (100+200=300) share same src/dst => aggregated to 7212 +assert_eq('node_nat_traffic{src="192.168.1.2",dst="1.2.3.4"}', 7212); +// line3 (300+400=700) +assert_eq('node_nat_traffic{src="192.168.1.3",dst="1.2.3.4"}', 700); +// line4 (60+120=180) +assert_eq('node_nat_traffic{src="192.168.1.2",dst="8.8.8.8"}', 180); + +print("\n" + passed + " passed, " + failed + " failed\n"); +exit(failed > 0 ? 1 : 0);