Add Dual-Stack Network Diagnostics

This commit is contained in:
2026-09-21 10:01:55 +02:00
parent 60c1e279d6
commit 06df47c934
18 changed files with 1488 additions and 270 deletions
+8
View File
@@ -187,6 +187,14 @@ Admin SSH `exit`, remote reboot, SSH stop/disconnect, and host-key rotate/reset
Browser stop/reboot uses this same owner-adapter control path. Exact forced certificate rotation instead uses the typed queue union and immutable `dispatcher_actions` mask to hand off after drain/200 ms to the existing 12 KiB dispatcher, not the 4 KiB control stack. Pending input is discarded through execution and an executing slot remains reserved across self-detach. Canonical shared service/identity replacement preserves commit/stop/start failure semantics. Parsed other-account interactive add/password and forced role/delete are allowed. Canonical Wi-Fi commands are also allowed, including hidden secret prompts and disruptive changes; these use the ordinary owner queue and may cut off the browser before output arrives, without a deferred acknowledgement guarantee. Browser self/generated/key/recovery, mDNS mutations and restricted SSH shell actions remain denied; typed Settings has separate permissions. Post-prompt currentness is operation admission, not an atomic session-liveness/NVS guarantee. [Browser policy and recovery](../web_administration.md#browser-shell-policy).
### Shared IPv4/IPv6 diagnostics
`network_console` owns root and `wifi`-alias ping/traceroute/nslookup for UART0, admin SSH and browser Admin. Family flags are parsed once; explicit IPv6 never silently falls back to IPv4. Hostname probes use IPv4-first resolution fallback only; nslookup queries A and AAAA separately and shows unscoped link-local answers. Probes require a device-local link-local zone (`%sta`, `%ap`, valid index/name). Resolver policy/DNS provisioning and serial transport ownership are unchanged.
Traceroute owns one raw socket and a 1280-byte transient receive buffer on the shared dispatcher stack, one eight-byte probe per hop (max30), and one-second absolute receive deadlines. It rejects incomplete/corrupt/unrelated packets and unsupported IPv6 extension headers/fragments. IDF5.5.0-specific framing, automatic ICMPv6 checksum, common `IP_TTL` hop-limit behavior and millisecond timeout flooring are pinned/retested; <1ms remaining returns timeout rather than configuring an infinite wait. DNS and send/scheduler latency are not covered by the receive deadline.
Ping keeps the 4200-byte PSRAM-only lazy queue payload and SDK transient task. Permanent callback context plus a pending reservation survives the count-based console deadline; later pings cannot reset a live producer's queue. SDK receive can outlast its timeout under unrelated ICMP, so a timed-out session stays reserved until END. END means callback production ended, not synchronous task/socket destruction; retiring resources can briefly overlap a new session. These diagnostics serialize administration but never execute on HTTPD or enter the serial hot path. Tests and exact limits: `tests/network_diagnostics/README.md`.
## Wi-Fi and persistence
`wifi_config` owns a fixed-width versioned NVS schema with four prioritized station profiles and AP policy `off`, `fallback`, or `always`. Missing configuration generates per-device defaults including a random AP password. Invalid stored data is generally left untouched while RAM defaults are used.
+1
View File
@@ -150,6 +150,7 @@ Shared UI regression: `tests/web_ui_session/run.py` and its domain `.cjs` fixtur
- Ownership: dispatcher is sole `esp_console_run()` caller; the SSH owner exclusively performs post-initialization wolfSSH runtime calls.
- Lifecycle: remote session tokens include slot generation; fixed output/history/prompt state is wiped immediately on idle close or after an executing handler returns. Admin SSH `exit` and Ctrl+D on an empty command line request bounded deferred self-disconnect after best-effort output draining.
- Constraint: one slow command or prompt serializes all administration. Admin SSH is unavailable until command registration and UART frontend creation complete; supported deferred actions wait only for a bounded application-buffer drain heuristic.
- Dual-stack diagnostics: `network_console.*` is the shared root/`wifi` handler on all three admin surfaces. `-4`/`-6`, device-local `%sta`/`%ap`/index/name; default probes IPv4-first resolution fallback only, nslookup explicit A+AAAA and unscoped link-local display. One-second/hop raw IPv4/IPv6 traceroute validates full outer packet/quoted echo and rejects IPv6 extensions/fragments. IDF5.5.0 raw framing/hoplimit/checksum/timeout contract is version-guarded. Ping uses existing PSRAM queue and SDK transient task with permanent callback context/deadline/pending reservation; END does not imply completed SDK teardown. Tests: `tests/network_diagnostics` (actual handlers/packet and SDK contracts), `tests/network_diagnostics_surfaces` (routing/policy/completion). No serial hot-path changes.
## Wi-Fi
+8
View File
@@ -2,6 +2,14 @@
Working memory, not an implementation timeline. Source is authoritative; begin with [code map](code-map.md), [architecture](architecture.md) and [decisions](design-decisions.md).
## Dual-stack diagnostics follow-up — implemented, device validation pending
- User explicitly requested IPv6 ping/traceroute/nslookup on all surfaces after accepting Phase12. Shared `network_console.*` now supports one `-4`/`-6` anywhere after command, preserves count/hop bounds, root and `wifi` aliases. Default probes try IPv4 then IPv6 on resolution failure only; nslookup performs A+AAAA queries separately (one selected address/family), displays unscoped link-local records. Link-local probes require ESP-local `%sta`/`%ap`/index/name; no client-interface guess or IPv4-mapped fallback. Help/completion updated; permissions unchanged.
- Actual IDF5.5.0 audit: raw ICMPv6 includes outer IPv6 header, automatic TX checksum, common `IPPROTO_IP/IP_TTL` hoplimit; replies validate checksums/target/id/seq and reject extensions/fragments. Traceroute one socket/max30 probes/1s receive deadline, 1280B receive buffer. Review caught sub-ms timeout flooring to infinite SDK wait; <1000us now returns timeout, actual SDK conversion/mailbox tests verify. No dependency/config/partition/serial path change.
- Ping retains4200B PSRAM queue, max20 probes and SDK transient task; permanent callback context plus pending reservation protects delayed END after count*2+2 second console deadline. SDK unrelated-ICMP receive loop may outlive timeout; later pings fail busy until END, which does not prove synchronous resource destruction. Read exact limits in command reference/tests README. IPv6-only unicast DNS still requires a configured resolver; no RDNSS/DHCPv6 provisioning added.
- `pio run` PASS **94,452 B linked RAM / 1,857,673 B flash**, +8/+3,188 versus accepted prior build. Traceroute receive-wait object frame measured240→1408B (not runtime stack high-water). No new permanent task/socket/queue capacity. Hardware/ changes in working tree belong to other agent and were not read/edited.
- Parent PASS: production diagnostics8027 checks+10k malformed packet inputs, seven installedSDK hashes+actualtimeout fixtures, 432 SSH/WEB root+alias routing cases/UART parity/ordinary-role and revocation guards/completion, existing admin policy and browser Wi-Fi secret fixture. Independent review fixes verified no remaining actionable findings in scoped paths. ASan/UBSan host runtimes absent. Broader console-boundary pre-existing missing rebootfake remains (not changed). No upload/device/network test; Phase12 acceptance remains valid only for its recorded baseline, not this follow-up.
## Phase 12 COMPLETE — explicit user validation
- User states: “I just validated Phase 12 successfully.” Record acceptance, not a pending phase. Canonical device evidence and reusable regression guidance: [Phase 12](../roadmap.md#phase-12--advanced-network-integration). No unreported DHCP capture, DNS-zone update, exhaustive address/service transition, fault/soak or byte-integrity pass is implied.
+30 -4
View File
@@ -108,12 +108,38 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st
| `wifi ap secret` / `wifi ap show-secret` | Set or reveal the AP password. |
| `wifi save|load|defaults|reset` | Persist, restore, reset in RAM, or reset and persist configuration. |
| `wifi counters|clear-counters` | Show or clear Wi-Fi counters. |
| `wifi ping <host> [count]` | Send 120 IPv4 or IPv6 ICMP probes. |
| `wifi nslookup <host>` | Resolve and display unique IPv4/IPv6 addresses. |
| `wifi traceroute <host> [max-hops]` | Run IPv4 ICMP traceroute with up to 30 hops. |
| `wifi ping [-4\|-6] <host> [count]` | IPv4/IPv6 ICMP echo; 120 probes, default 4. |
| `wifi nslookup [-4\|-6] <host>` | Explicit A/AAAA lookup; default queries both families. |
| `wifi traceroute [-4\|-6] <host> [max-hops]` | IPv4/IPv6 ICMP echo traceroute; 130 hops, default 16. |
`ping`, `nslookup`, and `traceroute` are root aliases. The four station-profile slots use lower priority values first. Edits to a disabled profile's SSID, priority, security mode, or secret are staged in RAM and do not interrupt the current Wi-Fi connection. Enabling or disabling a profile, changing an enabled profile, or changing AP policy/configuration applies the new radio policy and may reconnect Wi-Fi. Use `wifi save` to persist working changes. Passwords are not displayed by ordinary status output.
### Dual-stack network diagnostics
Root `ping`, `traceroute` and `nslookup`, and their `wifi` aliases, share one implementation on **UART0, admin SSH and the browser Admin console**. They execute on the ESP32, not the client computer or the attached UART1 equipment. Ordinary serial-role users do not gain administration access. `-4` and `-6` completion is available for all six command prefixes.
```text
ping -6 example.com 4
ping example.com 4 -4
wifi ping -6 fe80::1%sta 3
traceroute -6 example.com 16
wifi traceroute -4 example.com 16
nslookup example.com
nslookup -6 peer.local
```
One optional family selector can appear before or after the host/count; duplicate/conflicting selectors and unknown options are rejected. Probe counts/hop limits are positional, not Linux `-c`/`-m` options. Without a selector, numeric addresses retain their family and probe hostnames try IPv4 resolution, then IPv6 if no address was obtained. A failed probe does **not** retry the other family; this is not Happy Eyeballs. Explicit `-6` never silently uses IPv4.
Link-local **probe destinations** require a scope on the ESP32: `%sta`, `%ap`, an existing numeric lwIP interface index, or an existing lwIP interface name. These are not the browser/SSH computer's interface names. If name resolution returns an unscoped link-local address, `nslookup` displays it, but ping/traceroute request an explicit scoped literal rather than guessing an interface. Numeric `nslookup` input is displayed without DNS/reverse lookup.
`nslookup` without a selector performs separate A and AAAA queries. The installed resolver returns one selected address per family (`CONFIG_LWIP_DNS_MAX_HOST_IP=1`), not a complete DNS RRset. Missing records, DNS timeouts and server failures can share the same SDK error; partial success is displayed rather than hidden. Unicast names require a usable configured DNS server; SLAAC alone does not provision one in this firmware. `.local` uses the existing lwIP mDNS query support. No DNS server or RA/DHCP policy is changed by these commands.
Traceroute sends one eight-byte echo probe per hop with a one-second absolute receive deadline; it can stop up to 999 microseconds early to avoid the SDK's zero-millisecond/infinite-wait conversion. Hop addresses are numeric. ICMPv6 replies are checked against the actual IDF raw framing/checksum, destination and probe identity; IPv6 extension headers/fragments in replies or quotes are unsupported and ignored. Filtering/rate limiting can produce `*` even when ordinary connectivity works. DNS, scheduler and send latency are outside the receive deadline.
Ping retains the SDK's bounded-count transient task and existing PSRAM event queue. Its console wait is bounded to `count * 2 + 2` seconds after start, but unrelated ICMP can extend the SDK receive loop: after a console timeout, further pings are refused until the old END event arrives. END finishes callback production; SDK resource teardown may briefly overlap a subsequent session. No IPv6 TTL is printed because the installed SDK does not provide a valid received IPv6 hop-limit profile. These commands serialize the administration dispatcher while executing; they do not run on HTTPD or change the broker/serial path.
Focused host/SDK and surface tests: `tests/network_diagnostics/` and `tests/network_diagnostics_surfaces/`. Real IPv4/IPv6/scoped ping, DNS and multi-hop traceroute remain device-validation tasks for this follow-up.
## mDNS
| Command | Description |
@@ -125,7 +151,7 @@ Opening `/dev/ttyACM*` with DTR asserted creates the `usb-cdc` broker client, st
When the Wi-Fi station has IPv4 or a preferred IPv6 address, the Wi-Fi manager announces `sak-<suffix>.local` and advertises available HTTPS/SSH services through DNS-SD. This is STA-only, local-link discovery, not certificate or host-key trust. The default suffix is the lower-case hexadecimal STA MAC address. Suffixes may contain lowercase ASCII letters, digits, and internal hyphens only. Changing a suffix queues a best-effort reannouncement and updates the DHCPv4 hostname (`sak-<suffix>`, without `.local`) for subsequent DHCP exchanges; it does not force a lease restart. A configured DHCP/DNS server may publish that name in its own zone.
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. Existing ping selects the first usable resolver result without family racing, and traceroute remains IPv4-only. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. See [Phase 12](roadmap.md#phase-12--advanced-network-integration) for discovery convergence limits and pending device validation.
`wifi status` and browser Network status distinguish IPv4 absence and IPv6 link-local/ULA/GUA availability. `wifi` / `wifi status` and the web Network settings dialog additionally list up to three actual preferred IPv6 addresses, labelled link-local, ULA or GUA; these are copied with the flags, not inferred from enabled IPv6 support. A link-local destination needs the client's interface as its zone. To separate client address preference from IPv6 reachability, run `ping -6 -c 3 sak-1024.local` on an IPv6-capable client; choosing IPv4 with plain `ping` does not mean the device lacks IPv6. If lookup fails, inspect AAAA resolution (for example `avahi-resolve-host-name -6 sak-1024.local` where Avahi is installed), then test the numeric address from `wifi status` directly. Link-local-only connectivity counts as `ONLINE` and can retire the fallback AP after the existing stability interval; it does not establish Internet access. HTTPS/WebSocket and SSH support both families; browser IPv6 literals require brackets and cannot contain interface zones. Use the `.local` hostname for link-local browser access where supported by the client. On-device diagnostics support explicit family selection and scoped IPv6 as described above; Linux client syntax and interface scope are separate. mDNS failures do not stop Wi-Fi, UART0, UART1, or native USB access. See [Phase 12](roadmap.md#phase-12--advanced-network-integration) for discovery convergence limits and recorded user acceptance.
## HTTPS web terminal
+1 -1
View File
@@ -314,7 +314,7 @@ The device is not intended to become a general-purpose router. Captive-portal in
- mDNS 1.12.0 exposes no readiness acknowledgement and can silently drop queued family actions. Absent families are disabled on each one-second owner pass; available families are re-enabled/reprobed on a 30-second repair cadence. Upstream synchronous calls can delay this cadence. There can be a transient stale/zero-A response window after IPv4 loss before disable is processed. AAAA records follow upstream **valid-address** semantics, including deprecated-but-still-valid addresses, not preferred-only filtering. Client caches expire independently.
- `cmake/mdns_membership.*` applies one version/hash-guarded, build-local source overlay to mDNS 1.12.0: balance per-family multicast leaves and unwind joins after PCB creation failure. Managed sources and dependency versions remain unchanged. An upstream mismatch fails configuration for deliberate review; see `tests/mdns_membership/README.md` for maintenance/removal and regression evidence. This is not the abandoned Phase 9 patch set.
- `sdkconfig.defaults` explicitly enables IPv4/IPv6 and increases mDNS service capacity from one to two. Existing saved configurations override defaults: verify `CONFIG_LWIP_IPV4=y`, `CONFIG_LWIP_IPV6=y`, `CONFIG_MDNS_MAX_SERVICES=2` and STA-only predefined mDNS interfaces before building. The local N16R8 saved configuration was updated accordingly. No new task, transport socket, broker slot, serial buffer, partition, filesystem or generated web asset was added.
- Existing diagnostic limits remain: hostname ping selects the resolver's first usable result (not Happy Eyeballs), scoped link-local ping is not newly supported, and traceroute remains IPv4-only. DHCPv6, infrastructure DNS registration of SLAAC addresses, and new IPv6 resolver provisioning are outside this baseline.
- The accepted baseline initially retained IPv4-only traceroute and limited family/scope selection. The later requested [dual-stack diagnostics follow-up](command_reference.md#dual-stack-network-diagnostics) adds `-4`/`-6` to ping/traceroute/nslookup across UART0, admin SSH and browser Admin, scoped IPv6 probes and explicit A+AAAA lookup. This follow-up is built/host-tested, not covered retroactively by the acceptance capture below. DHCPv6, infrastructure DNS registration of SLAAC addresses, and new IPv6 resolver provisioning remain outside scope.
#### Validation evidence and acceptance
+1 -1
View File
@@ -28,7 +28,7 @@ HTTPD alone owns browser-admin socket IO and its 1,552-byte PSRAM-only payload.
Typed Settings permissions do not expand shell permissions. Parsed canonical arguments, not raw prefixes or completion suggestions, control admission:
- Browser `web` permits only `web status`, `web stop`, and exact `web certificate rotate --force`; certificate info/reset, diagnostics/performance and other web forms are denied.
- Browser `wifi` uses the canonical command handler, including bare `wifi` (status), profile/AP settings, persistence, lifecycle and network diagnostics. Profile/AP secret entry uses the shared hidden prompt: no input echo, history or completion of secret bytes; cancellation, disconnect and failed currentness discard the input. Explicit `wifi ap show-secret` reveals the AP password only in the invoking admin terminal; routine status and completion do not reveal credentials. Browser `mdns` still permits only exact `mdns status`.
- Browser `wifi` uses the canonical command handler, including bare `wifi` (status), profile/AP settings, persistence, lifecycle and network diagnostics. Profile/AP secret entry uses the shared hidden prompt: no input echo, history or completion of secret bytes; cancellation, disconnect and failed currentness discard the input. Explicit `wifi ap show-secret` reveals the AP password only in the invoking admin terminal; routine status and completion do not reveal credentials. Browser `mdns` still permits only exact `mdns status`. Root and `wifi`-prefixed ping/traceroute/nslookup support the same `-4`/`-6` and device-local IPv6 scope syntax as UART0/admin SSH; see [dual-stack diagnostics](command_reference.md#dual-stack-network-diagnostics). They run on the shared dispatcher, not HTTPD or the client browser; no extra endpoint or permission expansion is involved.
- Browser `user` permits status/list/show and interactive add/password plus forced role/delete for **other accounts only**. Self changes, generated passwords, key commands and recovery are denied there; typed Accounts supports the separately bounded self/generated/key workflows.
- Browser SSH stop/disconnect/reset and host-key mutation are denied; typed SSH Settings has its own safe owner path. Do not claim full browser-shell parity.
- Browser `reboot` and owner-relative `exit` are supported. First-admin provisioning uses normal `user add` on UART0; unavailable-database recovery is UART0-only. The legacy `user bootstrap` and web credential commands no longer exist.