DNS for the hopeless
DNS 101: the client protocol
A client sends a request (
QR query message) to a server and receives a response (
QR reply message).
Messages are limited to 512 bytes unless
EDNS is supported, servers listen on port
53/udp and reply to the client sending port. DNS-over-TCP/TLS/HTTPS/QUIC are the exception.
DNS is a central part of the internet, which makes it a delicate beast. The “DNS stack” varies with the system configuration (operating system, programming language runtime).
DNS on C
The standard C library (
libc1) provides an API for “protocol-independent nodename and service name translation”.
getaddrinfo() to obtain a list of IPs for a particular host name, and the standard C library takes care of the rest.2
getaddrinfo is part of the C POSIX library, and documented in RFC 3493, section 6.1.
The BIND DNS client library became the de facto standard API for Unix-like operating systems. It defines an API for the stub resolver3 deployment method, along with a configuration format. 4
The GNU C Library (
glibc) is the most common
libc implementation on Linux. It provides
getaddrinfo along with several non-standard (but documented5) extension APIs and, most importantly, is backed by the GNU Name Service Switch (NSS).
The main goal of
nss is to provide a configurable and extensible6 facade to internal system databases (e.g. users and groups) whose entries are obtained from multiple sources (e.g. a file, or a networked service) in a given order configured by a policy.
In the case of name resolution, the
hosts database is queried. Its (mostly) default policy is to use the
file source (
/etc/hosts) followed by the
dns source (the
glibc implementation of the “stub resolver” API).
musl is another popular
libc implementation that expectedly differs from
glibc, but still preserves relative compatibility by using the same stub resolver configuration format, and mimicking its default policy.7
Finally, the “stub resolver” API is also implemented by both libraries, however there’s no up to date documentation.8
The situation is considerably more complicated if the application uses any non-standard C API for service name translation.9 For example, if you face problems with domain name resolution on cURL you first need to determine whether it’s using
libc, but switching between them requires re-compiling
In summary: DNS resolution depends entirely on your system’s
libc implementation, which will likely use the “stub resolver” deployment method. Its name resolution process might involve different sources and protocols, and will (hopefully) at some point query some DNS recursive resolver. In case of
glibc, this process involves the use of the GNU NSS
DNS on Linux
In order to actually resolve a name, the “stub resolver” needs to be configured (via
resolver(5)) with the network address of the recursive resolver(s) it shall use. However, this configuration model falls short when it comes to dynamic network environments on which multiple programs concurrently and asynchronously supply and use nameserver information.
resolvconf is “a framework for keeping track of the system’s information about currently available nameservers”. By putting itself as “the intermediary between programs that supply nameserver information and applications that use that information”, it takes the role of managing the shared nameserver configuration database.11
The systemd project also (unsurprisingly) provides an alternative to nameserver configuration management, (unsurprisingly) called
systemd-resolved, which (unsurprisingly) also does a lot more than just that. It features a “local DNS stub listener”, a NSS plugin intended to replace
dns source, and a D-Bus API. 12.
Depending on the system configuration, the standard C library can end up using
systemd for name resolution by either using its “local DNS stub listener” as the configured nameserver for its “stub resolver”, or using its NSS plugin to replace the
glibc “stub resolver” entirely.
In summary: nameserver configuration can come from multiple sources, and is commonly managed by some implementation of the
resolvconf mechanism, but there’s also multiple ways in which this configuration can reach the
libc “stub resolver”.
For the purposes of this post, I refer to
libcas the C POSIX library plus widely agreed-upon extensions (such as BSD
libresolv) - not the Standard C library. ↩︎
Due to being protocol-independent, it’s up to the
libcimplementation to decide which naming service and protocol to use (and how to implement it), but even when DNS is used as naming service, performing a name translation hardly ever means simply making a DNS query.
Each implementation has a “resolver algorithm” (RFC 1034, section 5.3.3) which resembles a protocol on its own: read a local database (
/etc/hosts), cache positive/negative responses, query multiple nameservers serially/concurrently, etc. ↩︎
The section 5.3.1 of RFC 1034 defines the “stub resolver” as an architectural component that delegates the resolution function to a name server which supports recursive queries. ↩︎
hostname(7)for more information. ↩︎
muslalso obtains nameserver configuration from
/etc/resolv.confas well as resolving names first from
/etc/hosts, followed by an unicast DNS query. See
c-aresand its competitors. ↩︎
libcurldefaults to a threaded resolver backend backed by
libc. For more information, see https://curl.se/docs/faq.html#How_does_libcurl_resolve_host_na and https://everything.curl.dev/libcurl/names#name-resolver-backends. ↩︎
resolvconf(8). There’s also a good overview of its motivations on