Files
bf-infra/docs/tp25-pin-s22.sh
Christoph Emsenhuber ac3a125c82 TP25/TP28: Walk-Harness auf N Geräte, Analyzer generalisiert, AP-Map-Fix
- tp25-walk-messung.sh: halber Refactor geschlossen — Rumpf schleift ueber die
  PH_*-Arrays (pro Geraet ein Sampler + ein Core-Ping, sampler-<name>.csv /
  walk-ping-<name>.csv), PID-Arrays im Cleanup, Archiv-/Echo-Block generisch.
  Lief vorher unter set -u gar nicht (unbound $PHONE_MAC).
- tp25-gehweg-auswertung.py: liest N benannte Geraete per glob; altes 2-Rollen-
  Schema (sampler.csv/walk-ping.csv/walk-ping-iphone.csv/beide-telefone.csv) bleibt
  als Legacy-Fallback erhalten. Beide Pfade getestet.
- tp25-pin-s22.sh: karte() repariert — channel.frequency ist seit dem DFS-Kanalplan
  (2026-09-03, Frequenz-Listen) ein Array; der alte Skalar-Vergleich blankte map,
  wodurch ap-map.txt leer blieb. Fix nimmt bei :typeof=array das erste Element.

Live-Smoke (4 Geraete) gruen: alle erreichbar, sauberes Cleanup, korrekte AP-Map.
Naechster Schritt: In-situ-Walk (nur Zahlen, Commit/Rollback des Kanalplans separat).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-04 12:42:30 +02:00

130 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# TP25 — einen Client per access-list auf EINEN AP festnageln.
#
# Wozu: die Gehweg-Messungen von 08-20/21 zeigen den Sticky-Client (Fund B) und das
# Band-Ping-Pong (Fund C) — aber sie zeigen NICHT, wie gut ein einzelner AP eine Wohnung
# allein ausleuchtet, weil der Client waehrend des Wegs wechselt. Wer die Abdeckung von
# ap1 und ap2 vergleichen will, muss den Wechsel vorher ausschliessen.
#
# Mechanik: am MANAGER (crs326-core) wird der Client auf den Radios des jeweils ANDEREN
# AP mit action=reject abgewiesen. Uebrig bleibt genau ein AP. Die Baender dieses AP
# bleiben bewusst BEIDE offen — gepinnt wird der Standort, nicht das Band.
#
# bash docs/tp25-pin-s22.sh ap2 # nur ap2 erlaubt (ap1 abgewiesen)
# bash docs/tp25-pin-s22.sh ap1 # nur ap1 erlaubt (ap2 abgewiesen)
# bash docs/tp25-pin-s22.sh off # Pin loesen, Normalbetrieb
# bash docs/tp25-pin-s22.sh status # Regeln + aktuelle Anmeldung zeigen
# bash docs/tp25-pin-s22.sh map # cap-wifiN -> apX/Band, LIVE vom Manager
# bash docs/tp25-pin-s22.sh which # auf welchem AP der Pin gerade steht (ap1|ap2|off)
#
# 🔴 Die access-list steht unter KEINER ComplianceRule (Stand 2026-08-22: es gibt
# wifi-configuration/-datapath/-security/-steering/-provisioning/-channel/-cap/-capsman,
# aber keine fuer access-list). Dieser Change erzeugt deshalb KEINEN Drift und wird von
# Nautobot auch nicht bemerkt. Genau darum traegt jede Regel den Kommentar TP25-PIN-S22
# und das Skript raeumt vor jedem Setzen auf: es gibt keine zweite Instanz, die das tut.
# Wenn Fund E (signal-range-Boden) dauerhaft kommt, gehoert diese Luecke geschlossen.
#
# ⚠️ Android randomisiert die MAC je SSID. Die hier gepinnte Adresse gilt fuer
# dirtyvader-main; wechselt das Telefon die SSID oder wuerfelt neu, greift der Pin
# still nicht mehr. Kontrolle ist immer `status`, nie die Annahme.
set -uo pipefail
MAC="${TP25_PIN_MAC:-2A:0D:B0:5D:AD:A7}"
TAG="TP25-PIN-S22"
ROUTER_IP="${TP25_MANAGER_IP:-10.40.99.1}"
# 🔴 Die cap-wifiN-Nummern werden vom Manager DYNAMISCH vergeben, in der Reihenfolge, in der
# sich die CAPs anmelden. Ein fest verdrahtetes Mapping ist damit nur so lange richtig, bis
# ein AP einmal neu bootet oder umgesteckt wird -- und es faellt still falsch aus, weil die
# Zahlen weiter existieren. (Genau so standen sie am 2026-08-22 in docs/tp6-marker/server.py
# und docs/tp25-gehweg-auswertung.py verkehrt herum: Baender richtig, ap1/ap2 vertauscht.)
# Deshalb wird hier NICHTS gecacht -- die Zuordnung kommt bei jedem Aufruf vom Geraet.
ZIEL="${1:-status}"
PW="$(grep -m1 '^CRS326_CORE_PASS=' ~/.secrets/credentials.env | cut -d= -f2- | sed 's/^["'"'"']//; s/["'"'"']$//')"
[[ -n "$PW" ]] || { echo "CRS326_CORE_PASS nicht in ~/.secrets/credentials.env gefunden" >&2; exit 1; }
# 🔴 sshpass -e statt -p: mit -p steht das Router-Passwort in der Kommandozeile und damit
# in `ps` und in jedem Prozess-Trace. Am 2026-08-22 ist es genau so im Klartext in einen
# Sitzungsmitschnitt geraten. Die sed-Maskierung der Ausgabe bleibt als zweite Schicht.
ros() {
SSHPASS="$PW" sshpass -e ssh -o StrictHostKeyChecking=no -o ConnectTimeout=8 \
"admin@${ROUTER_IP}" "$1" 2>&1 | sed "s/${PW//\//\\/}/***/g"
}
# cap-wifiN -> "apX Band" -- live, ohne Cache. Ausgabe je Zeile: cap-wifi3=ap1/5GHz
# 🔴 channel.frequency ist seit dem DFS-Kanalplan (2026-09-03, Frequenz-Listen 5260,5280 /
# 5300,5320) fuer die 5G-Radios ein ARRAY, kein Skalar. Die alte Zeile verglich das Array
# direkt mit 3000 und lieferte fuer die betroffenen Radios Muell -- den `grep '^cap-wifi'`
# samt pipefail dann verschluckte, sodass `map` LEER zurueckkam und ap-map.txt leer blieb.
# Fix: bei :typeof=array das erste Element nehmen. Beleg-Kommando dieses Fundes am Manager:
# `:foreach ... :put (name . " freq=" . channel.frequency)` -> "5260;5280" fuer cap-wifi1.
karte() {
ros ':foreach i in=[/interface wifi find where !master-interface] do={:local c [/interface wifi get $i cap]; :local fl [/interface wifi get $i channel.frequency]; :local f 0; :if ([:typeof $fl]="array") do={:set f [:tonum [:pick $fl 0]]} else={:set f [:tonum $fl]}; :local b "2,4GHz"; :if ($f > 3000) do={:set b "5GHz"}; :put ([/interface wifi get $i name] . "=" . [:pick $c 0 [:find $c "@"]] . "/" . $b)}' \
| tr -d '\r' | grep '^cap-wifi[0-9]*='
}
# Auf welchem AP steht der Pin? Ergibt sich aus den Regeln: abgewiesen wird der ANDERE AP.
welcher() {
local m; m="$(karte)"
local regeln; regeln="$(ros "/interface wifi access-list print terse where comment=\"$TAG\"")"
[[ -z "${regeln//[[:space:]]/}" ]] && { echo off; return; }
local blockiert; blockiert="$(echo "$regeln" | grep -o 'interface=cap-wifi[0-9]*' | cut -d= -f2)"
local aps_blockiert; aps_blockiert="$(for i in $blockiert; do echo "$m" | grep "^$i=" | cut -d= -f2 | cut -d/ -f1; done | sort -u)"
# Uebrig bleibt der AP, der NICHT blockiert ist.
for ap in $(echo "$m" | cut -d= -f2 | cut -d/ -f1 | sort -u); do
echo "$aps_blockiert" | grep -qx "$ap" || { echo "$ap"; return; }
done
echo off
}
# Die Interfaces EINES AP -- live ermittelt, nicht geraten.
ifaces_von() {
karte | grep "=$1/" | cut -d= -f1
}
aufraeumen() {
ros "/interface wifi access-list remove [find where comment=\"$TAG\"]" >/dev/null
}
# Der Reject greift erst bei der NAECHSTEN Anmeldung. Ohne Kick bleibt das Telefon
# u.U. minutenlang auf dem verbotenen AP haengen -- und der Rundgang misst dann nichts.
kick() {
ros "/interface wifi registration-table remove [find where mac-address=\"$MAC\"]" >/dev/null
}
zeigen() {
echo "--- Regeln ($TAG) ---"
local r; r="$(ros "/interface wifi access-list print terse where comment=\"$TAG\"")"
if [[ -z "${r//[[:space:]]/}" ]]; then echo " (keine -- Pin ist AUS)"; else echo "$r" | sed 's/^/ /'; fi
echo "--- Anmeldung $MAC ---"
local a; a="$(ros "/interface wifi registration-table print terse where mac-address=\"$MAC\"")"
if [[ -z "${a//[[:space:]]/}" ]]; then echo " (nicht angemeldet)"; else echo "$a" | sed 's/^/ /'; fi
}
case "$ZIEL" in
ap1|ap2)
ANDERER="ap1"; [[ "$ZIEL" == "ap1" ]] && ANDERER="ap2"
SPERREN="$(ifaces_von "$ANDERER" | tr '\n' ' ')"
if [[ -z "${SPERREN//[[:space:]]/}" ]]; then
echo "Keine Interfaces fuer $ANDERER gefunden -- haengt der AP am Manager? Abbruch." >&2; exit 1
fi
aufraeumen
for i in $SPERREN; do
ros "/interface wifi access-list add mac-address=$MAC interface=$i action=reject comment=\"$TAG\"" >/dev/null
done
echo "S22 -> nur $ZIEL ($ANDERER abgewiesen auf: $SPERREN)"; kick ;;
off)
aufraeumen; echo "Pin geloest -- S22 darf wieder ueberall."; kick ;;
status) ;;
map) karte; exit 0 ;;
which) welcher; exit 0 ;;
*) echo "Aufruf: $0 {ap1|ap2|off|status|map|which}" >&2; exit 2 ;;
esac
[[ "$ZIEL" == "status" ]] || sleep 6
zeigen