Skip to content

dns: add experimental c-ares cache and lookup support#62361

Draft
mcollina wants to merge 1 commit intonodejs:mainfrom
mcollina:dns-cares-cache-experimental
Draft

dns: add experimental c-ares cache and lookup support#62361
mcollina wants to merge 1 commit intonodejs:mainfrom
mcollina:dns-cares-cache-experimental

Conversation

@mcollina
Copy link
Member

@mcollina mcollina commented Mar 21, 2026

Add --experimental-dns-cache-max-ttl to expose c-ares query cache, and --experimental-dns-lookup-cares to route dns.lookup() through ares_getaddrinfo instead of libuv's blocking getaddrinfo.

Refs: #57641


I had AI compile a record of this history of this change.

The Two DNS Resolution Paths in Node.js: A History

The Original Design

Node.js originally used c-ares exclusively for all DNS operations. c-ares was
the natural choice: the standard POSIX getaddrinfo(3) is synchronous and
blocking, fundamentally incompatible with an event-driven runtime. c-ares
provides non-blocking DNS resolution that integrates with event loops.

c-ares was part of Node.js from its earliest days, initially living inside the
libuv source tree before being moved to deps/cares in 2012.

Why dns.lookup() Was Added

dns.lookup() was introduced because c-ares, as a pure DNS stub resolver,
could not replicate the full behavior of the operating system's name resolution:

  • /etc/hosts for static name-to-IP mappings
  • NSS (/etc/nsswitch.conf) to determine resolution order (files, dns, mdns)
  • mDNS/Bonjour for local network service discovery
  • LLMNR on Windows
  • System-wide DNS cache configurations
  • Correct localhost handling per the OS

As Ben Noordhuis explained in nodejs/node#49394:

"In the deep past node used c-ares exclusively. dns.lookup was added
explicitly because lots of things only work with the system resolver.
Ex. mDNS."

The workaround was to run getaddrinfo(3) on libuv's threadpool (default 4
threads) to simulate async behavior from JavaScript's perspective.

The Current Split

Feature dns.lookup() dns.resolve*()
Backend libuv getaddrinfo (threadpool) c-ares channel (event loop)
Reads /etc/hosts Yes (via OS) No
NSS/mDNS support Yes No
Truly async No (threadpool) Yes
DNS caching No (delegates to OS) Yes (c-ares qcache)
dns.setServers() effect None Yes
Used by http/net Yes (default) No (unless custom lookup)

net.connect(), http.request(), and all high-level APIs use dns.lookup()
by default. The c-ares resolver (dns.resolve*()) is only used when explicitly
called or when a custom lookup function is provided to an HTTP agent or
socket.

Known Problems

Threadpool Starvation

The most critical issue, reported since 2012 (nodejs/node-v0.x-archive#2868,
nodejs/node#8436). When DNS servers are slow or unreachable,
getaddrinfo(3) calls saturate libuv's shared threadpool and block unrelated
file system I/O and crypto operations.

No DNS Caching

Node.js performs no DNS caching in dns.lookup(). Every call goes through
getaddrinfo(3) from scratch, delegating caching entirely to the OS. Many
production environments (containers, minimal Linux images) have no local DNS
cache daemon, causing unnecessary latency and load on DNS servers.

Behavioral Inconsistency

dns.lookup() and dns.resolve4() can return different results for the same
hostname because they use entirely different resolution mechanisms. This
confuses developers. dns.setServers() has no effect on dns.lookup().

HTTP Timeout Does Not Cover DNS

http.request()'s setTimeout does not include the dns.lookup() phase. A
request with a 10ms timeout can hang for seconds during DNS resolution
(nodejs/node#8436).

Failed Unification Attempts

2015: Pure JavaScript DNS Resolver (#1843)

Proposed adding a new pure JS resolver as default, relegating c-ares behind a
--use-old-dns flag. Closed in 2016 because the author could not match the
speed of the existing resolver from JavaScript.

2015: Replace c-ares with node-dns (#1013)

Discussion about replacing c-ares with Tim Fontaine's node-dns module. Ben
Noordhuis was in favor, but Fontaine himself concluded his own module was not
ready for core inclusion, citing technical debt and the impossibility of
faithfully implementing dns.lookup() behavior from userspace.

2017: Tracking DNS Features (#14713)

Tracked growing limitations of c-ares (no AXFR, no DNSSEC at the time, limited
TTL exposure). Considered writing a custom DNS library. Went stale.

2023: Global Flag for Async DNS (#49394)

Proposed a Go-like flag to globally switch dns.lookup() to use the async
c-ares resolver. Ben Noordhuis rejected a global flag as "probably a bad idea"
because "lots of things only work with the system resolver." His
counter-proposal was to improve libuv's threadpool scaling and add a transparent
DNS cache. Neither was implemented. The issue was closed by the stale bot.

What Changed: ares_getaddrinfo (c-ares 1.16.0, 2020)

c-ares 1.16.0 introduced ares_getaddrinfo(), an async equivalent to POSIX
getaddrinfo() with:

  • RFC 6724-compliant address sorting
  • TTL information in results
  • Alias data
  • /etc/hosts reading (with caching since c-ares 1.22.0)
  • Correct localhost handling per RFC 6761

The original gap between c-ares and the system resolver has significantly
narrowed. The main remaining difference is NSS plugin support (mDNS, LDAP,
NIS), which ares_getaddrinfo does not implement.

Bun uses ares_getaddrinfo as its default dns.lookup() backend on Linux,
demonstrating the approach is viable for a JavaScript runtime.

Add --experimental-dns-cache-max-ttl to expose c-ares query cache,
and --experimental-dns-lookup-cares to route dns.lookup() through
ares_getaddrinfo instead of libuv's blocking getaddrinfo.

Combined, these flags enable fast cached async DNS for all HTTP/net
connections without code changes.

Refs: nodejs#57641
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/net

@mcollina mcollina added the semver-minor PRs that contain new features and should be released in the next minor version. label Mar 21, 2026
@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. semver-minor PRs that contain new features and should be released in the next minor version.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants