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.
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# mDNS 1.12.0 multicast membership regression
|
||||
|
||||
This is a narrow dependency correctness fix for the lwIP backend, not a general
|
||||
patch framework or a revival of the abandoned Phase 9 patches. Dependency
|
||||
versions, the component manifest/lock, managed sources, socket backend, and
|
||||
application mDNS lifecycle policy are unchanged.
|
||||
|
||||
## Defects and fix
|
||||
|
||||
In the inspected `mdns_networking_lwip.c`, upstream `pcb_if_deinit()` (original
|
||||
lines 267–277) only leaves a group when the interface's **last** protocol bit is
|
||||
cleared. Removing one family while the other remains active therefore leaks a
|
||||
membership reference. Restoration joins again; repeated partial transitions
|
||||
accumulate references in lwIP's bounded group-use counter. Deinitializing an
|
||||
already-disabled family can also issue an unmatched leave.
|
||||
|
||||
The generated copy checks the requested family's active bit, returns immediately
|
||||
if absent, and leaves that family's group before clearing its bit. It clears
|
||||
`ready` only after the last family on that interface is removed, and frees the
|
||||
shared PCB only if no other interface remains ready.
|
||||
|
||||
In `pcb_if_init()` (original lines 282–301), a successful group join followed by
|
||||
`pcb_init()` failure was not unwound. The copy attempts a leave before returning
|
||||
the **original** PCB error. No readiness or protocol bit is published on failure.
|
||||
|
||||
These operations still run through upstream's existing lwIP-thread wrappers.
|
||||
No new locks, allocations, task ownership, retry state, or public API are added.
|
||||
Upstream `join_group()` is unchanged, including its refusal to act when the netif
|
||||
is absent/down. Leaves remain best effort: the patch guarantees balanced leave
|
||||
**attempts**, not successful lwIP cleanup after an interface disappears. It does
|
||||
not retry a failed leave or retain a PCB solely because leave failed; physical
|
||||
netif teardown remains responsible for its own membership cleanup.
|
||||
|
||||
## Build integration
|
||||
|
||||
1. Root `CMakeLists.txt` includes `cmake/mdns_membership.cmake` **after** IDF's
|
||||
`project()`, when component targets and resolved versions are available.
|
||||
2. The include obtains `espressif__mdns`'s `COMPONENT_DIR`, `COMPONENT_LIB`, and
|
||||
`COMPONENT_VERSION` via IDF component properties. Socket-backend builds skip
|
||||
the overlay entirely; the fix is only relevant to the lwIP source.
|
||||
3. The resolved component version and manifest version must both be exactly
|
||||
`1.12.0`. Using IDF's configured Python interpreter, the helper verifies the
|
||||
complete source SHA-256, then performs two exact, unique replacements.
|
||||
4. It writes only `${CMAKE_BINARY_DIR}/mdns_membership/mdns_networking_lwip.c`.
|
||||
Unchanged output is not rewritten, avoiding gratuitous rebuilds. Everything
|
||||
outside the two replacement regions, including provenance/license headers,
|
||||
remains byte-for-byte intact. The actual upstream file identifies itself as
|
||||
Apache-2.0, copyright 2022–2025 Espressif; it is not relabeled as GPL. Existing
|
||||
project GPL/license material is untouched.
|
||||
5. The include replaces exactly one matching entry in the existing component
|
||||
target's `SOURCES` property, accepting absolute or component-relative paths.
|
||||
All other source entries and all component compile definitions, include paths,
|
||||
dependencies, and target linkage are retained. The original file is not also
|
||||
compiled. An unexpected source list fails configuration.
|
||||
6. The helper, original source, manifest, and generated copy are registered as
|
||||
configure dependencies. Reconfiguration regenerates/verifies the copy; a
|
||||
clean build simply recreates it. The CMake include itself is automatically a
|
||||
CMake input. Version/hash/replacement errors fail configure rather than
|
||||
silently compiling an unpatched dependency.
|
||||
|
||||
The integration uses the component's existing target; no whole-component copy,
|
||||
managed in-place edits, dependency overrides, manifest changes, or extra source
|
||||
library are involved. The inspected upstream CMake file offers no per-source
|
||||
substitution option; replacing the target source after `project()` is the local
|
||||
integration point.
|
||||
|
||||
### Exact reviewed baseline
|
||||
|
||||
- Component: `espressif/mdns`, version `1.12.0`.
|
||||
- Manifest repository: `espressif/esp-protocols`, `components/mdns`.
|
||||
- Manifest commit: `db06b19b7be729c163d346f62ec0eba01047b7f1` (provenance;
|
||||
guards are the resolved/manifest version and complete source hash).
|
||||
- Source: `managed_components/espressif__mdns/mdns_networking_lwip.c`.
|
||||
- SHA-256: `adc139fa504a925ab644f21f8dce3659927f534e390a176b72b0ae3206c6a3ea`.
|
||||
|
||||
## Run the host tests
|
||||
|
||||
From the repository root, on a POSIX host with Python 3, CMake >= 3.16, and a C11
|
||||
compiler:
|
||||
|
||||
```sh
|
||||
./tests/mdns_membership/run.py
|
||||
# Optional compiler selection (CC is an executable, not a shell command):
|
||||
CC=clang ./tests/mdns_membership/run.py
|
||||
```
|
||||
|
||||
The managed component must already be installed. The runner uses temporary
|
||||
directories, does not fetch dependencies, does not build firmware, and disables
|
||||
core dumps for deliberate negative tests. It generates the overlay with the
|
||||
**same helper used by configure**, extracts the actual patched state declarations
|
||||
and six functions (`pcb_init`, `pcb_deinit`, `mdns_priv_if_ready`,
|
||||
`is_any_pcb_in_use`, `pcb_if_init`, `pcb_if_deinit`), and compiles them with
|
||||
`-std=c11 -Wall -Wextra -Werror -pedantic`. Only group operations and low-level
|
||||
UDP APIs are mocked; the lifecycle logic under test is not reimplemented.
|
||||
|
||||
Coverage:
|
||||
|
||||
- 512 IPv4 loss/restoration cycles while IPv6 stays active, and 512 inverse
|
||||
cycles; exact group references and readiness checked after each transition.
|
||||
- Repeated disabled-family deinit, final-family teardown, and duplicate init.
|
||||
- Join failure without an unmatched leave or PCB allocation.
|
||||
- 512 allocation failures and 512 bind failures per family, with successful
|
||||
joins unwound, no state publication, original error preserved, and recovery.
|
||||
- Leave failure on teardown and unwind: one attempt, no repeated disabled leave,
|
||||
original allocation error retained.
|
||||
- 512 cycles per family with a second interface holding the shared PCB; exactly
|
||||
one allocation, no premature free, and one final removal.
|
||||
- Separate negative controls restoring each original buggy function must fail
|
||||
the very same executable harness.
|
||||
- Mock-IDF CMake configure fixtures for absolute/relative source replacement,
|
||||
unchanged unrelated source, repeat configure without rewriting, socket bypass,
|
||||
missing/duplicate networking source rejection, resolved-version rejection,
|
||||
and full-source hash rejection. Manifest-version rejection is tested through
|
||||
the helper CLI. Rejected fresh helper runs must not produce output.
|
||||
- Original managed networking bytes are checked unchanged after the run.
|
||||
|
||||
These are host unit/configure tests, **not** a real ESP-IDF integration build or
|
||||
hardware/network test. The real `join_group()`, lwIP IGMP/MLD counters, scheduling,
|
||||
and network teardown are not exercised by mocks. Run the normal `pio run`
|
||||
separately (never concurrently with another full build) to validate actual IDF
|
||||
integration. In its generated `compile_commands.json`, the mDNS networking entry
|
||||
must point to the build-local `mdns_membership/mdns_networking_lwip.c`, with no
|
||||
original managed networking entry; other mDNS sources must remain managed paths.
|
||||
On device, exercise repeated IPv4-only and IPv6-only loss/restoration while the
|
||||
other family remains active, final network teardown/recovery, and continued
|
||||
mDNS discovery. No hardware result is implied by the host PASS output.
|
||||
|
||||
## Maintenance / removal
|
||||
|
||||
Do **not** update the expected hash merely to make a new dependency build. On
|
||||
any mismatch, review the entire changed networking source and component CMake,
|
||||
especially protocol state, join/leave behavior, netif-down semantics, and shared
|
||||
PCB lifetime. Establish whether upstream has fixed both defects first. A
|
||||
version bump is a separate explicitly reviewed change; this overlay does not
|
||||
select or upgrade the dependency.
|
||||
|
||||
If upstream fixes both paths, remove the root include and these narrowly scoped
|
||||
helper/test files (or replace the tests with suitable upstream coverage), then
|
||||
clean/reconfigure and check that only the upstream source compiles. If a local
|
||||
fix is still necessary, re-audit the exact new source/version, update the guards
|
||||
and exact replacements together, update this provenance record, rerun the host
|
||||
suite with its negative controls, and run a serial full firmware build. Never
|
||||
fall back to unguarded search/replace or silently skip a failed patch. A stale
|
||||
build-local copy is not authority: it must always be reproducible from the
|
||||
managed source plus the reviewed helper.
|
||||
Reference in New Issue
Block a user