tooling: observe client connection ATTEMPTS; make the build guard reject bad args
openfut-observe.sh answers the one question no server log can: when a gate fails and a service logged nothing, did the client try and fail, or never try? Both look like silence. Two redirector gates were lost to that ambiguity -- "roster server logged nothing" was equally consistent with a broken roster service, a wrong roster URL, and a client that never asked. Built on iptables packet counters because this box has no tcpdump, no conntrack, and no readable kernel log. That last one is verified rather than assumed: an initial LOG-based version installed correctly and its rules matched (counters proved it), but the output went nowhere -- journalctl -k has no entries and dmesg is empty. Counters are also lower volume and record only SYNs, so no payload can be captured even in principle. Validated against the live client, not a loopback stand-in: an initial self-test using this host's own address counted almost nothing, because locally-generated packets never traverse PREROUTING. Against the real remote client it counts 8081 at ~4/min, matching the roster server's own log. Known gap, recorded rather than hidden: the catch-all TOTAL runs well above the sum of the named ports, so the client makes steady background attempts to ports not tracked here. It is present during a working session, so it is not the failure signature, and it is not chased further here. verify-build-identity.sh now rejects an argument that is not a commit hash. Passing the binary path instead of its stamp previously produced a plausible "REFUSING: binary was built from ./target/release/... but HEAD is <sha>", which reads as a real stale-build finding rather than a caller mistake -- and a safeguard that cries wolf is one people learn to route around. Usage error is now exit 2, distinct from a genuine stale build (1) and success (0).
This commit is contained in:
Executable
+140
@@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Passive connection-attempt observer for one client address.
|
||||||
|
#
|
||||||
|
# openfut-observe.sh on <CLIENT_IP>
|
||||||
|
# openfut-observe.sh off
|
||||||
|
# openfut-observe.sh status
|
||||||
|
# openfut-observe.sh mark record the current counters as a baseline
|
||||||
|
# openfut-observe.sh delta attempts since the last mark
|
||||||
|
#
|
||||||
|
# WHY THIS EXISTS
|
||||||
|
#
|
||||||
|
# A live gate can fail in two very different ways that look identical in every
|
||||||
|
# server log: the client tried to connect and could not, or the client never
|
||||||
|
# tried at all. No server log can separate those, because both produce silence.
|
||||||
|
#
|
||||||
|
# Worked example, and the reason this exists: two redirector gates failed with
|
||||||
|
# "An error occurred downloading the FUT Squad Update" while the roster server
|
||||||
|
# logged nothing at all. "Nothing" was consistent with a broken roster service,
|
||||||
|
# a wrong roster URL, and a client that never asked — three very different bugs.
|
||||||
|
#
|
||||||
|
# HOW
|
||||||
|
#
|
||||||
|
# This box has no tcpdump, no conntrack, and no readable kernel log (iptables
|
||||||
|
# LOG rules match but their output goes nowhere — verified, not assumed). What
|
||||||
|
# does work is iptables PACKET COUNTERS, so the observation is built from those:
|
||||||
|
# a dedicated raw-table chain, one counting rule per interesting port, each with
|
||||||
|
# no target so it counts and falls through.
|
||||||
|
#
|
||||||
|
# Only SYNs are counted, so one line per connection attempt, and no payload is
|
||||||
|
# recorded — this cannot see message contents even in principle.
|
||||||
|
#
|
||||||
|
# SAFETY
|
||||||
|
#
|
||||||
|
# A counting rule has no target: it cannot drop, rewrite or delay a packet. All
|
||||||
|
# state lives in one custom chain, so `off` is "unhook, flush, delete" and its
|
||||||
|
# verification re-reads the table rather than trusting the delete's exit code.
|
||||||
|
# No rule value contains a space, which is what made an earlier LOG-based
|
||||||
|
# version impossible to delete by reconstructed spec.
|
||||||
|
set -uo pipefail
|
||||||
|
|
||||||
|
TAG=openfut-observe
|
||||||
|
CHAIN=OPENFUT_OBS
|
||||||
|
TABLE=raw
|
||||||
|
STATE="${OPENFUT_OBSERVE_STATE:-${TMPDIR:-/tmp}/openfut-observe.mark}"
|
||||||
|
|
||||||
|
# Ports worth separating. The final catch-all counts EVERY attempt, so it is a
|
||||||
|
# TOTAL and not a remainder: attempts to an untracked port show up as the gap
|
||||||
|
# between TOTAL and the sum of the named ports, rather than vanishing.
|
||||||
|
PORTS=(42127 42227 42130 8081 8099 8080 8094 9988 8999 4216 80 443 17502)
|
||||||
|
|
||||||
|
die() { echo "observe: $*" >&2; exit 1; }
|
||||||
|
ipt() { sudo iptables -t "$TABLE" "$@"; }
|
||||||
|
chain_exists() { ipt -S "$CHAIN" >/dev/null 2>&1; }
|
||||||
|
hooks() { sudo iptables-save -t "$TABLE" 2>/dev/null | grep -cF -- "--comment $TAG"; }
|
||||||
|
|
||||||
|
cmd_on() {
|
||||||
|
local ip="${1:-}"
|
||||||
|
[[ -n "$ip" ]] || die "usage: openfut-observe.sh on <CLIENT_IP>"
|
||||||
|
chain_exists && die "already on — run 'off' first"
|
||||||
|
|
||||||
|
ipt -N "$CHAIN" || die "could not create $CHAIN"
|
||||||
|
local p
|
||||||
|
for p in "${PORTS[@]}"; do
|
||||||
|
ipt -A "$CHAIN" -p tcp --dport "$p" || { cmd_off >/dev/null; die "rule for $p failed"; }
|
||||||
|
done
|
||||||
|
ipt -A "$CHAIN" -p tcp || { cmd_off >/dev/null; die "catch-all rule failed"; }
|
||||||
|
|
||||||
|
# --syn is SYN without ACK: one match per connection ATTEMPT, retries included.
|
||||||
|
ipt -I PREROUTING -s "$ip" -p tcp --syn -m comment --comment "$TAG" -j "$CHAIN" \
|
||||||
|
|| { cmd_off >/dev/null; die "could not hook $CHAIN into PREROUTING"; }
|
||||||
|
|
||||||
|
[[ "$(hooks)" == "1" ]] || { cmd_off >/dev/null; die "hook not installed"; }
|
||||||
|
rm -f "$STATE"
|
||||||
|
echo "observing $ip: ${#PORTS[@]} ports + catch-all, hooked into $TABLE/PREROUTING"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_off() {
|
||||||
|
local removed=0
|
||||||
|
# Unhook by parsed fields as argv elements, then flush and delete. No value
|
||||||
|
# here contains a space, so this round trip is safe.
|
||||||
|
while read -r ip; do
|
||||||
|
[[ -n "$ip" ]] || continue
|
||||||
|
ipt -D PREROUTING -s "$ip" -p tcp --syn -m comment --comment "$TAG" -j "$CHAIN" \
|
||||||
|
2>/dev/null && removed=$((removed + 1))
|
||||||
|
done < <(sudo iptables-save -t "$TABLE" 2>/dev/null \
|
||||||
|
| grep -F -- "--comment $TAG" \
|
||||||
|
| sed -nE 's/.* -s ([0-9.]+)(\/32)? .*/\1/p')
|
||||||
|
|
||||||
|
chain_exists && { ipt -F "$CHAIN"; ipt -X "$CHAIN"; }
|
||||||
|
|
||||||
|
local left_hooks left_chain
|
||||||
|
left_hooks="$(hooks)"; chain_exists && left_chain=yes || left_chain=no
|
||||||
|
if [[ "$left_hooks" != "0" || "$left_chain" != "no" ]]; then
|
||||||
|
echo "observe: REFUSING to report success — hooks=$left_hooks chain=$left_chain" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rm -f "$STATE"
|
||||||
|
echo "observing off: removed $removed hook(s) and the chain, verified none remain"
|
||||||
|
}
|
||||||
|
|
||||||
|
# "port<TAB>packets", catch-all reported as "other".
|
||||||
|
counters() {
|
||||||
|
ipt -L "$CHAIN" -v -n -x 2>/dev/null | awk '
|
||||||
|
/dpt:/ { for(i=1;i<=NF;i++) if($i ~ /^dpt:/){ sub(/dpt:/,"",$i); print $i "\t" $1 } ; next }
|
||||||
|
/^ *[0-9]+ +[0-9]+ +/ && !/dpt:/ && NR>2 { print "TOTAL\t" $1 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
chain_exists || { echo "INACTIVE (no observe chain)"; return 0; }
|
||||||
|
echo "ACTIVE, hooked for: $(sudo iptables-save -t "$TABLE" | grep -F -- "--comment $TAG" \
|
||||||
|
| sed -nE 's/.* -s ([0-9.]+)(\/32)? .*/\1/p' | tr '\n' ' ')"
|
||||||
|
counters | awk -F'\t' '$2>0 {printf " %-8s %s attempt(s)\n", $1, $2}'
|
||||||
|
counters | awk -F'\t' '$2>0' | grep -q . || echo " (no connection attempts yet)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_mark() {
|
||||||
|
chain_exists || die "not observing"
|
||||||
|
counters > "$STATE" || die "could not write $STATE"
|
||||||
|
echo "baseline recorded ($(wc -l <"$STATE") counters)"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_delta() {
|
||||||
|
chain_exists || die "not observing"
|
||||||
|
[[ -f "$STATE" ]] || die "no baseline — run 'mark' first"
|
||||||
|
join -t$'\t' -a2 -e 0 -o '0,1.2,2.2' <(sort "$STATE") <(counters | sort) \
|
||||||
|
| awk -F'\t' '{ d=$3-$2; if (d>0) printf " %-8s %s attempt(s)\n", $1, d }' \
|
||||||
|
| sort -k2 -rn
|
||||||
|
echo " ---"
|
||||||
|
join -t$'\t' -a2 -e 0 -o '0,1.2,2.2' <(sort "$STATE") <(counters | sort) \
|
||||||
|
| awk -F'\t' '{ if ($3-$2 > 0) n++ } END { print " ports contacted since mark: " n+0 }'
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
on) shift; cmd_on "$@" ;;
|
||||||
|
off) cmd_off ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
mark) cmd_mark ;;
|
||||||
|
delta) cmd_delta ;;
|
||||||
|
*) sed -n '2,9p' "$0" | sed 's/^# \?//'; exit 2 ;;
|
||||||
|
esac
|
||||||
@@ -21,6 +21,16 @@ cd "$(dirname "$(readlink -f "$0")")/.."
|
|||||||
STAMPED="${1:-}"
|
STAMPED="${1:-}"
|
||||||
[[ -n "$STAMPED" ]] || { echo "usage: verify-build-identity.sh <binary-commit>" >&2; exit 2; }
|
[[ -n "$STAMPED" ]] || { echo "usage: verify-build-identity.sh <binary-commit>" >&2; exit 2; }
|
||||||
|
|
||||||
|
# Reject anything that is not a commit BEFORE comparing. Passing the binary
|
||||||
|
# path instead of its stamp otherwise produces a perfectly plausible-looking
|
||||||
|
# REFUSING line, which reads as a real finding rather than a caller mistake —
|
||||||
|
# and a safeguard that cries wolf is one people learn to work around.
|
||||||
|
if [[ ! "$STAMPED" =~ ^[0-9a-f]{7,40}$ ]]; then
|
||||||
|
echo "usage error: expected a commit hash, got '$STAMPED'" >&2
|
||||||
|
echo " (to check a binary: verify-build-identity.sh \"\$(BIN --identity | sed -nE 's/.*commit=([0-9a-f]+).*/\\1/p')\")" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
CRATES=(openfut-protocol-blaze openfut-adapter-fifa17 openfut-host-config
|
CRATES=(openfut-protocol-blaze openfut-adapter-fifa17 openfut-host-config
|
||||||
openfut-blaze-host openfut-redirector-host)
|
openfut-blaze-host openfut-redirector-host)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user