diff --git a/HANDOVER.md b/HANDOVER.md new file mode 100644 index 0000000..44ddf88 --- /dev/null +++ b/HANDOVER.md @@ -0,0 +1,182 @@ +# BF-Netzwerk — Übergabe (Netzwerk · Routing · VLAN · Sprachtelefonie) + +> **Single Source of Truth** für die Nachfolge-Session. Diese übernimmt **Implementierung** (jetzt Phase A) +> **und die weitere Planung** (Phasen B–D + Backlog). Alles Nötige steht hier; tiefere Historie nur als Referenz. +> +> **Referenzen (Historie, optional):** Ursprungs-Vergleich `~/.claude/plans/welche-kosteng-nstigen-alternativen-zu-memoized-sparkle.md` · +> Projekt-Memory `project_bf_network_r6` · Repo `~/dev/bf-network-r6/` (Git). + +--- + +## 0. Auftrag der nächsten Session +1. **Phase A jetzt** umsetzen (Switch-VLAN-Vorbereitung, non-disruptiv) — siehe §9. +2. **Phasen B–D planen & ausführen**, sobald die 2× MikroTik hAP ax² produktiv sind (hAP-Cutover, Firewall, Adress-Migration). +3. Doku/Memory aktuell halten (`inventory/build.py`, Memory `project_bf_network_r6`). +**Kickoff-Prompt** zum Starten: §15. + +## 1. Ziel & Treiber +Günstige, **lokal/kostenlos verwaltete** Alternative zu UniFi/Omada über Router+WiFi+Switch. K.O.-Kriterien: +lokaler Gratis-Controller · VLAN · QoS · Multi-SSID→VLAN · DNS/DHCP-Redundanz · (6 GHz – bewusst zurückgestellt). +Konkrete Treiber am Standort **BF**: +- **Sprachtelefonie/Call-Qualität** (Hauptmotiv): Cable-Upload sättigt → **Bufferbloat** → Jitter in Telefonie/Calls. + Lösung = echtes **WAN-QoS (CAKE/SQM)** am neuen Router. (Bezug: ungelöstes Thema aus `project_network_calls_openwrt`.) +- **AdGuard-Forwarder an BF ablösen**: lokaler Router/DNS soll DoH→Cloudflare + lokale Rewrites selbst können. +- **Familienverträglichkeit**: WLAN + **Telefon** dürfen beim Umbau nur minimal/kurz ausfallen. + +## 2. Architektur-Entscheidung „R6+C2" (kompakt, mit Begründung) +- **Kein** Anbieter bietet *einen* gratis lokalen Single-Pane über Router+WiFi+Switch **+** 6 GHz **+** Router-HA. + Router-HA entfällt (DNS/DHCP-Redundanz genügt) → Feld öffnet sich. +- **Gewählt: MikroTik** (RouterOS). Gründe: kann **CAKE/SQM** (Call-Fix), **DNS** (DoH/Rewrites), **VLAN/QoS**, + **CAPsMAN** (zentrale WiFi-Config ohne Extra-Controller), nativer **DHCP**; günstig; minimale Box-Zahl. +- **Hardware: 2× hAP ax²** (WiFi 6 2,4+5 GHz, **kein 6 GHz** — bewusst, spart €, Refresh in 3–4 J.; 5×1G). +- **Core-Switch behalten**: ALLNET ALL-SG8316M kann 802.1Q (Trunk/Access/Hybrid + PVID) → tauglich, kein Tausch. +- **FB6660** bleibt **Cable-Modem + Telefonanlage**; Routing/DHCP/DNS/QoS wandert auf hAP#1. +- **C2 = DNS/DHCP-Strategie**: DHCP **nativ RouterOS** (Split-Scope); DNS = **Technitium-Container auf pve + fm**. + +## 3. Topologie +**Ist (heute):** FB6660 = Router/Gateway `10.40.41.254`, alles flach in VLAN 1; Core-Switch verteilt nur L2. +``` +Internet ─ FB6660 (Modem+Telefon+Router .254) ─ Core-Switch sw1 (10.40.41.239, flach) ─ Clients/Server/NAS/pve +``` +**Ziel (nach Cutover):** +``` +Internet ─ FB6660 (Modem+Telefon, "Exposed Host"→hAP#1) Telefon bleibt an FB + │ (WAN-VLAN untagged) + Core-Switch sw1 (VLAN-Trunk-Verteiler, 802.1Q) + ├─ Trunk → hAP ax² #1 (Router: NAT/DHCP/DNS/Firewall/CAKE, CAPsMAN-Manager) + ├─ Trunk → hAP ax² #2 (AP, CAPsMAN-managed) + └─ Access-Ports je VLAN (lokale Geräte) +``` +**Geräte:** FB6660 · sw1 `10.40.41.239` · 2× hAP ax² (erwartet) · Mac mini `pve` `10.40.41.167` (macOS, L2-lokal zu sw1) · +NAS `nas-bf` `10.40.41.10`. Switch-Ports Ist (`inventory/bf-core-ports.csv`): **4**→Wohnzimmer-Switch, **8**→Arbeitszimmer-Switch +(.10/.167/.247), **15**→pve, **16**→FB (.246 auf FB-LAN4). 4/8/15/16 **behalten** (4+8 interim bis neue Switches da). + +## 4. VLAN-Modell · Adressplan · Firewall +| VLAN | Name | Subnetz (Ziel) | Interim | Zweck | +|---|---|---|---|---| +| 10 | **MAIN** | 10.40.10.0/24 | **vorerst 10.40.41.0/24** | Trusted-Clients **+ Media** (Sonos/AppleTV/TV/SAT/Drucker) | +| 20 | **INFRA** | 10.40.20.0/24 | **vorerst vorhandene 172.x** | Server, VMs, Docker, NAS-Workloads, HA | +| 30 | **IOT** | 10.40.30.0/24 | — | Smart-Home (HA-managed) | +| 40 | **UNTRUSTED** | 10.40.40.0/24 | — | Spielekonsole & Low-Trust-Internet | +| 90 | **GUEST** | 10.40.90.0/24 | — | nur Internet (abgesetzt; 50–89 frei für künftige VLANs) | +| 99 | **MGMT** | 10.40.99.0/24 | Switch vorerst .239 | Switch/AP/Hypervisor-Mgmt-Interfaces | +| 100 | **WAN** | (FB-Transfer) | — | zur Fritzbox | + +**Konventionen:** Gateway je VLAN = `.1` (hAP#1) · Switch-Mgmt = `10.40.99.2` · 3. Oktet = VLAN-ID · alles RFC1918. +**Warum so (Kurz-Begründung der Diskussion):** +- **INFRA ≠ MGMT** (Workloads vs. Steuer-Interfaces = andere Vertrauensstufe). +- **Media auf MAIN** (nicht aus Vertrauen, sondern weil **mDNS/SSDP nicht über VLANs** quert; Sonos-Steuerung vom Handy soll trivial bleiben). +- **UNTRUSTED (40) ≠ IOT (30)**: Konsole ist kein IoT → Name nicht verwässern. +- **Adressierung `10.40..0/24`**: altes 172.40/50/99 lag **außerhalb RFC1918** (privat=172.16–31) = öffentlicher Raum → korrigiert. + +**Firewall = Least-Privilege** (NICHT „Main→alle", sonst wäre INFRA entwertet): +- Default **deny** inter-VLAN, dann gezielt erlauben. +- **Main→Infra:** nur Whitelist-Dienste (NAS-Shares, HA-UI, Proxmox-UI auf def. Ports). +- **Infra→Main:** grds. deny (Ausnahmen gezielt: Backups/Notifications). +- **IoT/Guest/Untrusted→intern:** deny — nur Internet. +- **MGMT (99):** aus Main nicht erreichbar. +- **mDNS-Strategie:** discovery-abhängige Media auf MAIN (kein Reflector nötig); Nicht-Discovery-Zugriff (NAS-UI/HA) via Firewall + DNS/Hostname. + +**IP-Detailentscheide:** VPNchristoph zieht von `.1` weg (wird Gateway; IP egal). vpnjoern `.2` gekickt → frei. +`.10` nas-bf bleibt vorerst (Umzug nach INFRA später). `.246`/`.247` bleiben. FB=`.254`. + +## 5. Routing / DHCP / DNS +- **Router:** hAP#1 = NAT, Inter-VLAN-Routing, Firewall (§4), CAKE auf WAN (§6). +- **DHCP:** **nativ RouterOS** je VLAN. Split-Scope für Redundanz: #1 `.50–.150`, #2 `.151–.250`. Option 6 (DNS) = beide Technitium-IPs. +- **DNS:** 2× **Technitium**-Container auf **pve (10.40.41.167)** + **fm (DS415+)**. **DoH→Cloudflare** (Filterprofil je Wunsch), + lokale **Rewrites/Split-Horizon** (aus AdGuard-Bestand migrieren). **Adlists in Technitium AUS** — CF-DoH filtert. Sync zwischen den 2 Instanzen. + → AdGuard-Forwarder an BF wird damit obsolet (Original-Ziel). Mac mini nicht im kritischen Pfad (DHCP/DNS-Failover über 2 Instanzen). + +## 6. Sprachtelefonie / Call-Qualität (Kern-Treiber) +- **Telefon bleibt an der FB6660** (DOCSIS-Telefonie tief verdrahtet) → beim Cutover **nicht anfassen**, Anrufe laufen weiter. +- **Call-/Video-Jitter** kommt vom **Bufferbloat am Cable-Upload** (typ. ~1000/50, Upload sättigt schnell). + → **CAKE/SQM auf dem WAN-Interface (vlan100) des hAP#1**, Upload-Rate **~85–95 % der real gemessenen FB-Upload-Linerate**, + iterativ via waveform.com/dslreports kalibrieren. Switches können das NICHT (kein Packet-Scheduling); FB-QoS reicht nicht. +- **Cutover-Priorität „Familie muss telefonieren":** Phase B startet mit WiFi + CAKE *innerhalb von Stunden*, nur vlan10 aktiv; + volle Segmentierung danach. Max. ~30 min Ausfall einplanen/ankündigen. +- **Verifikation:** Bufferbloat-Test unter parallelem Upload-Stress bleibt niedrig; reale Telefonie ohne Jitter. + +## 7. WiFi +- **CAPsMAN** auf hAP#1 (Manager), hAP#2 als CAP → zentrale Wireless-Config, gleiche SSIDs, Roaming. +- 3 SSIDs → VLAN-Mapping (MAIN/IOT/GUEST; UNTRUSTED je nach Bedarf). Phase B startet mit **einer** SSID „BF-Main"→vlan10. +- WiFi 6 auf 2,4+5 GHz (kein 6 GHz). + +## 8. Aktueller Stand (erledigt) +- ✅ Switch `sw1` **auf FW 2.2.1.2956 geflasht** (von 1.0). IP blieb `.239`; Cred-Store gewiped → `claudecode`-Account wiederhergestellt. +- ✅ **VLAN-DB 10/20/30/40/100** angelegt + `save` (alle Ports noch VLAN 1 → **null Disruption**, Netz läuft). +- ✅ Repo + Tooling: CLI-Runner, `.cli`-Files, CLI-Referenz, kanonisches Inventar (`bf.csv`/`build.py`), Port-Map, VLAN-Plan, Runbooks. +- ⚠️ FW 2.2.1 hat Web-UI umgebaut → alte `dispatcher.cgi`-cmd-Codes tot. **Konfig nur via Telnet-CLI** (kein SSH). + +## 9. Implementierungs-Phasen +### Phase A — Switch-VLAN-Vorbereitung (JETZT, non-disruptiv, kein Reboot, kein Port-Move) +Tooling: `cd ~/dev/bf-network-r6/switch && . ./.secrets && expect cli/run-cli.exp cli/.cli` +1. `show running-config` → `switch/cli/running-PRE.txt` sichern (klärt zugleich **Netzmasken-Syntax** von `ip address`). +2. `vlan 90`, `vlan 99` anlegen (neues `cli/02-vlan-extend.cli`, analog `01-vlan-db.cli`). +3. **VLAN-Namen** setzen (MAIN/INFRA/IOT/UNTRUSTED/GUEST/MGMT) — Namens-Syntax erst per `?` erkunden. +4. `save`; mit `show vlan` verifizieren. +> Vor Schritt 2 Mini-Entscheid bestätigen: **Guest=90** (empfohlen) vs 50. + +### Phase B — hAP-Cutover (wenn hAP ax² produktiv) — DISRUPTIV, ankündigen +- Trunk-Ports sw1→hAP#1/#2 (tagged 10/20/30/40/90/100); FB-Port = Access untagged WAN. +- hAP#1: Gateway `.1` je VLAN, **DHCP** (Split-Scope), **DNS**→Technitium, **CAKE** auf WAN, **CAPsMAN**, SSID(s). +- FB: DHCP aus, „Exposed Host" → hAP#1-WAN. **Telefon unangetastet.** +- Start „nur vlan10" für schnelles WLAN+Telefon, dann Segmentierung. +- Switch-Mgmt nach vlan99 ziehen: `management-vlan vlan 99` + `ip address 10.40.99.2` (Reihenfolge-Gotcha → Reachability!). + +### Phase C — Firewall (Least-Privilege) am hAP — Regelwerk §4. +### Phase D — Adress-Migrationen (behutsam): MAIN 10.40.41→10.40.10; INFRA 172.x→10.40.20; NAS .10→INFRA. + +### Backlog / weitere Planung +- 4G/5G-WAN-Failover (USB-Modem an hAP#1 oder separater MikroTik-LTE) — optional, nachrüstbar. +- 6E/WiFi7-Hardware-Refresh in 3–4 J. +- Switch-Upgrade auf 2,5G (Branch/Core) bei Bedarf. +- Container-Konsolidierung Docker→vlan20/INFRA (mit Übergangs-Brücke für alte IPs). +- Restliche Switch-Ports erfassen (mit User), Port→VLAN-Zuordnung finalisieren. + +## 10. Switch-CLI (FW 2.2.1, Telnet, IOS-artig) — Kompakt +Login `claudecode` (Passwort in `switch/.secrets`). Prompts `sw1>`→`sw1#`→`sw1(config)#`. **Nur 1 VTY-Line!** +- mgmt-IP ansehen (enable): `show ip` · setzen (config): `ip address ` + `ip default-gateway ` (Maske-Syntax noch bestätigen) +- mgmt-VLAN (config): `management-vlan vlan ` · ansehen `show management-vlan` +- VLANs (config): `vlan ` z.B. `vlan 10,20,30,40,100` · ansehen `show vlan` +- Ports: `interface gi` → `switchport mode {access|hybrid|trunk}` · `switchport access vlan ` · `switchport trunk native/allowed ...` +- Persistieren: `save` · Dump: `show running-config` (Paging `--More--`→Space) · Version: `show version` +- **NICHT vorhanden:** `interface vlan`, `show ip interface`, `terminal datadump`. Details: `switch/cli/CLI-REFERENCE.md`. + +## 11. Gotchas +- **Switch: nur 1 Telnet-VTY**, reapt stale Sessions schlecht (~15 min „busy") → seriell, **sauber ausloggen**, bei busy warten statt hämmern. +- **`save` nicht vergessen** (sonst nach Reboot weg). +- FW-Flash-Default wäre 192.168.1.1/admin/admin (hier blieb IP; gut für Recovery zu wissen). +- Cutover: Alt-Lease-Clients zeigen noch auf FB `.254` → hAP#1 temporär Secondary-IP `.254` bis Renew. +- mgmt-VLAN-Wechsel = Lockout-Risiko (VLAN muss existieren + Port muss es tragen). +- `inventory/IPs.xlsx`/`bf.csv` enthalten echte IPs/MACs → bei öffentlichem Remote vorher prüfen. + +## 12. Zugang / Dateien +- **Switch:** `telnet 10.40.41.239`, `claudecode` (`switch/.secrets`). +- **Mac mini (pve):** `ssh 10.40.41.167`. **DS415+ (fm):** siehe `reference_ds415_docker` (Memory). +- **Repo:** `~/dev/bf-network-r6/` — Schlüssel: `HANDOVER.md` (dies), `docs/vlan-plan.md`, `switch/cli/`, `inventory/` (`build.py`). + +## 13. Offene Entscheidungen +- Guest-VLAN-ID **90** (empfohlen) vs 50 — vor Phase A.2. +- Netzmasken-Syntax `ip address` final bestätigen (Phase A.1). +- DNS-Filterprofil bei Cloudflare (Families o.ä.) — Phase B/Backlog. + +## 14. Verifikation (End-to-End) +- **A:** `show vlan` zeigt 10/20/30/40/90/99/100 mit Namen; in `show running-config`; `save`=Success. +- **B:** SSID→VLAN-Tag greift; Client verbindet; DHCP/DNS vom hAP; **Bufferbloat-Test niedrig unter Last (CAKE)**; **Telefon ohne Jitter**; CAPsMAN sieht hAP#2. +- **C:** IoT/Guest erreichen Main/Infra nicht; Handy erreicht Whitelist-Dienste; **Sonos-Steuerung funktioniert**. + +## 15. Kickoff-Prompt (copy-paste an die Nachfolge) +``` +Übernimm das BF-Netzwerk-Projekt (Netzwerk/Routing/VLAN/Sprachtelefonie): Implementierung + weitere Planung. +LIES ZUERST die komplette Übergabe: ~/dev/bf-network-r6/HANDOVER.md (alles drin: Architektur+Gründe, Phasen, CLI, Gotchas, Zugänge). +Memory: project_bf_network_r6. Repo: ~/dev/bf-network-r6/ (Git). + +STAND: Switch 10.40.41.239 auf FW 2.2.1 geflasht, VLAN-DB 10/20/30/40/100 angelegt+saved, alle Ports noch VLAN1 +(Netz läuft, FB ist noch Router/Telefon). Switch-Konfig NUR via Telnet-CLI (Login claudecode, PW in switch/.secrets); +nur 1 VTY-Line → seriell, sauber ausloggen, bei "busy" warten. + +JETZT Phase A (non-disruptiv, §9): running-config sichern → vlan 90+99 anlegen → VLAN-Namen setzen → save → show vlan. + Runner: cd ~/dev/bf-network-r6/switch && . ./.secrets && expect cli/run-cli.exp cli/.cli + Vorher mit User bestätigen: Guest-VLAN-ID 90 vs 50. +Danach Phasen B–D planen/ausführen, sobald die hAP ax² produktiv sind (Cutover: Telefon muss durchlaufen, CAKE gegen Jitter). +``` diff --git a/switch/cli/02-vlan-names.cli b/switch/cli/02-vlan-names.cli new file mode 100644 index 0000000..53d8474 --- /dev/null +++ b/switch/cli/02-vlan-names.cli @@ -0,0 +1,30 @@ +# === BF Core-Switch sw1 (ALL-SG8316M, FW 2.2.1) — VLAN-Namen setzen === +# Reproduzierbar: . switch/.secrets && expect switch/cli/02-vlan-names.exp +# NON-DISRUPTIV: benennt nur die bereits angelegten VLANs (aus 01-vlan-db.cli); +# ändert KEINE Port-Zugehörigkeit. +# VID-Plan: 10=MAIN 20=INFRA 30=IOT 40=UNTRUSTED 90=GUEST 99=MGMT 100=WAN +configure +vlan 10 + name MAIN +exit +vlan 20 + name INFRA +exit +vlan 30 + name IOT +exit +vlan 40 + name UNTRUSTED +exit +vlan 90 + name GUEST +exit +vlan 99 + name MGMT +exit +vlan 100 + name WAN +exit +end +save +show vlan diff --git a/switch/cli/02-vlan-names.exp b/switch/cli/02-vlan-names.exp new file mode 100644 index 0000000..c6642f6 --- /dev/null +++ b/switch/cli/02-vlan-names.exp @@ -0,0 +1,107 @@ +#!/usr/bin/expect -f +# VLAN-Namen setzen — korrigiert: `end` aus config-vlan geht zu sw1#, +# also nicht nutzen. Stattdessen `exit` aus config-vlan → config, +# und einmal `end` zum Speichern am Ende. + +set timeout 30 +log_user 1 + +spawn telnet $env(SW_HOST) + +expect "Username:" +send "$env(SW_USER)\r" + +expect "Password:" +sleep 1 +send "$env(SW_PASS)\r" + +expect "sw1#" + +send "configure\r" +expect "(config)" + +# 90 GUEST +send "vlan 90\r" +expect "(config-vlan)" +send "name GUEST\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 99 MGMT +send "vlan 99\r" +expect "(config-vlan)" +send "name MGMT\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 10 MAIN +send "vlan 10\r" +expect "(config-vlan)" +send "name MAIN\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 20 INFRA +send "vlan 20\r" +expect "(config-vlan)" +send "name INFRA\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 30 IOT +send "vlan 30\r" +expect "(config-vlan)" +send "name IOT\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 40 UNTRUSTED +send "vlan 40\r" +expect "(config-vlan)" +send "name UNTRUSTED\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# 100 WAN +send "vlan 100\r" +expect "(config-vlan)" +send "name WAN\r" +expect "(config-vlan)" +send "exit\r" +expect "(config)" + +# Save! +send "end\r" +expect "sw1#" + +send "save\r" +expect { + "Success" { send "\r" } + timeout { } +} + +# Verifizieren mit show vlan (paged) +send "show vlan\r" + +# --More-- handling +for {set i 0} {$i < 20} {incr i} { + expect { + -ex "--More--" { + sleep 0.3 + send " " + } + -ex "sw1#" { + break + } + timeout { break } + } +} + +send "exit\r" +expect eof diff --git a/switch/cli/02-vlan-names.log b/switch/cli/02-vlan-names.log new file mode 100644 index 0000000..242b20b --- /dev/null +++ b/switch/cli/02-vlan-names.log @@ -0,0 +1,46 @@ +spawn telnet 10.40.41.239 +Trying 10.40.41.239... +Connected to sw1.fritz.box. +Escape character is '^]'. +Username: claudecode +Password: ************ +sw1# configure +sw1(config)# vlan 90 +sw1(config-vlan)# name GUEST +sw1(config-vlan)# exit +sw1(config)# vlan 99 +sw1(config-vlan)# name MGMT +sw1(config-vlan)# exit +sw1(config)# vlan 10 +sw1(config-vlan)# name MAIN +sw1(config-vlan)# exit +sw1(config)# vlan 20 +sw1(config-vlan)# name INFRA +sw1(config-vlan)# exit +sw1(config)# vlan 30 +sw1(config-vlan)# name IOT +sw1(config-vlan)# exit +sw1(config)# vlan 40 +sw1(config-vlan)# name UNTRUSTED +sw1(config-vlan)# exit +sw1(config)# vlan 100 +sw1(config-vlan)# name WAN +sw1(config-vlan)# exit +sw1(config)# end +sw1# save +Success +sw1# +sw1# show vlan + VID | VLAN Name | Untagged Ports | Tagged Ports | Type +-------+------------------+------------------------------+------------------------------+--------- + 1 | default | gi1-16,lag1-8 | --- | Default + 10 | MAIN | --- | --- | Static + 20 | INFRA | --- | --- | Static + 30 | IOT | --- | --- | Static + 40 | UNTRUSTED | --- | --- | Static + 90 | GUEST | --- | --- | Static + 99 | MGMT | --- | --- | Static + 100 | WAN | --- | --- | Static + +sw1# exit +sw1> \ No newline at end of file diff --git a/switch/cli/backup.exp b/switch/cli/backup.exp new file mode 100644 index 0000000..8bf1c4c --- /dev/null +++ b/switch/cli/backup.exp @@ -0,0 +1,47 @@ +#!/usr/bin/expect -f +# Backup running-config mit --More-- Handling +# Schreibt komplette running-config nach STDOUT (zur Weiterleitung in Datei) + +set timeout 60 +log_user 1 + +spawn telnet $env(SW_HOST) + +expect "Username:" +send "$env(SW_USER)\r" + +expect "Password:" +sleep 1 +send "$env(SW_PASS)\r" + +expect { + "sw1#" { } + timeout { puts "TIMEOUT: kein Prompt"; exit 1 } +} + +send "show running-config\r" + +# Seiten durchblättern bis zurück zum Prompt +set more_count 0 +while {$more_count < 100} { + expect { + -ex "--More--" { + set more_count [expr $more_count + 1] + sleep 0.3 + send " " + } + -ex "sw1#" { + break + } + timeout { + puts "TIMEOUT beim Paging" + break + } + } +} + +send "\r" +expect "sw1#" + +send "exit\r" +expect eof