Skip to content

Commit 69e41dc

Browse files
committed
dns: enable usage of independent cares resolvers
Ref: #7231 PR-URL: #14518 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Tobias Nießen <[email protected]>
1 parent 106a23b commit 69e41dc

File tree

3 files changed

+145
-36
lines changed

3 files changed

+145
-36
lines changed

doc/api/dns.md

+45
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,47 @@ dns.resolve4('archive.org', (err, addresses) => {
5454
There are subtle consequences in choosing one over the other, please consult
5555
the [Implementation considerations section][] for more information.
5656

57+
## Class dns.Resolver
58+
<!-- YAML
59+
added: REPLACEME
60+
-->
61+
62+
An independent resolver for DNS requests.
63+
64+
Note that creating a new resolver uses the default server settings. Setting
65+
the servers used for a resolver using
66+
[`resolver.setServers()`][`dns.setServers()`] does not affect
67+
other resolver:
68+
69+
```js
70+
const { Resolver } = require('dns');
71+
const resolver = new Resolver();
72+
resolver.setServers(['4.4.4.4']);
73+
74+
// This request will use the server at 4.4.4.4, independent of global settings.
75+
resolver.resolve4('example.org', (err, addresses) => {
76+
// ...
77+
});
78+
```
79+
80+
The following methods from the `dns` module are available:
81+
82+
* [`resolver.getServers()`][`dns.getServers()`]
83+
* [`resolver.setServers()`][`dns.setServers()`]
84+
* [`resolver.resolve()`][`dns.resolve()`]
85+
* [`resolver.resolve4()`][`dns.resolve4()`]
86+
* [`resolver.resolve6()`][`dns.resolve6()`]
87+
* [`resolver.resolveAny()`][`dns.resolveAny()`]
88+
* [`resolver.resolveCname()`][`dns.resolveCname()`]
89+
* [`resolver.resolveMx()`][`dns.resolveMx()`]
90+
* [`resolver.resolveNaptr()`][`dns.resolveNaptr()`]
91+
* [`resolver.resolveNs()`][`dns.resolveNs()`]
92+
* [`resolver.resolvePtr()`][`dns.resolvePtr()`]
93+
* [`resolver.resolveSoa()`][`dns.resolveSoa()`]
94+
* [`resolver.resolveSrv()`][`dns.resolveSrv()`]
95+
* [`resolver.resolveTxt()`][`dns.resolveTxt()`]
96+
* [`resolver.reverse()`][`dns.reverse()`]
97+
5798
## dns.getServers()
5899
<!-- YAML
59100
added: v0.11.3
@@ -590,6 +631,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
590631

591632
[`Error`]: errors.html#errors_class_error
592633
[`dns.lookup()`]: #dns_dns_lookup_hostname_options_callback
634+
[`dns.resolve()`]: #dns_dns_resolve_hostname_rrtype_callback
593635
[`dns.resolve4()`]: #dns_dns_resolve4_hostname_options_callback
594636
[`dns.resolve6()`]: #dns_dns_resolve6_hostname_options_callback
595637
[`dns.resolveCname()`]: #dns_dns_resolvecname_hostname_callback
@@ -601,6 +643,9 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
601643
[`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback
602644
[`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback
603645
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
646+
[`dns.getServers()`]: #dns_dns_getservers
647+
[`dns.setServers()`]: #dns_dns_setservers_servers
648+
[`dns.reverse()`]: #dns_dns_reverse_ip_callback
604649
[DNS error codes]: #dns_error_codes
605650
[Implementation considerations section]: #dns_implementation_considerations
606651
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags

lib/dns.js

+47-36
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,6 @@ const {
3636
isIP
3737
} = cares;
3838

39-
const defaultChannel = new ChannelWrap();
40-
4139
const isLegalPort = internalNet.isLegalPort;
4240

4341

@@ -243,6 +241,12 @@ function onresolve(err, result, ttls) {
243241
this.callback(null, result);
244242
}
245243

244+
// Resolver instances correspond 1:1 to c-ares channels.
245+
class Resolver {
246+
constructor() {
247+
this._handle = new ChannelWrap();
248+
}
249+
}
246250

247251
function resolver(bindingName) {
248252
return function query(name, /* options, */ callback) {
@@ -264,26 +268,27 @@ function resolver(bindingName) {
264268
req.hostname = name;
265269
req.oncomplete = onresolve;
266270
req.ttl = !!(options && options.ttl);
267-
var err = defaultChannel[bindingName](req, name);
271+
var err = this._handle[bindingName](req, name);
268272
if (err) throw errnoException(err, bindingName);
269273
return req;
270274
};
271275
}
272276

273-
274277
var resolveMap = Object.create(null);
275-
resolveMap.ANY = resolver('queryAny');
276-
resolveMap.A = resolver('queryA');
277-
resolveMap.AAAA = resolver('queryAaaa');
278-
resolveMap.CNAME = resolver('queryCname');
279-
resolveMap.MX = resolver('queryMx');
280-
resolveMap.NS = resolver('queryNs');
281-
resolveMap.TXT = resolver('queryTxt');
282-
resolveMap.SRV = resolver('querySrv');
283-
resolveMap.PTR = resolver('queryPtr');
284-
resolveMap.NAPTR = resolver('queryNaptr');
285-
resolveMap.SOA = resolver('querySoa');
286-
278+
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
279+
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
280+
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
281+
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
282+
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
283+
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
284+
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
285+
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
286+
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
287+
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
288+
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
289+
Resolver.prototype.reverse = resolver('getHostByAddr');
290+
291+
Resolver.prototype.resolve = resolve;
287292

288293
function resolve(hostname, type_, callback_) {
289294
var resolver, callback;
@@ -298,15 +303,16 @@ function resolve(hostname, type_, callback_) {
298303
}
299304

300305
if (typeof resolver === 'function') {
301-
return resolver(hostname, callback);
306+
return resolver.call(this, hostname, callback);
302307
} else {
303308
throw new Error(`Unknown type "${type_}"`);
304309
}
305310
}
306311

307312

313+
Resolver.prototype.getServers = getServers;
308314
function getServers() {
309-
const ret = defaultChannel.getServers();
315+
const ret = this._handle.getServers();
310316
return ret.map((val) => {
311317
if (!val[1] || val[1] === IANA_DNS_PORT) return val[0];
312318

@@ -316,10 +322,11 @@ function getServers() {
316322
}
317323

318324

325+
Resolver.prototype.setServers = setServers;
319326
function setServers(servers) {
320327
// cache the original servers because in the event of an error setting the
321328
// servers cares won't have any servers available for resolution
322-
const orig = defaultChannel.getServers();
329+
const orig = this._handle.getServers();
323330
const newSet = [];
324331
const IPv6RE = /\[(.*)\]/;
325332
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
@@ -351,35 +358,39 @@ function setServers(servers) {
351358
throw new Error(`IP address is not properly formatted: ${serv}`);
352359
});
353360

354-
const errorNumber = defaultChannel.setServers(newSet);
361+
const errorNumber = this._handle.setServers(newSet);
355362

356363
if (errorNumber !== 0) {
357364
// reset the servers to the old servers, because ares probably unset them
358-
defaultChannel.setServers(orig.join(','));
365+
this._handle.setServers(orig.join(','));
359366

360367
var err = cares.strerror(errorNumber);
361368
throw new Error(`c-ares failed to set servers: "${err}" [${servers}]`);
362369
}
363370
}
364371

372+
const defaultResolver = new Resolver();
373+
365374
module.exports = {
366375
lookup,
367376
lookupService,
368-
getServers,
369-
setServers,
370-
resolve,
371-
resolveAny: resolveMap.ANY,
372-
resolve4: resolveMap.A,
373-
resolve6: resolveMap.AAAA,
374-
resolveCname: resolveMap.CNAME,
375-
resolveMx: resolveMap.MX,
376-
resolveNs: resolveMap.NS,
377-
resolveTxt: resolveMap.TXT,
378-
resolveSrv: resolveMap.SRV,
379-
resolvePtr: resolveMap.PTR,
380-
resolveNaptr: resolveMap.NAPTR,
381-
resolveSoa: resolveMap.SOA,
382-
reverse: resolver('getHostByAddr'),
377+
378+
Resolver,
379+
getServers: defaultResolver.getServers.bind(defaultResolver),
380+
setServers: defaultResolver.setServers.bind(defaultResolver),
381+
resolve: defaultResolver.resolve.bind(defaultResolver),
382+
resolveAny: defaultResolver.resolveAny.bind(defaultResolver),
383+
resolve4: defaultResolver.resolve4.bind(defaultResolver),
384+
resolve6: defaultResolver.resolve6.bind(defaultResolver),
385+
resolveCname: defaultResolver.resolveCname.bind(defaultResolver),
386+
resolveMx: defaultResolver.resolveMx.bind(defaultResolver),
387+
resolveNs: defaultResolver.resolveNs.bind(defaultResolver),
388+
resolveTxt: defaultResolver.resolveTxt.bind(defaultResolver),
389+
resolveSrv: defaultResolver.resolveSrv.bind(defaultResolver),
390+
resolvePtr: defaultResolver.resolvePtr.bind(defaultResolver),
391+
resolveNaptr: defaultResolver.resolveNaptr.bind(defaultResolver),
392+
resolveSoa: defaultResolver.resolveSoa.bind(defaultResolver),
393+
reverse: defaultResolver.reverse.bind(defaultResolver),
383394

384395
// uv_getaddrinfo flags
385396
ADDRCONFIG: cares.AI_ADDRCONFIG,
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
const common = require('../common');
3+
const dnstools = require('../common/dns');
4+
const { Resolver } = require('dns');
5+
const assert = require('assert');
6+
const dgram = require('dgram');
7+
8+
const servers = [
9+
{
10+
socket: dgram.createSocket('udp4'),
11+
reply: { type: 'A', address: '1.2.3.4', ttl: 123, domain: 'example.org' }
12+
},
13+
{
14+
socket: dgram.createSocket('udp4'),
15+
reply: { type: 'A', address: '5.6.7.8', ttl: 123, domain: 'example.org' }
16+
}
17+
];
18+
19+
let waiting = servers.length;
20+
for (const { socket, reply } of servers) {
21+
socket.on('message', common.mustCall((msg, { address, port }) => {
22+
const parsed = dnstools.parseDNSPacket(msg);
23+
const domain = parsed.questions[0].domain;
24+
assert.strictEqual(domain, 'example.org');
25+
26+
socket.send(dnstools.writeDNSPacket({
27+
id: parsed.id,
28+
questions: parsed.questions,
29+
answers: [reply],
30+
}), port, address);
31+
}));
32+
33+
socket.bind(0, common.mustCall(() => {
34+
if (0 === --waiting) ready();
35+
}));
36+
}
37+
38+
39+
function ready() {
40+
const resolvers = servers.map((server) => ({
41+
server,
42+
resolver: new Resolver()
43+
}));
44+
45+
for (const { server: { socket, reply }, resolver } of resolvers) {
46+
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
47+
resolver.resolve4('example.org', common.mustCall((err, res) => {
48+
assert.ifError(err);
49+
assert.deepStrictEqual(res, [reply.address]);
50+
socket.close();
51+
}));
52+
}
53+
}

0 commit comments

Comments
 (0)