Skip to content

Commit

Permalink
dns: enable usage of independent cares resolvers
Browse files Browse the repository at this point in the history
Ref: #7231
PR-URL: #14518
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
  • Loading branch information
addaleax committed Aug 1, 2017
1 parent 106a23b commit 69e41dc
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 36 deletions.
45 changes: 45 additions & 0 deletions doc/api/dns.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
83 changes: 47 additions & 36 deletions lib/dns.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@ const {
isIP
} = cares;

const defaultChannel = new ChannelWrap();

const isLegalPort = internalNet.isLegalPort;


Expand Down Expand Up @@ -243,6 +241,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) {
Expand All @@ -264,26 +268,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, type_, callback_) {
var resolver, callback;
Expand All @@ -298,15 +303,16 @@ function resolve(hostname, type_, callback_) {
}

if (typeof resolver === 'function') {
return resolver(hostname, callback);
return resolver.call(this, hostname, callback);
} else {
throw new Error(`Unknown type "${type_}"`);
}
}


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];

Expand All @@ -316,10 +322,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+))?$/;
Expand Down Expand Up @@ -351,35 +358,39 @@ function setServers(servers) {
throw new Error(`IP address is not properly formatted: ${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 Error(`c-ares failed to set servers: "${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,
Expand Down
53 changes: 53 additions & 0 deletions test/parallel/test-dns-multi-channel.js
Original file line number Diff line number Diff line change
@@ -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();
}));
}
}

0 comments on commit 69e41dc

Please sign in to comment.