# Architektur — barox Virtual Switch Simulator

## 1. Ziel

Ein **Management-Plane-Simulator** für barox Ethernet-Switches (erstes Zielmodell:
`RY-LGSP38-28`), der sich gegenüber externen Systemen (NMS, Genetec, Zabbix, PRTG,
SNMP-Tools) über SNMP, REST-API und HTTP soweit wie sinnvoll wie ein echter Switch
verhält — **ohne** eine SparX-5/FireAnt-ASIC-Dataplane zu emulieren.

Kein Composer. Nur PHP-Standardbibliothek, PDO, sockets, pcntl, posix, JSON,
Apache2, systemd, MySQL/MariaDB, Vanilla JS.

## 2. Zentrales Prinzip: Single Source of Truth

```
             Management Web UI      REST API      SNMP Agent      WebStaX-Compat
                     \                  |               |               /
                      \                 |               |              /
                       ---------------- SimulatorStateService ---------
                                          |
                                   Repository-Schicht (app/Models)
                                          |
                                       MySQL (PDO)
```

Alle vier Protokoll-Fassaden rufen **dieselben** `app/Services/*Service.php`-Klassen
auf. Es gibt keine parallele State-Haltung (kein SNMP-eigenes In-Memory-Modell,
kein Cache, der von der DB abweichen kann). Ein SNMP-SET ruft denselben
`PortService::setAdminState()` auf wie ein Klick in der Web-UI oder ein
`PUT /api/v1/switches/{id}/ports/{n}`. Ein `EventService` wird von allen Services
als Nebeneffekt aufgerufen (Event-Log, ggf. Trap, ggf. Scenario-Trigger).

## 3. Verzeichnisstruktur

Siehe `README.md` für die vollständige, aktuell umgesetzte Struktur (angelehnt an
den Vorschlag aus der Aufgabenstellung, mit `Simulator/` für Modell-Capabilities
und Zähler-Berechnung).

## 4. Datenfluss-Beispiel: „SNMP SET schaltet Port 7 ab“

1. `workers/snmp-agent.php` empfängt UDP-Paket auf `:161` (prod) bzw. `:1161` (dev).
2. `Snmp\Ber` dekodiert die SNMPv2c-PDU (Community-Check gegen
   `virtual_switches.snmp_community_rw`, anhand der Ziel-IP aufgelöst).
3. `Snmp\PduHandler` sucht die OID in der `Snmp\OidRegistry`, findet den
   Handler für `rylgsp3828PortConfAdminState.7` (Beispiel-Name), ruft
   `PortService::setAdminState($switchId, 7, false)`.
4. `PortService` schreibt `ports.admin_state='disabled'`, setzt `link_state='down'`
   (kein Admin-Up ohne Link), aktualisiert `last_change`, ruft
   `EventService::record(..., source: 'snmp')`.
5. `EventService` schreibt `events`, prüft Trap-Targets (`trap_targets`),
   erzeugt bei Bedarf einen Eintrag, den `workers/trap-worker.php` versendet.
6. Die nächste Anfrage der Web-UI (`GET /api/v1/...` oder Seiten-Reload) liest denselben
   Datensatz aus `ports` — Port 7 erscheint sofort als „Disabled“.

## 5. Multi-Switch / Multi-IP (Ziel: 100+ virtuelle Switches)

* Jeder virtuelle Switch bekommt eine zusätzliche IP auf dem Loopback- oder
  Ethernet-Interface (`ip addr add 10.50.1.101/24 dev eth0`, siehe
  `scripts/install.sh` / `docs/`).
* **Apache**: Ein einziger VHost mit `ServerAlias *` bzw. mehrere
  IP-basierte `<VirtualHost 10.50.1.101:80>`-Blöcke sind für 100+ IPs
  unpraktisch zu pflegen. Stattdessen nutzt `public/index.php` /
  `public/api/index.php` `$_SERVER['SERVER_ADDR']` (die Ziel-IP der Anfrage),
  um über `virtual_switches.management_ip` den angesprochenen Switch
  aufzulösen — ein einziger VHost mit `<VirtualHost *:80>` reicht.
* **SNMP-Agent**: Ein einzelner UDP-Socket kann die Ziel-IP eines eingehenden
  Pakets nur über `IP_PKTINFO` (bzw. `recvmsg` mit Ancillary-Daten) ermitteln.
  PHP's Socket-Extension bietet dafür kein natives High-Level-API. Der
  Simulator verwendet daher den robusteren, auch mit reinem `sockets`-Modul
  funktionierenden Ansatz: **ein UDP-Socket pro virtueller Switch-IP**,
  gebunden an `IP:161`/`IP:1161`. Der Daemon verwaltet einen
  `socket_select()`-Loop über alle gebundenen Sockets (funktioniert bis in
  den drei- bis vierstelligen Bereich problemlos; 100 Switches = 100 Sockets
  = unkritisch). Wird `CAP_NET_BIND_SERVICE` via systemd gesetzt, kann der
  PHP-Prozess als `www-data` an Port 161 auf allen IPs binden.
* Dieses Design ist in `workers/snmp-agent.php` als `MultiSocketListener`
  umgesetzt und pollt bei Switch-Änderungen (neu/gelöscht) die Socket-Liste
  periodisch neu ein.

## 6. Performance-Architektur (Ziel: 2800 simulierte Ports)

Es wird **nicht** jede Sekunde jeder Port in MySQL aktualisiert. Stattdessen:

* **Traffic-Zähler** (`rx_bytes`, `tx_bytes`, …) werden **nicht** kontinuierlich
  geschrieben. Jeder Port speichert `traffic_profile`, eine daraus abgeleitete
  Rate (`app/Simulator/TrafficProfile.php`), einen Basiswert
  (`counters_base_*`) und einen Zeitstempel (`counters_synced_at`). Der
  aktuelle Zählerwert wird bei jedem Lesezugriff (SNMP GET, REST GET, UI)
  **on-the-fly** als `basis + rate * (jetzt − synced_at)` berechnet
  (`Services/CounterService.php`). Ein DB-Write erfolgt nur bei tatsächlichen
  Events (Profiländerung, „Clear Counters“, Link-Wechsel), nicht periodisch.
* **CPU/Memory/Temperatur**: Baseline-Wert in der DB, deterministisches
  Pseudo-Rauschen (Sinus + Hash-Seed aus Switch-ID und aktueller Minute) wird
  beim Lesen addiert — ebenfalls ohne periodische Schreib-Last. Szenarien wie
  „CPU Overload“ überschreiben die Baseline explizit (seltene Schreibzugriffe).
* **Uptime** wird nie gespeichert, sondern immer aus `uptime_started_at`
  berechnet.
* Die einzigen Hintergrund-Worker sind ereignisgesteuert:
  `workers/scenario-worker.php` (prüft fällige Szenario-Schritte, Poll-Intervall
  1s, aber nur Schreibzugriffe wenn ein Schritt tatsächlich fällig ist) und
  `workers/trap-worker.php` (versendet nur tatsächlich anstehende Traps).
  `workers/snmp-agent.php` ist rein I/O-getrieben (blockiert in
  `socket_select`).

## 7. SNMP-Implementierung ohne Composer

* `app/Snmp/Ber.php`: minimaler, selbst geschriebener BER/DER-Encoder/Decoder
  für die in SNMPv2c benötigten Typen (INTEGER, OCTET STRING, NULL, OID,
  SEQUENCE, Counter32, Gauge32, TimeTicks, Counter64, IpAddress,
  NoSuchObject/NoSuchInstance/EndOfMibView bei GETNEXT/GETBULK).
* `app/Snmp/Pdu.php`: Kapselt GetRequest/GetNextRequest/GetBulkRequest/
  SetRequest/GetResponse/Trapv2 auf Basis von `Ber`.
* `app/Snmp/MibParser.php`: schlanker, auf die konkrete Herstellersyntax
  optimierter Parser für `OBJECT-TYPE`-Blöcke (kein vollständiger
  ASN.1-Compiler). Erkennt Name, `SYNTAX`, `MAX-ACCESS`, `DESCRIPTION`,
  `INDEX` und den `::= { parent N }`-Pfad, löst daraus die numerische OID
  rekursiv über die MIB-Objektbaum-Definitionen auf.
* `app/Snmp/OidRegistry.php`: kombiniert (a) handgepflegte Einträge für
  `SNMPv2-MIB`/`IF-MIB` (Phase-1-Subset aus der Aufgabenstellung) und (b)
  automatisch aus der privaten MIB geparste Einträge, jeweils mit einem
  PHP-Handler (Closure), der auf `SimulatorStateService` liest/schreibt.
* `workers/snmp-agent.php`: CLI-Daemon (`declare(strict_types=1)`,
  eigener Autoloader), lauscht auf UDP, dekodiert, dispatcht über
  `PduHandler`, kodiert Antwort.

## 8. Firmware-/MIB-Analyse

Details siehe `docs/FIRMWARE_ANALYSIS.md` und `docs/MIB_SUPPORT.md`.
Kurzfassung: Die `.imgs`-Datei ist ein proprietärer Firmware-Container
(`Firmware&EZCM001`-Header) mit mehreren eingebetteten, komprimierten
Partitionen. Eine davon ist ein reguläres **SquashFS**-Root-Filesystem
(gefunden per Magic-Byte-Scan, mit `unsquashfs` extrahiert), das u. a.
`/var/www/webstax/*.htm` (das komplette WebStaX-Web-UI), `/usr/bin/switch_app`,
Hiawatha-Webserver-Konfiguration, Dropbear und die üblichen BusyBox-Tools
enthält. Aus den `.htm`/eingebetteten `<script>`-Blöcken der WebStaX-Seiten
wurden die tatsächlichen Backend-Endpunkte und deren (Nicht-JSON!)
Antwortformate extrahiert — siehe FIRMWARE_ANALYSIS.md.

## 9. Erweiterbarkeit

`switch_models.capabilities` (JSON) und `switch_models.port_definitions`
(JSON) kapseln alles Modellspezifische. Neue Modelle (siehe Abschnitt 31 der
Aufgabenstellung) werden als neue Zeile in `switch_models` angelegt, ohne
PHP-Code zu ändern. Zukünftige Protokoll-Features (VLAN, STP, LACP, …) werden
als eigene `Services/*Service.php` + eigene Tabellen ergänzt, ohne bestehende
Services zu verändern (Open/Closed).

## 10. Sicherheitsarchitektur

PDO Prepared Statements überall, `password_hash()`/`password_verify()`,
CSRF-Token pro Formular/Session, `htmlspecialchars()` beim Ausgeben
nutzergenerierter Daten, Security-Header (`Content-Security-Policy`,
`X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`), Cookies mit
`HttpOnly; Secure (falls TLS); SameSite=Lax`, Session-Regenerierung nach
Login, Login-Rate-Limiting über `users.failed_login_count`/`locked_until`,
API-Keys werden nur als `hash('sha256', ...)` gespeichert.
