diff --git a/README.md b/README.md index 52722c3..36bd598 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,14 @@ a virtual node whose I/O behavior is driven by scripted scenarios. config, 255 = DHCP), WBM-equivalent pages (Port/Ethernet, Modbus settings, Modbus Mapping, I/O Config), and device-state persistence with factory reset. +- **Host network settings** *(requested, planned — spec §15)*: a + Docker-gated, opt-in settings page (`WAGO_SIM_HOSTNET=1` **and** + container detection) that lists the host OS's network interfaces + and adds/removes/changes their IPv4 addresses through a + whitelisted host-side agent — so simulated node re-addressing + (DIP dedicated-IP mode) and network-loss fault cases become real + on the bench network. Runtime-only changes, snapshot/undo, and an + audit log; fully absent (404) when the flag is off. - **Long-duration (continuous) simulation** *(requested, planned — see `docs/spec/software-spec.md` §8)*: endurance/soak testing over days or weeks of simulated time. Designated inputs are driven from streaming @@ -343,6 +351,34 @@ requirement ID for each item in parentheses. protocol tracing, diagnostics and fault injection (FR-14.2.5– 14.2.6); scoping questions to answer before planning (§14.3). +### Requested — host network settings (spec §15, not yet scheduled) + +- [ ] Host OS IP settings page (FR-15.2.1–15.2.3): exposed in the web + UI only when `WAGO_SIM_HOSTNET=1` **and** running inside Docker + (compose sets `WAGO_SIM_IN_DOCKER=1`; `/.dockerenv` as a + supplementary signal); authenticated page listing the host's + interfaces with link state and current IPv4 addresses, read + from live host state, with add/remove/change-address operations + validated client- and agent-side; routes answer 404 when + disabled — no dormant surface. +- [ ] Host-side agent + safety rails (FR-15.2.4–15.2.8): a + whitelisted companion service on the host (Unix socket mounted + into the container; `pyroute2`/`ip` on Linux v1, systemd unit in + `deploy/`) — interface whitelist, runtime-only (non-persistent) + address changes so a host reboot restores deployed addressing, + snapshot before every mutation with undo/restore, single-line + audit log; degraded state (agent unreachable) shows a banner + and answers 503 without affecting the rest of the UI; readonly + mode hides the page and 403s mutations. +- [ ] DIP dedicated-IP integration (FR-15.2.9): applying a DIP-switch + address change in dedicated-IP mode routes through the agent so + the derived IP lands on the host NIC — the PLC on the bench + network must genuinely re-connect to the new node address, like + a physical coupler after a DIP change (ties into §11 + FR-11.3.3); scoping questions (platform scope, bridge vs host + networking, agent auth, IPv6, link up/down) in §15.4 before + planning. + ### Web UI (spec §10) — in implementation - [x] Spec §10.3–10.6 updated with binding implementation decisions: diff --git a/docs/spec/software-spec.md b/docs/spec/software-spec.md index ed0c74e..bc6e8ee 100644 --- a/docs/spec/software-spec.md +++ b/docs/spec/software-spec.md @@ -7,7 +7,7 @@ | Project | `wago-modbustcp-simulator-py` | | Version | 1.4 (Draft for review) | | Date | 2026-10-03 | -| Status | Framework phase; module addressing implemented (FR-4.3.11 v1.5 per-type numbering); service/container deployment implemented (§12); plain-text request tracing implemented (NFR-6.4); clean CLI error handling implemented (NFR-6.5 CLI part); web UI §10 in implementation (M1–M5a done: auth, overview, manipulation, diagnostics, configuration, channel names, import/export, split overview; M6 scenario view open); long-duration simulation (§8) implemented 2026-10-04 incl. PlantEnv (FR-8.2.14–16), datasets, degradation, invariants, snapshots; bench mode (§9), runtime device configuration (§11 — coupler/module/channel-name subset covered by web UI M5/M5a), multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14) requested | +| Status | Framework phase; module addressing implemented (FR-4.3.11 v1.5 per-type numbering); service/container deployment implemented (§12); plain-text request tracing implemented (NFR-6.4); clean CLI error handling implemented (NFR-6.5 CLI part); web UI §10 in implementation (M1–M5a done: auth, overview, manipulation, diagnostics, configuration, channel names, import/export, split overview; M6 scenario view open); long-duration simulation (§8) implemented 2026-10-04 incl. PlantEnv (FR-8.2.14–16), datasets, degradation, invariants, snapshots; bench mode (§9), runtime device configuration (§11 — coupler/module/channel-name subset covered by web UI M5/M5a), multi-vendor device profiles (§13) and additional fieldbus protocols PROFINET/COMLI (§14), and host network settings (§15) requested 2026-10-06 | | Target device simulated | WAGO 750-362 / 750-362/0000-0001 FC Modbus TCP (G4) bus coupler | | Reference documents | `docs/m07500362_xxxxxxxx_0en.pdf` (750-362 Modbus TCP manual), Modbus TCP specification (modbus.org) | @@ -1018,7 +1018,8 @@ Terminal monitors (§9) suffice for single engineers, but FAT benches are message (alias-ownership rules of §8 FR-8.2.2 apply). - FR-10.2.10: The UI's **configuration** pages (Ethernet/port settings, Modbus watchdog settings, I/O Config with SAVE CONFIG, Modbus Mapping - overview, address-selection DIP switch) are specified in §11; §10 covers + overview, address-selection DIP switch) are specified in §11; the + Docker-gated host network settings page is specified in §15. §10 covers value/state oversight and manipulation. - FR-10.2.11: **Channel names** (requested 2026-10-03): every module channel can carry an application-specific name (e.g. `start_button`, @@ -1594,7 +1595,187 @@ the same process-image core. --- -## 15. Roadmap (post-framework) +## 15. Feature Request — Host Network Settings (Docker Bench Host) + +> **Status: requested 2026-10-06, not yet planned into a roadmap +> phase.** A settings page in the web UI that views and manipulates +> the **host OS's IP addresses**, exposed only when an explicit +> environment flag is set **and** the simulator runs inside Docker. +> This section captures the requirements and the enabling +> architecture (a whitelisted host-side agent); it does not commit to +> a schedule. + +### 15.1 Motivation + +A containerized simulator (§12) cannot reach beyond its own network +namespace. It can rebind its listen address (FR-11.3.3 dedicated-IP +mode), but the **host's** network interfaces — what the bench network +and the PLC under test actually see — are outside the container. As a +result, FAT cases that depend on *network-level* events cannot be +staged from inside the simulator today: + +- "Re-addressing" the virtual coupler (DIP switch, FR-11.3.1–11.3.3) + changes nothing on the bench network in the default bridge + deployment: the PLC keeps talking to the unchanged host address, so + the reconnect/re-addressing logic in the PLC program is never + exercised. +- Cable-pull / node-power-off fault cases require stopping the whole + container (coarse — every port drops at once) or shell access to the + bench host, breaking the "operate everything from the browser" + workflow of the web UI (§10). +- Moving the simulated node to a different subnet mid-bench + (multi-bench swaps, address conflicts) likewise needs host access. + +The requested feature closes the gap: an opt-in settings page through +which an authenticated operator changes the host OS's IP addresses +from the bench UI — making the simulated node's network identity +**real on the bench network**, and making network-level fault cases +first-class FAT scenarios. + +### 15.2 Requirements + +- FR-15.2.1: **Opt-in via environment flag.** The feature is disabled + unless `WAGO_SIM_HOSTNET` is set truthy (same semantics as + `WAGO_SIM_VERBOSE`: `1`/`true`/`yes`/`on`); the flag lives in the + compose file so enabling it is a visible, deliberate deployment + change. Default: off. When off, the page is absent and every + `/api/hostnet/*` route answers 404 — no dormant surface. +- FR-15.2.2: **Docker-only.** Independently, the simulator must detect + a container environment — binding: the compose file sets + `WAGO_SIM_IN_DOCKER=1` (supplementary signal: `/.dockerenv` exists). + Outside docker the routes stay 404 regardless of the flag: a + bare-metal operator has direct access to the OS network settings; + the page exists precisely because a container does not. +- FR-15.2.3: **Settings page.** A page in the web UI's configuration + area (alongside the Port/Ethernet page, FR-11.4.1) listing the + host's network interfaces — name, link state, current IPv4 + addresses — read from **live host state**, not a desired-state + mirror, so out-of-band changes made on the host appear on refresh. + The page also shows the agent's health and the effective whitelist. +- FR-15.2.4: **Operations.** Add an address (address + prefix length), + remove an address, replace an interface's primary address — each + validated client-side and agent-side (address/prefix syntax, + interface exists, interface whitelisted). Changes are + **runtime-only** (like `ip addr add`: non-persistent across host + reboots); v1 deliberately does not write netplan/NetworkManager/ + systemd-networkd configuration, so a host reboot is a guaranteed + escape hatch back to the deployed addressing. Optional v2, behind + the same whitelist and an explicit "disruptive" confirmation: set + an interface's link up/down (cable-pull simulation). +- FR-15.2.5: **Safety rails.** The agent enforces an interface + **whitelist** from its own config file (operations on other + interfaces are refused and surfaced as errors on the page); it + snapshots an interface's address set before every mutation, and the + page offers **undo last** / **restore interface**; every mutation + and every refusal is appended to a single-line **audit log** + (operator session, interface, old → new, timestamp) written to + stdout/journald like the simulator's own logging (FR-12.2.6) and + viewable (bounded) on the page. +- FR-15.2.6: **Trust boundary** (same stance as FR-10.2.16): the page + requires an authenticated session (FR-10.2.8); read-only mode + (`WAGO_SIM_WEB_READONLY`) hides the page and answers mutations with + 403. Changing host addresses is a privileged operation by design — + the flag must never be set on a network the operator does not + control. +- FR-15.2.7: **Host-side agent** (binding architecture decision). A + container cannot change host addresses, and the simulator container + itself shall not run privileged (§12 non-root, least-privilege + deployment). Instead a small companion service — + `wago-sim-hostnet-agent` — runs **on the host** (systemd unit in + `deploy/`, root or `CAP_NET_ADMIN`), listening on a Unix domain + socket that the compose file mounts into the container read-write. + The agent's protocol is a narrow whitelist of operations (health, + list, snapshot, address add/remove/change, restore) over JSON + lines; it never executes a shell with operator input — on Linux v1 + it uses the netlink API (`pyroute2`, with the `ip` tool as a + fallback). *Rejected alternative, documented why*: running the + simulator container itself privileged with host namespaces — it + forfeits the non-root deployment and couples host control to the + simulator's own lifecycle and restarts. +- FR-15.2.8: **Degraded states never break the UI.** Flag set but the + agent socket absent/unreachable: the page shows a clear "host + control unavailable" banner and mutation routes answer 503 with a + message; the Modbus server and the rest of the web UI are + unaffected. +- FR-15.2.9: **DIP dedicated-IP integration** (FR-11.3.3). When a DIP + address change is applied in dedicated-IP mode and the derived + interface is whitelisted, the apply step may route through the same + agent — the derived IP is added to the host NIC — so the PLC on the + bench network must genuinely re-connect to the new node address, + exactly like a physical coupler after a DIP change. Under + `network_mode: host` the simulator rebinds directly per FR-11.3.3; + either way the agent supplies the address on the host. + +### 15.3 Architecture sketch (planned) + +``` +bench host (Linux) +├── wago-sim hostnet-agent --config /etc/wago-sim/hostnet.yaml +│ (systemd unit: deploy/wago-sim-hostnet-agent.service; +│ root or CAP_NET_ADMIN — it owns the interface whitelist) +│ config: socket: /run/wago-sim/hostnet.sock, whitelist: [...] +└── docker compose + ├── mounts /run/wago-sim:/run/wago-sim (agent socket in) + └── wago-sim serve (page + /api/hostnet/* routes, + gated by FR-15.2.1 flag + FR-15.2.2 docker detection) +``` + +Web API, mounted only under the FR-15.2.1/2 gates: + +| Method/Route | Purpose | FR | +|---|---|---| +| `GET /api/hostnet` | availability (flag, docker, agent health) + interface/address list + whitelist | 15.2.3 | +| `POST /api/hostnet/interfaces/{name}/addresses` | add an address (address, prefix length) | 15.2.4 | +| `DELETE /api/hostnet/interfaces/{name}/addresses/{addr}` | remove an address | 15.2.4 | +| `POST /api/hostnet/interfaces/{name}/restore` | undo last / restore snapshot | 15.2.5 | +| `GET /api/hostnet/audit` | bounded recent audit lines | 15.2.5 | + +### 15.4 Scoping questions (answer before planning) + +1. Platform scope v1: Linux bench hosts only (agent via `pyroute2`)? + Windows hosts run the container inside Docker Desktop/WSL2 — the + container's "host" is the WSL2 VM; reaching the Windows NICs needs + a Windows-side agent over a named pipe — defer or include? +2. Networking-mode story for the DIP integration (FR-15.2.9): keep + the current bridge deployment (host IP + mapped port) as v1, or + require `network_mode: host` for full parity (node IP == host IP, + port 502)? +3. Agent authentication: Unix-socket file permissions only, or a + shared token in the agent config to defend against other local + users? +4. IPv6 in v1 or deferred? +5. Does link up/down (cable pull) belong in v1 — it can sever the + operator's own web UI session on that NIC — or are address + add/remove/change sufficient for the planned FAT cases? + +### 15.5 Acceptance criteria (draft) + +1. Without `WAGO_SIM_HOSTNET`: no page; all `/api/hostnet/*` routes + answer 404; the rest of the UI unchanged (regression suite green). +2. Flag set but not in docker: same as 1. +3. Flag set + docker + agent reachable: the page lists the host's real + interfaces; adding `192.168.1.33/24` to the whitelisted interface + shows in `ip addr` **on the host**, and the new address is + reachable from the bench network (a master connects through it). +4. Removing that address severs the master's connection through it — + the PLC's reconnect logic is exercised (the FAT case). +5. Restore/undo returns the interface to its pre-change snapshot; a + host reboot likewise restores the deployed addressing (changes are + runtime-only, FR-15.2.4). +6. An operation on a non-whitelisted interface is refused by the + agent and surfaced as an error on the page; the audit log records + every mutation and every refusal. +7. Read-only mode: page hidden, mutations 403. +8. DIP integration: applying switch position 33 in dedicated-IP mode + results in `192.168.1.33` present on the whitelisted NIC (bridge + mode) or the server rebind (host mode); the "restart required" + semantics of FR-11.3.3 are unchanged. +9. Agent socket missing: "host control unavailable" banner, + mutations 503, Modbus server and the rest of the web UI unaffected. + +--- + +## 16. Roadmap (post-framework) | Phase | Feature | |---|---| @@ -1603,11 +1784,11 @@ the same process-image core. | 4 | FAT report generator (HTML/PDF), scenario comparison, template library per project type | | 5 | Multi-node orchestration (several couplers in one FAT bench), UDP variant, WBM simulation; **web UI (§10)**; **runtime device configuration (§11)**: module hot-configuration with image rebuild, DIP-switch emulation, WBM-equivalent pages, device-state persistence | | 6 | CI integration: run scenarios headless, export JUnit XML | -| ? | **Multi-vendor device profiles (§13)** and **additional fieldbus protocols PROFINET/COMLI (§14)** — not yet scheduled; phase assignment after §13.3/§14.3 scoping | +| ? | **Multi-vendor device profiles (§13)**, **additional fieldbus protocols PROFINET/COMLI (§14)**, and **host network settings (§15)** — not yet scheduled; phase assignment after §13.3/§14.3/§15.4 scoping | --- -## 16. References +## 17. References - WAGO 750-362 manual `m07500362_xxxxxxxx_0en.pdf`, sections 7.2–7.3 (process image), 11.2 (Modbus), 12.2 (module data architecture) - WAGO I/O System 750 system description `m075xxxxx_xxxxxxxx_0en.pdf`