Files
ESP32_Serial_Swiss_Army_Knife/tests/mdns_phase12
Commander1024 8902b25d78 Complete Phase 12 dual-stack networking
Add IPv6-aware Wi-Fi state, HTTPS/SSH listeners, mDNS service
reconciliation, and browser Wi-Fi administration.

Include a guarded build-local fix for mDNS 1.12.0 membership handling,
focused regression suites, and Phase 12 acceptance documentation.
2026-09-20 22:35:34 +02:00
..
2026-09-20 22:35:34 +02:00
2026-09-20 22:35:34 +02:00

Phase 12 mDNS owner host regression

Run python3 tests/mdns_phase12/run.py from the repository root. Uses the host C11 compiler and temporary fake SDK headers; compiles the actual production module. No PlatformIO, device operations, or generated firmware assets.

Covers notifications before initialization and while the project mutex is held; default unavailable SSH; fixed HTTPS443/SSH22 and empty TXT; repeated starts without duplicate records or hostname churn; record add/remove failures and retry; offline withdrawal; offline rename and retry; latest-state coalescing; init/hostname/instance failure latches and cleanup. Family tests cover lost IPv4 with surviving IPv6, IPv6-only/link-local readiness, no valid IPv6, netif down, stop with stale addresses, missed GOT_IP6, explicitly failed and silently dropped action submission, delayed upstream disable, missing STA netif, and no enable churn during ordinary healthy polls. TCP/IP fakes assert address reads occur in TCP/IP context; netif actions assert neither project mutex nor publication critical section is held. Actual multicast, component tasks, DNS conflicts, and sockets are not simulated.

Integration contract

  • Listener owners call void mdns_service_set_https_available(bool) / void mdns_service_set_ssh_available(bool) after successful listener creation and on unavailability. Calls use tiny portMUX critical sections, safe before initialization, with no blocking semaphore, allocation, callbacks, or component calls. Xtensa does not promise lock-free C11 atomic bool, so no atomics are required.
  • The sole Wi-Fi owner calls esp_err_t mdns_service_reconcile(void) periodically, even offline. No new arguments are needed: it looks up the permanent WIFI_STA_DEF netif and samples addresses via esp_netif_tcpip_exec. No project mutex or publication mux is held across SDK/component calls.
  • Reconciliation never initializes the responder. Before module init it returns invalid-state; after module init but before responder startup it is a successful no-op. Once initialized it applies listener states and family readiness, retrying failed operations on subsequent passes. Notifications converge on a later pass, not synchronously; an in-flight pass can briefly reflect an older state.
  • Existing start/reannounce also reconcile. Start remains gated by usable STA in either family. Stop now clears announcement expectation and requests family disable, even if netif still holds nonzero addresses; it does not destroy/reinitialize the responder. Errors are retained in the snapshot. Do not call lifecycle operations concurrently or while holding another service lock. The manager owns start/stop, not event callbacks.
  • Required effective SDK options remain CONFIG_MDNS_PREDEF_NETIF_STA=y, AP/ETH predefined interfaces disabled, CONFIG_LWIP_IPV4=y, CONFIG_LWIP_IPV6=y, CONFIG_MDNS_MAX_SERVICES >= 2. No configuration change was made for the repair.

Exact dependency contracts and limitations

Inspected installed Espressif mDNS 1.12.0 and ESP-IDF 5.5.0:

  • mdns_netif.c initializes IPv6 from esp_netif_get_ip6_linklocal, independently of IPv4. Its predefined handlers process disconnect and address acquisition, but not STA_LOST_IP. mdns_networking_lwip.c tracks family readiness separately from current addresses. mdns_send.c checks readiness before emitting A/AAAA, but can emit zero A if readiness remains true after IPv4 loss. Explicit family disable repairs that stale readiness, including when IPv6 survives.
  • mdns_send.c uses esp_netif_get_all_ip6, whose IDF implementation includes valid addresses, including deprecated addresses, excludes invalid/tentative/zero addresses, and reads lwIP state directly. Our sampling therefore runs in TCP/IP context and intentionally follows valid-address semantics, not preferred-only semantics. No address cache is passed to mDNS; it still reads addresses when serializing records.
  • mdns_netif_action is asynchronous, has no public readiness getter or acknowledgement, and post_custom_action returns ESP_OK even if action enqueue fails. A cached successful API return is not proof of applied state. Allocation errors retry on the next pass; silent losses require reassertion. Missing-family disables are idempotent and reasserted every poll. Present-family enables restart probes, so they are submitted on family-mask transitions and at a 30-second repair cadence, not every healthy interval. This also repairs late upstream disconnect actions and failed internal PCB initialization. There is no responder restart or service-record churn.
  • Public-only control cannot simultaneously guarantee acknowledged readiness and absolutely no periodic enable reprobes. Healthy families re-probe every 30 seconds as the explicit tradeoff. Recovery from silent dropped enables is on a subsequent repair cadence once the component queue/network resources make progress, not a hard deadline under sustained failure. Missing-family disable retries each poll. Polling, concurrent network changes, and component queuing leave a transient window in which stale/zero A responses can still escape; this is convergence, not an atomic packet-level filter. No upstream patch is included.
  • mdns_responder.c adds/removes records synchronously under its own mutex. Hostname setting waits for the component worker; instance setting queues work. Work per pass is one bounded address scan, at most one family action, and two record decisions; no additional tasks, queues, dynamic application storage, or retry loops. This is not a hard wall-clock guarantee for upstream blocking calls.

Target validation

In addition to the host suite, the updated src/mdns_service.c was compiled to a temporary object using its exact .pio/build/esp32-s3-devkitc-1-n16r8/compile_commands.json command and the installed Xtensa compiler / IDF 5.5 headers. Compilation passed. No full PlatformIO build, link, or hardware validation was performed.