Skip to content

[mDNS] IPv6 is never enabled on the predefined ETH interface when Wi-Fi predef netifs are disabled #1120

Description

@code-overload42

Answers checklist.

  • I have read the documentation for esp-protocols components and the issue is not addressed there.
  • I have updated my esp-protocols branch (master or release) to the latest version and checked that the issue is present there.
  • I have searched the issue tracker for a similar issue and not found a similar issue.

What component are you using? If you choose Other, provide details in More Information.

mDNS

component version

1.11.3

IDF version.

v5.5.4

More Information.

Environment

  • mdns component: 1.11.3 (bug still present on master mdns_netif.c as of 2026-08-06)
  • ESP-IDF: v5.5.4
  • Target: ESP32-P4 (Ethernet via internal EMAC + IP101, no Wi-Fi), but target-independent
  • Relevant config:
    • CONFIG_MDNS_PREDEF_NETIF_STA=n
    • CONFIG_MDNS_PREDEF_NETIF_AP=n
    • CONFIG_MDNS_PREDEF_NETIF_ETH=y (default)
    • CONFIG_MDNS_MAX_INTERFACES=1
    • CONFIG_LWIP_IPV6=y

Describe the bug

On an Ethernet-only build (Wi-Fi predefined netifs compiled out), the responder never
enables its IPv6 PCB: it never joins ff02::fb and serves no AAAA records, an AAAA query
for the hostname gets an NSEC "no such record" while the A record is answered normally.
IPv4 mDNS works fine, which makes this very hard to notice.

Real-world impact: a Matter-over-Ethernet device becomes permanently unreachable for
IPv6-only controllers (Google Home hubs). Commissioning succeeds (the phone tolerates
IPv4), then every endpoint shows "unresponsive" forever, while Apple Home keeps working,
we lost days on this before tracing it into the component.

Root cause

s_esp_netifs[] in mdns_netif.c is built from #if CONFIG_MDNS_PREDEF_NETIF_* entries,
so with STA/AP disabled, ETH occupies slot 0 while its preset enum value MDNS_IF_ETH
is 2. The IP_EVENT_GOT_IP6 case resolves the event's netif to its slot via
get_if_from_netif(), then passes that slot to post_enable_pcb() /
post_announce_pcb(), whose parameter is a mdns_predef_if_t preset and which
re-convert via mdns_if_from_preset():

case IP_EVENT_GOT_IP6: {
    ip_event_got_ip6_t *event = (ip_event_got_ip6_t *) event_data;
    mdns_if_t mdns_if = get_if_from_netif(event->esp_netif);   /* returns SLOT (0 here) */
    if (mdns_if >= MDNS_MAX_INTERFACES) {
        return;
    }
    post_enable_pcb(mdns_if, MDNS_IP_PROTOCOL_V6);             /* expects mdns_predef_if_t */
    post_announce_pcb(mdns_if, MDNS_IP_PROTOCOL_V4);           /* expects mdns_predef_if_t */
    post_browse_send_by_ip_protocol_action(mdns_if, MDNS_IP_PROTOCOL_V6); /* takes slot, OK */
}
break;
static inline void post_enable_pcb(mdns_predef_if_t preset_if, mdns_ip_protocol_t protocol)
{
    post_custom_action(mdns_if_from_preset(preset_if),
                       protocol == MDNS_IP_PROTOCOL_V4 ? MDNS_EVENT_ENABLE_IP4 : MDNS_EVENT_ENABLE_IP6);
}

With ETH in slot 0, post_enable_pcb(0, V6) is interpreted as MDNS_IF_STA;
mdns_if_from_preset(MDNS_IF_STA) finds no STA entry and returns MDNS_MAX_INTERFACES;
post_custom_action() rejects that with ESP_ERR_INVALID_STATE (return value ignored),
so the ENABLE_IP6 action is silently dropped on every GOT_IP6 event.

With the default config (STA, AP, ETH all compiled in) the slot indices coincide with the
enum values (0/1/2), the double conversion is a numerical no-op, and the bug is invisible.
Any predef set other than exactly {STA, AP, ETH} breaks the mapping, e.g. with only
STA disabled, ETH lands in slot 1, which maps to preset MDNS_IF_AP, enabling IPv6 on the
wrong interface.

Note the inconsistency inside the same block: post_browse_send_by_ip_protocol_action()
correctly receives the slot (its other call sites are passed
mdns_if_from_preset(MDNS_IF_X)), while its two neighbors receive the slot but expect the
preset.

Steps to reproduce

  1. Take any Ethernet + mdns example, enable IPv6 (CONFIG_LWIP_IPV6=y).
  2. Set CONFIG_MDNS_PREDEF_NETIF_STA=n and CONFIG_MDNS_PREDEF_NETIF_AP=n.
  3. Boot on a network with IPv6 so IP_EVENT_GOT_IP6 fires (link-local + SLAAC).
  4. From a host: dns-sd -G v4v6 <hostname>.local. (macOS) or
    avahi-resolve -6 -n <hostname>.local.

Expected: A and AAAA records.
Observed: only A; AAAA is NSEC-denied; tcpdump shows no MLD join for ff02::fb and no
IPv6 mDNS traffic at all.

Instrumenting the case with

ESP_LOGW(TAG, "GOT_IP6 diag: netif=%p slot=%d preset-mapped=%d max=%d",
         (void *)event->esp_netif, (int)mdns_if,
         (int)mdns_if_from_preset((mdns_predef_if_t)mdns_if), (int)MDNS_MAX_INTERFACES);

prints slot=0 preset-mapped=1 max=1 on every GOT_IP6, confirming the action is posted
for an invalid interface and discarded.

Suggested fix

The slot is already resolved, post the action directly instead of re-converting:

post_custom_action(mdns_if, MDNS_EVENT_ENABLE_IP6);
post_custom_action(mdns_if, MDNS_EVENT_ANNOUNCE_IP4);

(or change post_enable_pcb()/post_announce_pcb() to take the slot and move
mdns_if_from_preset() into the literal-preset call sites). While there: announcing V4
on a GOT_IP6 event looks surprising, if intentional, a comment would help.

Workaround for affected users

After network-up (or from an IP_EVENT_GOT_IP6 handler), call:

mdns_netif_action(eth_netif, MDNS_EVENT_ENABLE_IP6 | MDNS_EVENT_ANNOUNCE_IP6);

mdns_netif_action() resolves the netif handle directly and bypasses the broken preset
conversion. Verified on hardware: AAAA records appear immediately and (in our case) Google
Home recovered.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions