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:

  • dig uses a custom resolver because it needs finer control over DNS queries than getaddrinfo provides.
  • Go offers a pure-Go DNS resolver as an alternative when CGo is not used.
  • There's a Ruby resolv library that bypasses getaddrinfo entirely.
  • 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.conf are 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.