#!/bin/bash # cira-tunnel-watchdog: drop half-open Intel AMT phone-home (CIRA) tunnels on MeshCentral's MPS. # # Why (servers/pfi-tacticalrmm/README.md): when a host whose AMT shares the network chip power-cycles, AMT opens # a new CIRA tunnel without closing the old one. MeshCentral 1.2.0 keeps both. Its web relay (KVM "HW Connect", # SOL, IDE-R, MeshCommander) can pick the dead one and hang at "Setup..." forever. Its own 90 s idle timeout # does not fire, because its writes to the dead socket keep resetting it. Seen 3 of 3 power events, at two sites # (2026-10-02/03). # # What: every run, any ESTABLISHED socket on the MPS port that has received nothing for MAX_SILENT_MS is # destroyed with `ss -K`. MeshCentral then drops it, and the live tunnel serves. Healthy tunnels hear from their # AMT every ~6-30 s (measured), so the 180 s default leaves a wide margin. A tunnel dropped by mistake comes # back on its own: the AMT policy reconnects every 10 s. # # cira-tunnel-watchdog act (root: ss -K needs CAP_NET_ADMIN) # cira-tunnel-watchdog --dry-run report what would be dropped # cira-tunnel-watchdog --dry-run --from-file F decide from a saved `ss -tnio` capture (tests) set -euo pipefail PORT="${CIRA_PORT:-4433}" MAX_SILENT_MS="${CIRA_MAX_SILENT_MS:-180000}" DRY=0 SRC="" while [ $# -gt 0 ]; do case "$1" in --dry-run) DRY=1 ;; --from-file) SRC="${2:?--from-file needs a path}"; shift ;; *) echo "usage: $0 [--dry-run] [--from-file ]" >&2; exit 2 ;; esac shift done if [ -n "$SRC" ]; then [ "$DRY" = 1 ] || { echo "--from-file is a test mode and needs --dry-run" >&2; exit 2; } out="$(cat "$SRC")" else out="$(ss -tnio state established "( sport = :$PORT )")" fi # `ss -tnio state established` prints a socket line (Recv-Q Send-Q Local Peer) followed by an indented info line. stale="$(printf '%s\n' "$out" | awk -v max="$MAX_SILENT_MS" ' /^[0-9]/ { peer = $4; next } /lastrcv:/ { lr = -1 for (i = 1; i <= NF; i++) if ($i ~ /^lastrcv:/) { split($i, a, ":"); lr = a[2] + 0 } if (peer != "" && lr > max) print peer, lr peer = "" }')" [ -z "$stale" ] && exit 0 while read -r peer lr; do [[ "$peer" =~ ^\[[0-9a-fA-F:.]+\]:[0-9]+$ ]] || { echo "skip unparsable peer [$peer]" >&2; continue; } if [ "$DRY" = 1 ]; then echo "would drop $peer (silent ${lr} ms > ${MAX_SILENT_MS})" else ss -K -tn "( sport = :$PORT ) and dst $peer" >/dev/null logger -t cira-tunnel-watchdog "dropped stale CIRA tunnel $peer (silent ${lr} ms > ${MAX_SILENT_MS})" echo "dropped $peer (silent ${lr} ms)" fi done <<<"$stale"