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
|
||||
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()
|
||||
<!-- YAML
|
||||
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
|
||||
[`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.resolve6()`]: #dns_dns_resolve6_hostname_options_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.resolveTxt()`]: #dns_dns_resolvetxt_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
|
||||
[Implementation considerations section]: #dns_implementation_considerations
|
||||
[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags
|
||||
|
81
lib/dns.js
81
lib/dns.js
@ -37,8 +37,6 @@ const {
|
||||
isIP
|
||||
} = cares;
|
||||
|
||||
const defaultChannel = new ChannelWrap();
|
||||
|
||||
const isLegalPort = internalNet.isLegalPort;
|
||||
|
||||
|
||||
@ -244,6 +242,12 @@ function onresolve(err, result, ttls) {
|
||||
this.callback(null, result);
|
||||
}
|
||||
|
||||
// Resolver instances correspond 1:1 to c-ares channels.
|
||||
class Resolver {
|
||||
constructor() {
|
||||
this._handle = new ChannelWrap();
|
||||
}
|
||||
}
|
||||
|
||||
function resolver(bindingName) {
|
||||
return function query(name, /* options, */ callback) {
|
||||
@ -266,26 +270,27 @@ function resolver(bindingName) {
|
||||
req.hostname = name;
|
||||
req.oncomplete = onresolve;
|
||||
req.ttl = !!(options && options.ttl);
|
||||
var err = defaultChannel[bindingName](req, name);
|
||||
var err = this._handle[bindingName](req, name);
|
||||
if (err) throw errnoException(err, bindingName);
|
||||
return req;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
var resolveMap = Object.create(null);
|
||||
resolveMap.ANY = resolver('queryAny');
|
||||
resolveMap.A = resolver('queryA');
|
||||
resolveMap.AAAA = resolver('queryAaaa');
|
||||
resolveMap.CNAME = resolver('queryCname');
|
||||
resolveMap.MX = resolver('queryMx');
|
||||
resolveMap.NS = resolver('queryNs');
|
||||
resolveMap.TXT = resolver('queryTxt');
|
||||
resolveMap.SRV = resolver('querySrv');
|
||||
resolveMap.PTR = resolver('queryPtr');
|
||||
resolveMap.NAPTR = resolver('queryNaptr');
|
||||
resolveMap.SOA = resolver('querySoa');
|
||||
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
|
||||
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
|
||||
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
|
||||
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
||||
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
||||
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
||||
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
||||
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
||||
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
||||
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
|
||||
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
|
||||
Resolver.prototype.reverse = resolver('getHostByAddr');
|
||||
|
||||
Resolver.prototype.resolve = resolve;
|
||||
|
||||
function resolve(hostname, rrtype, callback) {
|
||||
var resolver;
|
||||
@ -300,15 +305,16 @@ function resolve(hostname, rrtype, callback) {
|
||||
}
|
||||
|
||||
if (typeof resolver === 'function') {
|
||||
return resolver(hostname, callback);
|
||||
return resolver.call(this, hostname, callback);
|
||||
} else {
|
||||
throw new errors.TypeError('ERR_INVALID_OPT_VALUE', 'rrtype', rrtype);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Resolver.prototype.getServers = getServers;
|
||||
function getServers() {
|
||||
const ret = defaultChannel.getServers();
|
||||
const ret = this._handle.getServers();
|
||||
return ret.map((val) => {
|
||||
if (!val[1] || val[1] === IANA_DNS_PORT) return val[0];
|
||||
|
||||
@ -318,10 +324,11 @@ function getServers() {
|
||||
}
|
||||
|
||||
|
||||
Resolver.prototype.setServers = setServers;
|
||||
function setServers(servers) {
|
||||
// cache the original servers because in the event of an error setting the
|
||||
// servers cares won't have any servers available for resolution
|
||||
const orig = defaultChannel.getServers();
|
||||
const orig = this._handle.getServers();
|
||||
const newSet = [];
|
||||
const IPv6RE = /\[(.*)\]/;
|
||||
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
||||
@ -353,35 +360,39 @@ function setServers(servers) {
|
||||
throw new errors.Error('ERR_INVALID_IP_ADDRESS', serv);
|
||||
});
|
||||
|
||||
const errorNumber = defaultChannel.setServers(newSet);
|
||||
const errorNumber = this._handle.setServers(newSet);
|
||||
|
||||
if (errorNumber !== 0) {
|
||||
// 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);
|
||||
throw new errors.Error('ERR_DNS_SET_SERVERS_FAILED', err, servers);
|
||||
}
|
||||
}
|
||||
|
||||
const defaultResolver = new Resolver();
|
||||
|
||||
module.exports = {
|
||||
lookup,
|
||||
lookupService,
|
||||
getServers,
|
||||
setServers,
|
||||
resolve,
|
||||
resolveAny: resolveMap.ANY,
|
||||
resolve4: resolveMap.A,
|
||||
resolve6: resolveMap.AAAA,
|
||||
resolveCname: resolveMap.CNAME,
|
||||
resolveMx: resolveMap.MX,
|
||||
resolveNs: resolveMap.NS,
|
||||
resolveTxt: resolveMap.TXT,
|
||||
resolveSrv: resolveMap.SRV,
|
||||
resolvePtr: resolveMap.PTR,
|
||||
resolveNaptr: resolveMap.NAPTR,
|
||||
resolveSoa: resolveMap.SOA,
|
||||
reverse: resolver('getHostByAddr'),
|
||||
|
||||
Resolver,
|
||||
getServers: defaultResolver.getServers.bind(defaultResolver),
|
||||
setServers: defaultResolver.setServers.bind(defaultResolver),
|
||||
resolve: defaultResolver.resolve.bind(defaultResolver),
|
||||
resolveAny: defaultResolver.resolveAny.bind(defaultResolver),
|
||||
resolve4: defaultResolver.resolve4.bind(defaultResolver),
|
||||
resolve6: defaultResolver.resolve6.bind(defaultResolver),
|
||||
resolveCname: defaultResolver.resolveCname.bind(defaultResolver),
|
||||
resolveMx: defaultResolver.resolveMx.bind(defaultResolver),
|
||||
resolveNs: defaultResolver.resolveNs.bind(defaultResolver),
|
||||
resolveTxt: defaultResolver.resolveTxt.bind(defaultResolver),
|
||||
resolveSrv: defaultResolver.resolveSrv.bind(defaultResolver),
|
||||
resolvePtr: defaultResolver.resolvePtr.bind(defaultResolver),
|
||||
resolveNaptr: defaultResolver.resolveNaptr.bind(defaultResolver),
|
||||
resolveSoa: defaultResolver.resolveSoa.bind(defaultResolver),
|
||||
reverse: defaultResolver.reverse.bind(defaultResolver),
|
||||
|
||||
// uv_getaddrinfo flags
|
||||
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