Files
bf-infra/docs/tp25-pin-s22.sh

124 lines
6.1 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
karte() {
ros ':foreach i in=[/interface wifi find where !master-interface] do={:local c [/interface wifi get $i cap]; :local f [/interface wifi get $i channel.frequency]; :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