#!/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