Answers checklist.
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
- Take any Ethernet + mdns example, enable IPv6 (
CONFIG_LWIP_IPV6=y).
- Set
CONFIG_MDNS_PREDEF_NETIF_STA=n and CONFIG_MDNS_PREDEF_NETIF_AP=n.
- Boot on a network with IPv6 so
IP_EVENT_GOT_IP6 fires (link-local + SLAAC).
- 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.
Answers checklist.
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
mastermdns_netif.cas of 2026-08-06)CONFIG_MDNS_PREDEF_NETIF_STA=nCONFIG_MDNS_PREDEF_NETIF_AP=nCONFIG_MDNS_PREDEF_NETIF_ETH=y(default)CONFIG_MDNS_MAX_INTERFACES=1CONFIG_LWIP_IPV6=yDescribe the bug
On an Ethernet-only build (Wi-Fi predefined netifs compiled out), the responder never
enables its IPv6 PCB: it never joins
ff02::fband serves no AAAA records, an AAAA queryfor 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[]inmdns_netif.cis built from#if CONFIG_MDNS_PREDEF_NETIF_*entries,so with STA/AP disabled, ETH occupies slot 0 while its preset enum value
MDNS_IF_ETHis 2. The
IP_EVENT_GOT_IP6case resolves the event's netif to its slot viaget_if_from_netif(), then passes that slot topost_enable_pcb()/post_announce_pcb(), whose parameter is amdns_predef_if_tpreset and whichre-convert via
mdns_if_from_preset():With ETH in slot 0,
post_enable_pcb(0, V6)is interpreted asMDNS_IF_STA;mdns_if_from_preset(MDNS_IF_STA)finds no STA entry and returnsMDNS_MAX_INTERFACES;post_custom_action()rejects that withESP_ERR_INVALID_STATE(return value ignored),so the
ENABLE_IP6action is silently dropped on everyGOT_IP6event.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 thewrong 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 thepreset.
Steps to reproduce
CONFIG_LWIP_IPV6=y).CONFIG_MDNS_PREDEF_NETIF_STA=nandCONFIG_MDNS_PREDEF_NETIF_AP=n.IP_EVENT_GOT_IP6fires (link-local + SLAAC).dns-sd -G v4v6 <hostname>.local.(macOS) oravahi-resolve -6 -n <hostname>.local.Expected: A and AAAA records.
Observed: only A; AAAA is NSEC-denied; tcpdump shows no MLD join for
ff02::fband noIPv6 mDNS traffic at all.
Instrumenting the case with
prints
slot=0 preset-mapped=1 max=1on everyGOT_IP6, confirming the action is postedfor an invalid interface and discarded.
Suggested fix
The slot is already resolved, post the action directly instead of re-converting:
(or change
post_enable_pcb()/post_announce_pcb()to take the slot and movemdns_if_from_preset()into the literal-preset call sites). While there: announcing V4on a
GOT_IP6event looks surprising, if intentional, a comment would help.Workaround for affected users
After network-up (or from an
IP_EVENT_GOT_IP6handler), call:mdns_netif_action()resolves the netif handle directly and bypasses the broken presetconversion. Verified on hardware: AAAA records appear immediately and (in our case) Google
Home recovered.