Browse Source

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>
v6
Anna Henningsen 8 years ago
parent
commit
6e05970494
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 45
      doc/api/dns.md
  2. 83
      lib/dns.js
  3. 53
      test/parallel/test-dns-multi-channel.js

45
doc/api/dns.md

@ -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

83
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

@ -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…
Cancel
Save