mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 04:33:56 +08:00
102 lines
4.4 KiB
Bash
Executable File
102 lines
4.4 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=dash
|
|
# Per-device byte counters using nftables maps.
|
|
# Works with software flow offload (hook priority -200, before flowtable at -150).
|
|
# Output: JSON array
|
|
# [{"ip":"…","bytes_in":N,"bytes_out":N,"bytes_tcp":-1,"bytes_udp":-1,
|
|
# "src":"nft","degraded":false}]
|
|
#
|
|
# "degraded" is always false here, and that is the entire point of this script:
|
|
# these counters sit ahead of the flowtable, so they keep moving under offload
|
|
# where conntrack freezes. If this path is unavailable, trafficctl-bytes.sh
|
|
# reports degraded:true instead of quietly handing back frozen numbers.
|
|
#
|
|
# The counters here are cumulative since the table was created, unlike the
|
|
# conntrack sums trafficctl-bytes.sh returns, which cover live flows only.
|
|
# "src" lets trafficctl-totals.sh notice the swap and rebaseline instead of
|
|
# accumulating the whole map as a single enormous delta.
|
|
#
|
|
# The protocol split is -1, i.e. "unknown", not 0: a `type ipv4_addr : counter`
|
|
# map is keyed by address alone, so there is nothing here to split TCP from UDP
|
|
# with. Reporting 0 would render as a device that sent no TCP at all.
|
|
|
|
. /usr/local/bin/trafficctl-fw.sh
|
|
|
|
command -v nft >/dev/null 2>&1 || { echo '[]'; exit 0; }
|
|
|
|
# Create table/maps/chain/rules if not already present (idempotent).
|
|
#
|
|
# The gate matches the CURRENT rule orientation, not merely "some rule exists".
|
|
# Until 1.13.x the maps were keyed the other way round (bytes_in by saddr), so
|
|
# a router upgrading from that version already has the chain: a looser check
|
|
# would leave the old, inverted rules in place forever and the fix would only
|
|
# ever reach fresh installs.
|
|
#
|
|
# Direction convention, matching trafficctl-bytes.sh and docs/API.md:
|
|
# bytes_in = traffic toward the device (download) -> keyed by ip daddr
|
|
# bytes_out = traffic from the device (upload) -> keyed by ip saddr
|
|
if ! nft list chain inet trafficctl_mon mon_forward 2>/dev/null | grep -q "bytes_in.*daddr"; then
|
|
# Drop the old table wholesale rather than appending to it — leaving the
|
|
# previous pair of rules in place would double-count every packet. This
|
|
# resets the counters once, on upgrade; they are only used for speed
|
|
# deltas, so a single discarded sample is the whole cost.
|
|
nft delete table inet trafficctl_mon 2>/dev/null
|
|
|
|
nft add table inet trafficctl_mon 2>/dev/null
|
|
nft add map inet trafficctl_mon bytes_in \
|
|
'{ type ipv4_addr : counter; flags dynamic; }' 2>/dev/null
|
|
nft add map inet trafficctl_mon bytes_out \
|
|
'{ type ipv4_addr : counter; flags dynamic; }' 2>/dev/null
|
|
nft add chain inet trafficctl_mon mon_forward \
|
|
'{ type filter hook forward priority -200; policy accept; }' 2>/dev/null
|
|
nft add rule inet trafficctl_mon mon_forward \
|
|
'update @bytes_in { ip daddr counter }' 2>/dev/null
|
|
nft add rule inet trafficctl_mon mon_forward \
|
|
'update @bytes_out { ip saddr counter }' 2>/dev/null
|
|
fi
|
|
|
|
# Dynamic counter maps are unsupported on some kernels ("Not supported").
|
|
# Without this check the maps silently never exist, every rule fails, and the
|
|
# script returns [] forever — per-device speed just stops working.
|
|
if ! nft list map inet trafficctl_mon bytes_in >/dev/null 2>&1; then
|
|
TCTL_FORCE_CONNTRACK=1 exec /usr/local/bin/trafficctl-bytes.sh
|
|
fi
|
|
|
|
IN=$(nft list map inet trafficctl_mon bytes_in 2>/dev/null)
|
|
OUT=$(nft list map inet trafficctl_mon bytes_out 2>/dev/null)
|
|
|
|
# Parse: lines look like "192.168.0.100 : counter packets 584 bytes 892341[,]"
|
|
printf '%s\n__SEP__\n%s\n' "$IN" "$OUT" | awk '
|
|
/^__SEP__$/ { phase = 1; next }
|
|
/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ : counter/ {
|
|
ip = ""; val = 0
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/) ip = $i
|
|
if ($i == "bytes") val = $(i+1) + 0
|
|
}
|
|
if (ip != "") {
|
|
if (phase == 0) in_b[ip] += val
|
|
else out_b[ip] += val
|
|
}
|
|
}
|
|
END {
|
|
printf "["
|
|
n = 0
|
|
for (ip in in_b) {
|
|
if (n > 0) printf ","
|
|
printf "{\"ip\":\"%s\",\"bytes_in\":%.0f,\"bytes_out\":%.0f,\"bytes_tcp\":-1,\"bytes_udp\":-1,\"src\":\"nft\",\"degraded\":false}", \
|
|
ip, in_b[ip], out_b[ip]+0
|
|
n++
|
|
}
|
|
for (ip in out_b) {
|
|
if (!(ip in in_b)) {
|
|
if (n > 0) printf ","
|
|
printf "{\"ip\":\"%s\",\"bytes_in\":0,\"bytes_out\":%.0f,\"bytes_tcp\":-1,\"bytes_udp\":-1,\"src\":\"nft\",\"degraded\":false}", \
|
|
ip, out_b[ip]
|
|
n++
|
|
}
|
|
}
|
|
printf "]\n"
|
|
}
|
|
'
|