dns: do not consult hosts file in reverse lookups

dns.reverse() is documented with dns.resolve*() as always performing DNS
queries without using hosts file configuration. However, c-ares
ares_gethostbyaddr() honors the channel lookup order, which can include
the files source and cause reverse lookups to return entries from
/etc/hosts.

Configure c-ares resolver channels with ARES_OPT_LOOKUPS set to "b" so
getHostByAddr uses DNS/PTR lookups only. This keeps dns.reverse()
aligned with dns.resolve*() and preserves the distinction from
system-resolution APIs such as dns.lookup() and dns.lookupService().

This is preferable to documenting the current behavior because hosts-
file reverse lookup results are platform-dependent and expose hostent
alias semantics rather than DNS PTR record semantics.

Signed-off-by: Archkon <180910180+Archkon@users.noreply.github.com>
PR-URL: https://github.com/nodejs/node/pull/64268
Fixes: https://github.com/nodejs/node/issues/64257
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Archkon authored and GitHub committed 2026-07-26 20:10:12 +02:00
1 parent 4a5eb1cf73
commit 54a5095764
4 files changed
+44 -15

No files matched your search

+4
View File
@@ -867,6 +867,10 @@ treated separately.
<!-- YAML
added: v0.1.16
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/64268
description: Reverse lookups no longer consult hosts files.
-->
* `ip` {string}
+3 -1
View File
@@ -1020,6 +1020,8 @@ void ChannelWrap::Setup() {
options.timeout = timeout_;
options.tries = tries_;
options.qcache_max_ttl = 0;
// Resolver APIs always perform DNS queries and must not consult hosts files.
options.lookups = const_cast<char*>("b");
int r;
if (!library_inited_) {
@@ -1033,7 +1035,7 @@ void ChannelWrap::Setup() {
/* We do the call to ares_init_option for caller. */
int optmask = ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_SOCK_STATE_CB |
ARES_OPT_TRIES | ARES_OPT_QUERY_CACHE;
ARES_OPT_TRIES | ARES_OPT_QUERY_CACHE | ARES_OPT_LOOKUPS;
if (max_timeout_ > 0) {
options.maxtimeout = max_timeout_;
-14
View File
@@ -76,17 +76,3 @@ dns.lookup('::1', common.mustSucceed((result, addressType) => {
assert.throws(() => dnsPromises.resolve('www.google.com', val), err);
});
// Windows doesn't usually have an entry for localhost 127.0.0.1 in
// C:\Windows\System32\drivers\etc\hosts
// so we disable this test on Windows.
// IBMi reports `ENOTFOUND` when get hostname by address 127.0.0.1
if (!common.isWindows && !common.isIBMi) {
dns.reverse('127.0.0.1', common.mustSucceed((domains) => {
assert.ok(Array.isArray(domains));
}));
(async function() {
assert.ok(Array.isArray(await dnsPromises.reverse('127.0.0.1')));
})().then(common.mustCall());
}
@@ -0,0 +1,37 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const dgram = require('dgram');
const dns = require('dns');
const dnstools = require('../common/dns');
const server = dgram.createSocket('udp4');
const resolver = new dns.Resolver();
const expectedHostname = 'reverse-dns-only.example';
server.on('message', common.mustCall((msg, { address, port }) => {
const parsed = dnstools.parseDNSPacket(msg);
const question = parsed.questions[0];
assert.strictEqual(question.domain, '1.0.0.127.in-addr.arpa');
assert.strictEqual(question.type, 'PTR');
server.send(dnstools.writeDNSPacket({
id: parsed.id,
questions: parsed.questions,
answers: [{
type: 'PTR',
domain: question.domain,
value: expectedHostname,
}],
}), port, address);
}));
server.bind(0, common.mustCall(() => {
resolver.setServers([`127.0.0.1:${server.address().port}`]);
resolver.reverse('127.0.0.1', common.mustSucceed((hostnames) => {
assert.deepStrictEqual(hostnames, [expectedHostname]);
server.close();
}));
}));