feat: add interface traffic counters as fallback telemetry when conntrack flows unavailable

Add interface_traffic collection in agent to aggregate VM/LXC network counters by vmid/nic with tap/fwln/fwpr/fwbr interface ranking, implement collect_interface_traffic to select best interface per VM NIC and format as flow-like records with rx/tx bytes/packets, add collect_flow_diagnostics to capture conntrack binary path and kernel bridge/netfilter settings for debugging, update workload_insights endpoint
This commit is contained in:
2026-07-09 15:12:31 +02:00
parent dbd7fc6f95
commit 3bfd77a74a
5 changed files with 122 additions and 9 deletions
+16 -4
View File
@@ -32,7 +32,11 @@ export function Nodes() {
function agentFlows(node: Node) {
const flows = node.agent?.last_payload?.flows;
return Array.isArray(flows) ? flows : [];
const interfaceTraffic = node.agent?.last_payload?.interface_traffic;
if (Array.isArray(flows) && flows.length) {
return flows;
}
return Array.isArray(interfaceTraffic) ? interfaceTraffic : [];
}
function agentInterfaces(node: Node) {
@@ -166,13 +170,21 @@ export function Nodes() {
<div className="max-h-48 overflow-auto rounded-md border border-border">
{agentFlows(detailNode).length ? agentFlows(detailNode).slice(0, 50).map((item, index) => {
const flow = item as Record<string, unknown>;
const isInterfaceCounter = flow.protocol === "interface-counter";
return (
<div key={index} className="border-b border-border px-3 py-2 text-xs last:border-0">
<div className="font-medium">{String(flow.source_ip)}:{String(flow.source_port ?? "")} {"->"} {String(flow.destination_ip)}:{String(flow.destination_port ?? "")}</div>
<div className="text-slate-500">{String(flow.protocol ?? "unknown")} · {String(flow.bytes ?? 0)} bytes · {String(flow.packets ?? 0)} packets · {String(flow.state ?? "unknown")}</div>
<div className="font-medium">
{isInterfaceCounter
? `VMID ${String(flow.vmid)} ${String(flow.interface ?? "")}`
: `${String(flow.source_ip)}:${String(flow.source_port ?? "")} -> ${String(flow.destination_ip)}:${String(flow.destination_port ?? "")}`}
</div>
<div className="text-slate-500">
{String(flow.protocol ?? "unknown")} · {String(flow.bytes ?? 0)} bytes · {String(flow.packets ?? 0)} packets · {String(flow.state ?? "unknown")}
</div>
{isInterfaceCounter ? <div className="text-slate-500">rx {String(flow.rx_bytes ?? 0)} · tx {String(flow.tx_bytes ?? 0)}</div> : null}
</div>
);
}) : <div className="p-3 text-xs text-slate-500">No conntrack flows were reported in the last heartbeat.</div>}
}) : <div className="p-3 text-xs text-slate-500">No conntrack flows or interface counters were reported in the last heartbeat.</div>}
</div>
</section>
<section>
+2
View File
@@ -63,7 +63,9 @@ export function Workloads() {
<div key={index} className="rounded-md border border-border p-3">
<div>{String(flow.source)} {String(flow.destination)}</div>
<div className="text-xs text-slate-500">{String(flow.protocol)}:{String(flow.port)} · {String(flow.bytes)} bytes · {String(flow.decision)}</div>
{flow.protocol === "interface-counter" ? <div className="mt-1 text-xs text-slate-500">Interface: {String(flow.interface ?? "unknown")} · rx {String(flow.rx_bytes ?? 0)} · tx {String(flow.tx_bytes ?? 0)}</div> : null}
{Array.isArray(flow.ip_addresses) ? <div className="mt-1 text-xs text-slate-500">IPs: {flow.ip_addresses.join(", ")}</div> : null}
{flow.note ? <div className="mt-1 text-xs text-slate-500">{String(flow.note)}</div> : null}
</div>
)) : <div className="rounded-md border border-border p-3 text-xs text-slate-500">No real traffic telemetry has been collected yet. Install the node agent or enable a flow source to populate this section.</div>}
</div>