mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 05:53:56 +08:00
139 lines
5.9 KiB
Bash
Executable File
139 lines
5.9 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=dash
|
|
# Per-device byte counters from conntrack (for speed calculation).
|
|
# Output: JSON array
|
|
# [{"ip":"…","bytes_in":N,"bytes_out":N,"bytes_tcp":N,"bytes_udp":N,
|
|
# "src":"ct","degraded":false}]
|
|
#
|
|
# "src" names the counter source. trafficctl-totals.sh accumulates these into
|
|
# lifetime totals and the two sources have unrelated magnitudes (conntrack
|
|
# reports only live flows, the nft maps count since the table was built), so it
|
|
# has to be able to tell a source switch from a burst of traffic.
|
|
#
|
|
# "degraded" says whether the numbers can be believed AT ALL — see below. It is
|
|
# separate from "src" on purpose: src is about magnitude (and drives the
|
|
# rebaseline), degraded is about trust, and conflating them would make a change
|
|
# in trust look like a change of counter and throw away a delta for nothing.
|
|
#
|
|
# bytes_tcp / bytes_udp carry both directions summed, and exist only on the
|
|
# conntrack path — nft counter maps are keyed by address alone and cannot split
|
|
# by protocol, so trafficctl-bytes-nft.sh reports -1 there. -1 rather than 0:
|
|
# "unknown" and "no TCP traffic" must not render as the same number.
|
|
|
|
. /usr/local/bin/trafficctl-fw.sh
|
|
|
|
# Any offload mode (software, hardware, hardware-counter) bypasses conntrack counters
|
|
# for fast-path packets. Use nftables counters at forward priority -200 (before the
|
|
# flowtable at -150) which capture every packet regardless of offload state.
|
|
# Only pure "none" mode has accurate conntrack counters.
|
|
# Modes whose counters ARE synced back to conntrack ("*-counter") keep the
|
|
# conntrack path accurate; only the uncountered ones need the nft fallback.
|
|
# TCTL_FORCE_CONNTRACK is set by that fallback when the kernel lacks dynamic
|
|
# counter maps, so we don't bounce between the two.
|
|
_offload=$(tctl_get_offload_mode)
|
|
_uncountered=0
|
|
case "$_offload" in
|
|
none|*-counter) ;;
|
|
*)
|
|
_uncountered=1
|
|
[ "$TCTL_FW" = "nft" ] && [ -z "$TCTL_FORCE_CONNTRACK" ] && \
|
|
exec /usr/local/bin/trafficctl-bytes-nft.sh
|
|
;;
|
|
esac
|
|
|
|
# Reaching here with uncountered offload means the nft fallback was unavailable
|
|
# and we are about to read counters the forwarding path does not update.
|
|
#
|
|
# Two ways in, and the second is easy to miss:
|
|
# * TCTL_FORCE_CONNTRACK — bytes-nft.sh bounced back because the kernel has
|
|
# no dynamic counter map support. Observed on real hardware.
|
|
# * fw3/iptables — there is no nft fallback to reach in the first place.
|
|
#
|
|
# The numbers that follow are not merely imprecise, they are FROZEN for every
|
|
# offloaded flow: they stop moving while traffic continues. Left unflagged, a
|
|
# lifetime total accumulated from them looks authoritative and simply stalls,
|
|
# which reads as stale statistics rather than as a broken counter — the same
|
|
# shape as the 2 GiB %d freeze (#56), and harder to notice than the tiny live
|
|
# values that prompted #26, because the number is plausible.
|
|
_degraded=false
|
|
[ "$_uncountered" = "1" ] && _degraded=true
|
|
|
|
# All monitored subnets (connected LANs + routed downstream subnets +
|
|
# trafficctl.main.extra_subnets), as awk membership spec.
|
|
MATCH_SPEC=$(tctl_monitored_subnets | awk '{printf "%s%s:%s:%s",(NR>1?" ":""),$2,$3,$4}')
|
|
[ -z "$MATCH_SPEC" ] && { echo '[]'; exit 0; }
|
|
|
|
# Router-owned IPv4 addresses, excluded from the NAT fallback below.
|
|
LOCAL_IPS=$(ip -4 addr show 2>/dev/null | awk '/inet /{split($2,a,"/");print a[1]}' | tr '\n' ' ')
|
|
|
|
cat /proc/net/nf_conntrack 2>/dev/null | awk -v spec="$MATCH_SPEC" -v localips="$LOCAL_IPS" -v degraded="$_degraded" '
|
|
function ip2int(ip, a) {
|
|
split(ip, a, ".")
|
|
return a[1]*16777216 + a[2]*65536 + a[3]*256 + a[4]
|
|
}
|
|
function is_lan(ip, si, k) {
|
|
si = ip2int(ip)
|
|
for (k = 1; k <= ns; k++)
|
|
if (si - (si % blk[k]) == base[k]) return 1
|
|
return 0
|
|
}
|
|
BEGIN {
|
|
printf "["
|
|
ns = split(spec, parts, " ")
|
|
for (k = 1; k <= ns; k++) {
|
|
split(parts[k], kv, ":")
|
|
base[k] = kv[1] + 0; blk[k] = kv[2] + 0
|
|
}
|
|
nl = split(localips, lp, " ")
|
|
for (k = 1; k <= nl; k++) if (lp[k] != "") islocal[lp[k]] = 1
|
|
}
|
|
{
|
|
src=""; osrc=""; rdst=""; nsrc=0; bytes_orig=0; bytes_reply=0; bc=0; proto=""
|
|
for (i=1; i<=NF; i++) {
|
|
# The L4 protocol is a bare word in the header fields ("ipv4 2 tcp 6 …"),
|
|
# before any key=value pair, so it is matched by value like summary.sh.
|
|
if ($i == "tcp") proto="tcp"
|
|
else if ($i == "udp") proto="udp"
|
|
if ($i ~ /^src=/) {
|
|
v = substr($i, 5)
|
|
nsrc++
|
|
if (nsrc == 1) osrc = v
|
|
if (src == "" && is_lan(v)) src = v
|
|
}
|
|
if (nsrc == 2 && rdst == "" && $i ~ /^dst=/) rdst = substr($i, 5)
|
|
if ($i ~ /^bytes=/) {
|
|
v = substr($i, 7) + 0
|
|
bc++
|
|
if (bc == 1) bytes_orig = v
|
|
else if (bc == 2) bytes_reply = v
|
|
}
|
|
}
|
|
# NAT fallback: flow was SNAT/masqueraded here (reply dst != original
|
|
# src), so the original src is a forwarded client even though it is not
|
|
# in any monitored subnet (e.g. behind a downstream router).
|
|
if (src == "" && osrc != "" && rdst != "" && rdst != osrc && \
|
|
osrc ~ /^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$/ && !(osrc in islocal))
|
|
src = osrc
|
|
if (src != "") {
|
|
key = src
|
|
in_total[key] += bytes_reply
|
|
out_total[key] += bytes_orig
|
|
# Both directions, unlike the per-direction sums above: the protocol
|
|
# split answers how much of the traffic for this device was TCP, which
|
|
# is not a question about direction.
|
|
if (proto == "tcp") tcp_total[key] += bytes_orig + bytes_reply
|
|
else if (proto == "udp") udp_total[key] += bytes_orig + bytes_reply
|
|
}
|
|
}
|
|
END {
|
|
n = 0
|
|
for (ip in in_total) {
|
|
if (n > 0) printf ","
|
|
printf "{\"ip\":\"%s\",\"bytes_in\":%.0f,\"bytes_out\":%.0f,\"bytes_tcp\":%.0f,\"bytes_udp\":%.0f,\"src\":\"ct\",\"degraded\":%s}", \
|
|
ip, in_total[ip], out_total[ip], tcp_total[ip]+0, udp_total[ip]+0, degraded
|
|
n++
|
|
}
|
|
printf "]\n"
|
|
}
|
|
'
|