The Hidden Layers of getaddrinfo
DNS behavior can vary in surprising ways depending on your operating system and the tools you use. A Python script might resolve a hostname via /etc/hosts while dig ignores it entirely. Switching from Ubuntu to Alpine Linux can subtly alter how DNS queries work, and macOS performs DNS caching while many Linux systems do not by default. These inconsistencies all trace back to one central function: getaddrinfo.
Where getaddrinfo Lives
getaddrinfo is part of libc, the standard C library. Since there are multiple implementations of libc, there are multiple versions of getaddrinfo:
- glibc (GNU libc), used by most mainstream Linux distributions
- musl libc, used by Alpine Linux and other lightweight distributions
- macOS libc, which is Apple's own implementation
Each implements getaddrinfo differently, and those differences have real consequences for how DNS queries behave.
Not Everyone Calls getaddrinfo
Despite being the standard interface for name resolution, getaddrinfo is not used by every program. Languages like Python, Ruby, and Node typically call it, and Go uses it in certain configurations. However, some tools implement their own DNS resolution logic instead:
diguses a custom resolver because it needs finer control over DNS queries thangetaddrinfoprovides.- Go offers a pure-Go DNS resolver as an alternative when CGo is not used.
- There's a Ruby
resolvlibrary that bypassesgetaddrinfoentirely. - DNS-over-HTTPS (DoH) is not supported by
getaddrinfo, so browsers implementing DoH must use their own resolver.
This explains why different tools on the same system can produce different DNS results.
Error Messages Can Be Misleading
Because getaddrinfo is so widely used, its name often appears in DNS error output. Consider a Python script attempting to resolve a nonexistent domain:
import requests
requests.get("http://xyxqqx.com")
The resulting error is:
Traceback (most recent call last):
File "/usr/lib/python3.10/site-packages/urllib3/connection.py", line 174, in _new_conn
conn = connection.create_connection(
File "/usr/lib/python3.10/site-packages/urllib3/util/connection.py", line 72, in create_connection
for res in socket.getaddrinfo(host, port, family, socket.SOCK_STREAM):
File "/usr/lib/python3.10/socket.py", line 955, in getaddrinfo
for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
socket.gaierror: [Errno -2] Name or service not known
That message — socket.gaierror: [Errno -2] Name or service not known — does not mention DNS or domains at all. Without knowing that socket.getaddrinfo wraps the libc function, it's hard to interpret the error as meaning "that domain doesn't exist."
macOS Takes a Different Path: mDNSResponder
On Linux, getaddrinfo reads /etc/resolv.conf to determine which DNS resolver to contact for queries. A typical configuration looks like:
# Generated by NetworkManager
nameserver 192.168.1.1
nameserver fd13:d987:748a::1
That directs queries to 192.168.1.1 on port 53 — in this case, a router's DNS resolver.
macOS works differently. Instead of consulting /etc/resolv.conf, its version of getaddrinfo communicates with mDNSResponder, a system daemon that handles DNS resolution and caching. This is why macOS has built-in DNS caching while Linux does not, unless you explicitly configure something like systemd-resolved or a similar service. The cache can be flushed using dscacheutil.
The musl vs. glibc Divide
The two Linux implementations of getaddrinfo are not equivalent either. The most significant difference is that musl libc does not support TCP-based DNS queries, a limitation that can cause failures with certain DNS responses. There are other behavioral differences as well:
- Search domains from
/etc/resolv.confare handled slightly differently between the two implementations. - musl does not support
nsswitch.conf, a glibc feature that adds another layer of configuration on top of the standard resolver settings.
Caching on Linux: The Role of nscd
Linux historically has had a name service cache daemon, nscd, which getaddrinfo can call to cache DNS lookups. It's present on many systems and can be checked interactively:
$ nscd
child exited with status 4
However, nscd has a reputation for instability and has largely fallen out of favor. In practice, admins who want DNS caching on Linux tend to use a DNS forwarder like dnsmasq or systemd-resolved rather than relying on nscd.
Why This Matters
It's striking that such a foundational library function behaves so differently across platforms. macOS designers reasonably chose to centralize DNS caching in mDNSResponder, while Linux left it to the discretion of the user or distribution. Some programs wisely skip getaddrinfo when they need capabilities it lacks. All of this means that reasoning about DNS behavior requires knowing not just what you're resolving, but which getaddrinfo implementation — if any — is actually doing the work.



