Bug#1127331: libnss-mdns: DNS tried before mDNS despite configuration for mdns4_minimal first
On Mon, 09 Feb 2026 at 19:49:56 +0100, Aurelien Jarno wrote:
On 2026-02-07 10:50, Simon McVittie wrote:
$ strace -e openat,connect getent hosts remnant.local
...
I think there are two issues with this command:
- You should add a final dot, so that the search is not expanded with
the search domains from /etc/resolv.conf, which libnss-mdns obviously
can't handle and then goes to your configured recursive DNS resolver.
Good catch, that makes sense. Yes, I confirm that with the final dot, I
get one DNS resolution (which you've explained below as the SOA check
for .local, rather than actually resolving remnant.local., so that's
benign) followed by mDNS resolution via Avahi.
- You should use ahosts instead of hosts. hosts uses the deprecated
gethostbyname2() interface, which does explicit lookups with AF_INET
and AF_INET6. The latter is not supported given your nsswitch.conf.
I agree that `getent ahosts` is a better choice than `getent hosts`,
because it replicates the behaviour we'd expect from a modern
application that does an AF_UNSPEC lookup.
Alternatively you should either add mdns6_minimal entry or even better
use mdns_minimal instead (why isn't that the default noawdays?).
mdns_minimal is intentionally not the default because it was observed to
cause long delays (5+ seconds) in legacy software that implements IPv6
by doing one lookup with AF_INET6, followed by a second lookup with
AF_INET only after failure of the first lookup has been reported, in the
scenario where the responding host (remnant.local in my example) is
IPv4-only. In that scenario, it would wait 5 seconds for an IPv6
response that will never happen, and then do a second, IPv4 query which
gets a result immediately.
More modern software that does an AF_UNSPEC lookup, or AF_INET and
AF_INET6 in parallel ("happy eyeballs"), would be OK with mdns_minimal,
but Avahi/nss-mdns upstream specifically asked us not to make that the
default. Because mDNS is inherently a local LAN protocol, the reasons to
prefer IPv6 don't really apply to it: RFC1918 and RFC3927 addresses are
readily available, even if globally-routable IPv4 addresses are not.
mdns6_minimal is only provided for completeness, and is basically
pointless: everyone should use either mdns_minimal or mdns4_minimal.
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("127.0.0.53")}, 16) = 0
This one is due to libnss-mdns doing a SOA lookup of the .local domain.
This is by design in libnss-mdns, which implements the heuristic
described in https://support.apple.com/en-us/HT201275. This is not
linked with glibc.
Yes, that makes sense. We can tell it's this because it happens after
/etc/hosts is opened, which means it's after the "files" step in
nsswitch.conf.
smcv
Bug#1127331: libnss-mdns: DNS tried before mDNS despite configuration for mdns4_minimal first
Hi,
On 2026-02-07 10:50, Simon McVittie wrote:
> Control: retitle -1 libnss-mdns: DNS tried before mDNS despite configuration
> for mdns4_minimal first
> Control: tags -1 + moreinfo
>
> On Sat, 07 Feb 2026 at 10:54:21 +0900, 황병주 wrote:
> > mDNS name resolution via libnss-mdns is completely broken on Debian trixie
> > with glibc 2.41. The NSS module is loaded but never called, causing .local
> > hostnames to fall through to DNS instead of being resolved via mDNS.
>
> I was unable to reproduce this on a trixie machine.
>
> Steps:
>
> 1. Install libnss-mdns and avahi-daemon on Debian trixie (glibc 2.41)
> . Have another host ($OTHER) with mDNS, on the same network
> 3. Edit /etc/nsswitch.conf to contain:
>hosts: files dns
> 4. getent hosts $OTHER.local; echo $? -> no output, exit status 2
> 5. Edit /etc/nsswitch.conf to contain:
>hosts: files mdns4_minimal [NOTFOUND=return] dns
> 6. getent hosts $OTHER.local; echo $? -> resolved, exit status 0
>
> But, something that I *do* observe in this configuration is that a strace'd
> getent process does a connect() to my DNS server (in my case it's
> systemd-resolved) *before* connecting to the Avahi socket:
>
> $ strace -e openat,connect getent hosts remnant.local
> ...
I think there are two issues with this command:
- You should add a final dot, so that the search is not expanded with
the search domains from /etc/resolv.conf, which libnss-mdns obviously
can't handle and then goes to your configured recursive DNS resolver.
- You should use ahosts instead of hosts. hosts uses the deprecated
gethostbyname2() interface, which does explicit lookups with AF_INET
and AF_INET6. The latter is not supported given your nsswitch.conf.
On the other hand ahosts uses getaddrinfo() with AF_UNSPEC.
Alternatively you should either add mdns6_minimal entry or even better
use mdns_minimal instead (why isn't that the default noawdays?).
> openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2",
> O_RDONLY|O_CLOEXEC) = 3
> connect(3, {sa_family=AF_INET, sin_port=htons(53),
> sin_addr=inet_addr("127.0.0.53")}, 16) = 0
> connect(3, {sa_family=AF_INET, sin_port=htons(53),
> sin_addr=inet_addr("127.0.0.53")}, 16) = 0
> connect(3, {sa_family=AF_INET, sin_port=htons(53),
> sin_addr=inet_addr("127.0.0.53")}, 16) = 0
This three lookups are likely due to the missing final dot and the use of
hosts instead of ahosts.
> openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
> connect(3, {sa_family=AF_INET, sin_port=htons(53),
> sin_addr=inet_addr("127.0.0.53")}, 16) = 0
This one is due to libnss-mdns doing a SOA lookup of the .local domain.
This is by design in libnss-mdns, which implements the heuristic
described in https://support.apple.com/en-us/HT201275. This is not
linked with glibc.
> connect(3, {sa_family=AF_FILE, path="/run/avahi-daemon/socket"}, 110) = 0
> REDACTED remnant.local
Regards
Aurelien
--
Aurelien Jarno GPG: 4096R/1DDD8C9B
[email protected] http://aurel32.net
Bug#1127331: libnss-mdns: DNS tried before mDNS despite configuration for mdns4_minimal first
Control: retitle -1 libnss-mdns: DNS tried before mDNS despite configuration
for mdns4_minimal first
Control: tags -1 + moreinfo
On Sat, 07 Feb 2026 at 10:54:21 +0900, 황병주 wrote:
mDNS name resolution via libnss-mdns is completely broken on Debian trixie
with glibc 2.41. The NSS module is loaded but never called, causing .local
hostnames to fall through to DNS instead of being resolved via mDNS.
I was unable to reproduce this on a trixie machine.
Steps:
1. Install libnss-mdns and avahi-daemon on Debian trixie (glibc 2.41)
. Have another host ($OTHER) with mDNS, on the same network
3. Edit /etc/nsswitch.conf to contain:
hosts: files dns
4. getent hosts $OTHER.local; echo $? -> no output, exit status 2
5. Edit /etc/nsswitch.conf to contain:
hosts: files mdns4_minimal [NOTFOUND=return] dns
6. getent hosts $OTHER.local; echo $? -> resolved, exit status 0
But, something that I *do* observe in this configuration is that a
strace'd getent process does a connect() to my DNS server (in my case
it's systemd-resolved) *before* connecting to the Avahi socket:
$ strace -e openat,connect getent hosts remnant.local
...
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libnss_mdns4_minimal.so.2",
O_RDONLY|O_CLOEXEC) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("127.0.0.53")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("127.0.0.53")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("127.0.0.53")}, 16) = 0
openat(AT_FDCWD, "/etc/hosts", O_RDONLY|O_CLOEXEC) = 3
connect(3, {sa_family=AF_INET, sin_port=htons(53),
sin_addr=inet_addr("127.0.0.53")}, 16) = 0
connect(3, {sa_family=AF_FILE, path="/run/avahi-daemon/socket"}, 110) = 0
REDACTED remnant.local
I think that could result in the same symptom that you reported, where an
ISP DNS server that (inappropriately) intercepts resolution of .local
names results in the ISP's DNS resolution being used before
mdns4_minimal gets an opportunity to step in.
I wonder whether this means that glibc is doing multiple lower-level NSS
lookups for a single higher-level getent operation, only some of which
are of a form implemented by mdns4_minimal, and as a result DNS
effectively takes precedence over mDNS even though the configuration
says the opposite?
LD_DEBUG reveals the root cause — glibc tries to resolve all NSS functions
and marks each missing one as fatal:
$ LD_DEBUG=symbols getent hosts pluto.local 2>&1 | grep fatal | head -5
symbol=_nss_mdns4_minimal_endaliasent; ... undefined symbol (fatal)
symbol=_nss_mdns4_minimal_endetherent; ... undefined symbol (fatal)
symbol=_nss_mdns4_minimal_endgrent; ... undefined symbol (fatal)
symbol=_nss_mdns4_minimal_endhostent; ... undefined symbol (fatal)
symbol=_nss_mdns4_minimal_endnetent; ... undefined symbol (fatal)
I think this is misleading: ld.so often says "fatal" when it doesn't
really mean it. I think this should be read as "fatal to this particular
attempt to resolve the symbol", and not as "fatal to any attempt to look
up NSS".
smcv

