dns: enable usage of independent cares resolvers
Ref: https://github.com/nodejs/node/issues/7231 PR-URL: https://github.com/nodejs/node/pull/14518 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de>
This commit is contained in:
parent
727b2911ec
commit
6e05970494
@ -54,6 +54,47 @@ dns.resolve4('archive.org', (err, addresses) => {
|
|||||||
There are subtle consequences in choosing one over the other, please consult
|
There are subtle consequences in choosing one over the other, please consult
|
||||||
the [Implementation considerations section][] for more information.
|
the [Implementation considerations section][] for more information.
|
||||||
|
|
||||||
|
## Class dns.Resolver
|
||||||
|
<!-- YAML
|
||||||
|
added: REPLACEME
|
||||||
|
-->
|
||||||
|
|
||||||
|
An independent resolver for DNS requests.
|
||||||
|
|
||||||
|
Note that creating a new resolver uses the default server settings. Setting
|
||||||
|
the servers used for a resolver using
|
||||||
|
[`resolver.setServers()`][`dns.setServers()`] does not affect
|
||||||
|
other resolver:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { Resolver } = require('dns');
|
||||||
|
const resolver = new Resolver();
|
||||||
|
resolver.setServers(['4.4.4.4']);
|
||||||
|
|
||||||
|
// This request will use the server at 4.4.4.4, independent of global settings.
|
||||||
|
resolver.resolve4('example.org', (err, addresses) => {
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
The following methods from the `dns` module are available:
|
||||||
|
|
||||||
|
* [`resolver.getServers()`][`dns.getServers()`]
|
||||||
|
* [`resolver.setServers()`][`dns.setServers()`]
|
||||||
|
* [`resolver.resolve()`][`dns.resolve()`]
|
||||||
|
* [`resolver.resolve4()`][`dns.resolve4()`]
|
||||||
|
* [`resolver.resolve6()`][`dns.resolve6()`]
|
||||||
|
* [`resolver.resolveAny()`][`dns.resolveAny()`]
|
||||||
|
* [`resolver.resolveCname()`][`dns.resolveCname()`]
|
||||||
|
* [`resolver.resolveMx()`][`dns.resolveMx()`]
|
||||||
|
* [`resolver.resolveNaptr()`][`dns.resolveNaptr()`]
|
||||||
|
* [`resolver.resolveNs()`][`dns.resolveNs()`]
|
||||||
|
* [`resolver.resolvePtr()`][`dns.resolvePtr()`]
|
||||||
|
* [`resolver.resolveSoa()`][`dns.resolveSoa()`]
|
||||||
|
* [`resolver.resolveSrv()`][`dns.resolveSrv()`]
|
||||||
|
* [`resolver.resolveTxt()`][`dns.resolveTxt()`]
|
||||||
|
* [`resolver.reverse()`][`dns.reverse()`]
|
||||||
|
|
||||||
## dns.getServers()
|
## dns.getServers()
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.11.3
|
added: v0.11.3
|
||||||
@ -590,6 +631,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
|
|||||||
|
|
||||||
[`Error`]: errors.html#errors_class_error
|
[`Error`]: errors.html#errors_class_error
|
||||||
[`dns.lookup()`]: #dns_dns_lookup_hostname_options_callback
|
[`dns.lookup()`]: #dns_dns_lookup_hostname_options_callback
|
||||||
|
[`dns.resolve()`]: #dns_dns_resolve_hostname_rrtype_callback
|
||||||
[`dns.resolve4()`]: #dns_dns_resolve4_hostname_options_callback
|
[`dns.resolve4()`]: #dns_dns_resolve4_hostname_options_callback
|
||||||
[`dns.resolve6()`]: #dns_dns_resolve6_hostname_options_callback
|
[`dns.resolve6()`]: #dns_dns_resolve6_hostname_options_callback
|
||||||
[`dns.resolveCname()`]: #dns_dns_resolvecname_hostname_callback
|
[`dns.resolveCname()`]: #dns_dns_resolvecname_hostname_callback
|
||||||
@ -601,6 +643,9 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
|
|||||||
[`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback
|
[`dns.resolveSrv()`]: #dns_dns_resolvesrv_hostname_callback
|
||||||
[`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback
|
[`dns.resolveTxt()`]: #dns_dns_resolvetxt_hostname_callback
|
||||||
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
|
[`dns.resolveAny()`]: #dns_dns_resolveany_hostname_callback
|
||||||
|
[`dns.getServers()`]: #dns_dns_getservers
|
||||||
|
[`dns.setServers()`]: #dns_dns_setservers_servers
|
||||||
|
[`dns.reverse()`]: #dns_dns_reverse_ip_callback
|
||||||
[DNS error codes]: #dns_error_codes
|
[DNS error codes]: #dns_error_codes
|
||||||
[Implementation considerations section]: #dns_implementation_considerations
|
[Implementation considerations section]: #dns_implementation_considerations
|
||||||
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags
|
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags
|
||||||
|
81
lib/dns.js
81
lib/dns.js
@ -37,8 +37,6 @@ const {
|
|||||||
isIP
|
isIP
|
||||||
} = cares;
|
} = cares;
|
||||||
|
|
||||||
const defaultChannel = new ChannelWrap();
|
|
||||||
|
|
||||||
const isLegalPort = internalNet.isLegalPort;
|
const isLegalPort = internalNet.isLegalPort;
|
||||||
|
|
||||||
|
|
||||||
@ -244,6 +242,12 @@ function onresolve(err, result, ttls) {
|
|||||||
this.callback(null, result);
|
this.callback(null, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolver instances correspond 1:1 to c-ares channels.
|
||||||
|
class Resolver {
|
||||||
|
constructor() {
|
||||||
|
this._handle = new ChannelWrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function resolver(bindingName) {
|
function resolver(bindingName) {
|
||||||
return function query(name, /* options, */ callback) {
|
return function query(name, /* options, */ callback) {
|
||||||
@ -266,26 +270,27 @@ function resolver(bindingName) {
|
|||||||
req.hostname = name;
|
req.hostname = name;
|
||||||
req.oncomplete = onresolve;
|
req.oncomplete = onresolve;
|
||||||
req.ttl = !!(options && options.ttl);
|
req.ttl = !!(options && options.ttl);
|
||||||
var err = defaultChannel[bindingName](req, name);
|
var err = this._handle[bindingName](req, name);
|
||||||
if (err) throw errnoException(err, bindingName);
|
if (err) throw errnoException(err, bindingName);
|
||||||
return req;
|
return req;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var resolveMap = Object.create(null);
|
var resolveMap = Object.create(null);
|
||||||
resolveMap.ANY = resolver('queryAny');
|
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
|
||||||
resolveMap.A = resolver('queryA');
|
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
|
||||||
resolveMap.AAAA = resolver('queryAaaa');
|
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
|
||||||
resolveMap.CNAME = resolver('queryCname');
|
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
||||||
resolveMap.MX = resolver('queryMx');
|
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
||||||
resolveMap.NS = resolver('queryNs');
|
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
||||||
resolveMap.TXT = resolver('queryTxt');
|
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
||||||
resolveMap.SRV = resolver('querySrv');
|
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
||||||
resolveMap.PTR = resolver('queryPtr');
|
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
||||||
resolveMap.NAPTR = resolver('queryNaptr');
|
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
|
||||||
resolveMap.SOA = resolver('querySoa');
|
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
|
||||||
|
Resolver.prototype.reverse = resolver('getHostByAddr');
|
||||||
|
|
||||||
|
Resolver.prototype.resolve = resolve;
|
||||||
|
|
||||||
function resolve(hostname, rrtype, callback) {
|
function resolve(hostname, rrtype, callback) {
|
||||||
var resolver;
|
var resolver;
|
||||||
@ -300,15 +305,16 @@ function resolve(hostname, rrtype, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof resolver === 'function') {
|
if (typeof resolver === 'function') {
|
||||||
return resolver(hostname, callback);
|
return resolver.call(this, hostname, callback);
|
||||||
} else {
|
} else {
|
||||||
throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'rrtype', rrtype);
|
throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'rrtype', rrtype);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Resolver.prototype.getServers = getServers;
|
||||||
function getServers() {
|
function getServers() {
|
||||||
const ret = defaultChannel.getServers();
|
const ret = this._handle.getServers();
|
||||||
return ret.map((val) => {
|
return ret.map((val) => {
|
||||||
if (!val[1] || val[1] === IANA_DNS_PORT) return val[0];
|
if (!val[1] || val[1] === IANA_DNS_PORT) return val[0];
|
||||||
|
|
||||||
@ -318,10 +324,11 @@ function getServers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Resolver.prototype.setServers = setServers;
|
||||||
function setServers(servers) {
|
function setServers(servers) {
|
||||||
// cache the original servers because in the event of an error setting the
|
// cache the original servers because in the event of an error setting the
|
||||||
// servers cares won't have any servers available for resolution
|
// servers cares won't have any servers available for resolution
|
||||||
const orig = defaultChannel.getServers();
|
const orig = this._handle.getServers();
|
||||||
const newSet = [];
|
const newSet = [];
|
||||||
const IPv6RE = /\[(.*)\]/;
|
const IPv6RE = /\[(.*)\]/;
|
||||||
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
||||||
@ -353,35 +360,39 @@ function setServers(servers) {
|
|||||||
throw new errors.Error('ERR_INVALID_IP_ADDRESS', serv);
|
throw new errors.Error('ERR_INVALID_IP_ADDRESS', serv);
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorNumber = defaultChannel.setServers(newSet);
|
const errorNumber = this._handle.setServers(newSet);
|
||||||
|
|
||||||
if (errorNumber !== 0) {
|
if (errorNumber !== 0) {
|
||||||
// reset the servers to the old servers, because ares probably unset them
|
// reset the servers to the old servers, because ares probably unset them
|
||||||
defaultChannel.setServers(orig.join(','));
|
this._handle.setServers(orig.join(','));
|
||||||
|
|
||||||
var err = cares.strerror(errorNumber);
|
var err = cares.strerror(errorNumber);
|
||||||
throw new errors.Error('ERR_DNS_SET_SERVERS_FAILED', err, servers);
|
throw new errors.Error('ERR_DNS_SET_SERVERS_FAILED', err, servers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const defaultResolver = new Resolver();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
lookup,
|
lookup,
|
||||||
lookupService,
|
lookupService,
|
||||||
getServers,
|
|
||||||
setServers,
|
Resolver,
|
||||||
resolve,
|
getServers: defaultResolver.getServers.bind(defaultResolver),
|
||||||
resolveAny: resolveMap.ANY,
|
setServers: defaultResolver.setServers.bind(defaultResolver),
|
||||||
resolve4: resolveMap.A,
|
resolve: defaultResolver.resolve.bind(defaultResolver),
|
||||||
resolve6: resolveMap.AAAA,
|
resolveAny: defaultResolver.resolveAny.bind(defaultResolver),
|
||||||
resolveCname: resolveMap.CNAME,
|
resolve4: defaultResolver.resolve4.bind(defaultResolver),
|
||||||
resolveMx: resolveMap.MX,
|
resolve6: defaultResolver.resolve6.bind(defaultResolver),
|
||||||
resolveNs: resolveMap.NS,
|
resolveCname: defaultResolver.resolveCname.bind(defaultResolver),
|
||||||
resolveTxt: resolveMap.TXT,
|
resolveMx: defaultResolver.resolveMx.bind(defaultResolver),
|
||||||
resolveSrv: resolveMap.SRV,
|
resolveNs: defaultResolver.resolveNs.bind(defaultResolver),
|
||||||
resolvePtr: resolveMap.PTR,
|
resolveTxt: defaultResolver.resolveTxt.bind(defaultResolver),
|
||||||
resolveNaptr: resolveMap.NAPTR,
|
resolveSrv: defaultResolver.resolveSrv.bind(defaultResolver),
|
||||||
resolveSoa: resolveMap.SOA,
|
resolvePtr: defaultResolver.resolvePtr.bind(defaultResolver),
|
||||||
reverse: resolver('getHostByAddr'),
|
resolveNaptr: defaultResolver.resolveNaptr.bind(defaultResolver),
|
||||||
|
resolveSoa: defaultResolver.resolveSoa.bind(defaultResolver),
|
||||||
|
reverse: defaultResolver.reverse.bind(defaultResolver),
|
||||||
|
|
||||||
// uv_getaddrinfo flags
|
// uv_getaddrinfo flags
|
||||||
ADDRCONFIG: cares.AI_ADDRCONFIG,
|
ADDRCONFIG: cares.AI_ADDRCONFIG,
|
||||||
|
53
test/parallel/test-dns-multi-channel.js
Normal file
53
test/parallel/test-dns-multi-channel.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
'use strict';
|
||||||
|
const common = require('../common');
|
||||||
|
const dnstools = require('../common/dns');
|
||||||
|
const { Resolver } = require('dns');
|
||||||
|
const assert = require('assert');
|
||||||
|
const dgram = require('dgram');
|
||||||
|
|
||||||
|
const servers = [
|
||||||
|
{
|
||||||
|
socket: dgram.createSocket('udp4'),
|
||||||
|
reply: { type: 'A', address: '1.2.3.4', ttl: 123, domain: 'example.org' }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
socket: dgram.createSocket('udp4'),
|
||||||
|
reply: { type: 'A', address: '5.6.7.8', ttl: 123, domain: 'example.org' }
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let waiting = servers.length;
|
||||||
|
for (const { socket, reply } of servers) {
|
||||||
|
socket.on('message', common.mustCall((msg, { address, port }) => {
|
||||||
|
const parsed = dnstools.parseDNSPacket(msg);
|
||||||
|
const domain = parsed.questions[0].domain;
|
||||||
|
assert.strictEqual(domain, 'example.org');
|
||||||
|
|
||||||
|
socket.send(dnstools.writeDNSPacket({
|
||||||
|
id: parsed.id,
|
||||||
|
questions: parsed.questions,
|
||||||
|
answers: [reply],
|
||||||
|
}), port, address);
|
||||||
|
}));
|
||||||
|
|
||||||
|
socket.bind(0, common.mustCall(() => {
|
||||||
|
if (0 === --waiting) ready();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ready() {
|
||||||
|
const resolvers = servers.map((server) => ({
|
||||||
|
server,
|
||||||
|
resolver: new Resolver()
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const { server: { socket, reply }, resolver } of resolvers) {
|
||||||
|
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
|
||||||
|
resolver.resolve4('example.org', common.mustCall((err, res) => {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.deepStrictEqual(res, [reply.address]);
|
||||||
|
socket.close();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user