Skip to content

Commit

Permalink
dns: refactor default resolver
Browse files Browse the repository at this point in the history
This patch refactors the DNS default resolver code to make it
easier to be included in a snapshot:

- The code specific for the callback-based DNS resolver are not
  in a separate module to make the dependency clearer (it's not
  actually needed if the user only ever loads `dns/promises`)
- The common part of the callback-based resolver and the promise-
  based resolver is now ResolverBase. The other two user-facing
  resolvers are now subclasses of ResolverBase. The default
  Resolver is constructed with just ResolverBase. This would
  be fine as the default resolver is never actually exposed
  to the user-land and it has been working using duck-typing anyway.
- Move the construction of Resolver subclasses into a common
  method `createResolverClass()` to reduce code duplication.
  The two subclasses now also share the same base constructor.
  This would make it possible for them to also share code
  for snapshot support later.
- `--dns-result-order` is now queried and refreshed during
  pre-execution. To avoid loading the cares_wrap binding unnecessarily
  the loading of the binding is also made lazy.

PR-URL: #44541
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Zeyu "Alex" Yang <himself65@outlook.com>
Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
  • Loading branch information
joyeecheung authored and RafaelGSS committed Sep 26, 2022
1 parent 360b74e commit 729dd95
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 141 deletions.
97 changes: 4 additions & 93 deletions lib/dns.js
Expand Up @@ -22,11 +22,8 @@
'use strict';

const {
ArrayPrototypeMap,
ObjectCreate,
ObjectDefineProperties,
ObjectDefineProperty,
ReflectApply,
Symbol,
} = primordials;

Expand All @@ -37,15 +34,16 @@ const { customPromisifyArgs } = require('internal/util');
const errors = require('internal/errors');
const {
bindDefaultResolver,
getDefaultResolver,
setDefaultResolver,
Resolver,
validateHints,
emitInvalidHostnameWarning,
getDefaultVerbatim,
setDefaultResultOrder,
errorCodes: dnsErrorCodes,
} = require('internal/dns/utils');
const {
Resolver
} = require('internal/dns/callback_resolver');
const {
NODATA,
FORMERR,
Expand Down Expand Up @@ -89,12 +87,10 @@ const {
const {
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
QueryReqWrap,
} = cares;

const kPerfHooksDnsLookupContext = Symbol('kPerfHooksDnsLookupContext');
const kPerfHooksDnsLookupServiceContext = Symbol('kPerfHooksDnsLookupServiceContext');
const kPerfHooksDnsLookupResolveContext = Symbol('kPerfHooksDnsLookupResolveContext');

const {
hasObserver,
Expand Down Expand Up @@ -293,91 +289,6 @@ function lookupService(address, port, callback) {
ObjectDefineProperty(lookupService, customPromisifyArgs,
{ __proto__: null, value: ['hostname', 'service'], enumerable: false });


function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = ArrayPrototypeMap(
result, (address, index) => ({ address, ttl: ttls[index] }));

if (err)
this.callback(dnsException(err, this.bindingName, this.hostname));
else {
this.callback(null, result);
if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupResolveContext, { detail: { result } });
}
}
}

function resolver(bindingName) {
function query(name, /* options, */ callback) {
let options;
if (arguments.length > 2) {
options = callback;
callback = arguments[2];
}

validateString(name, 'name');
validateFunction(callback, 'callback');

const req = new QueryReqWrap();
req.bindingName = bindingName;
req.callback = callback;
req.hostname = name;
req.oncomplete = onresolve;
req.ttl = !!(options && options.ttl);
const err = this._handle[bindingName](req, toASCII(name));
if (err) throw dnsException(err, bindingName, name);
if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupResolveContext, {
type: 'dns',
name: bindingName,
detail: {
host: name,
ttl: req.ttl,
},
});
}
return req;
}
ObjectDefineProperty(query, 'name', { __proto__: null, value: bindingName });
return query;
}

const resolveMap = ObjectCreate(null);
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
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) {
let resolver;
if (typeof rrtype === 'string') {
resolver = resolveMap[rrtype];
} else if (typeof rrtype === 'function') {
resolver = resolveMap.A;
callback = rrtype;
} else {
throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype);
}

if (typeof resolver === 'function') {
return ReflectApply(resolver, this, [hostname, callback]);
}
throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);
}

function defaultResolverSetServers(servers) {
const resolver = new Resolver();

Expand Down Expand Up @@ -429,7 +340,7 @@ module.exports = {
CANCELLED,
};

bindDefaultResolver(module.exports, getDefaultResolver());
bindDefaultResolver(module.exports, Resolver.prototype);

ObjectDefineProperties(module.exports, {
promises: {
Expand Down
116 changes: 116 additions & 0 deletions lib/internal/dns/callback_resolver.js
@@ -0,0 +1,116 @@
'use strict';

const {
ObjectDefineProperty,
ReflectApply,
ArrayPrototypeMap,
Symbol
} = primordials;

const { toASCII } = require('internal/idna');

const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
},
dnsException
} = require('internal/errors');

const {
createResolverClass,
} = require('internal/dns/utils');

const {
validateFunction,
validateString,
} = require('internal/validators');

const {
QueryReqWrap,
} = internalBinding('cares_wrap');

const {
hasObserver,
startPerf,
stopPerf,
} = require('internal/perf/observe');

const kPerfHooksDnsLookupResolveContext = Symbol('kPerfHooksDnsLookupResolveContext');

function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = ArrayPrototypeMap(
result, (address, index) => ({ address, ttl: ttls[index] }));

if (err)
this.callback(dnsException(err, this.bindingName, this.hostname));
else {
this.callback(null, result);
if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) {
stopPerf(this, kPerfHooksDnsLookupResolveContext, { detail: { result } });
}
}
}

function resolver(bindingName) {
function query(name, /* options, */ callback) {
let options;
if (arguments.length > 2) {
options = callback;
callback = arguments[2];
}

validateString(name, 'name');
validateFunction(callback, 'callback');

const req = new QueryReqWrap();
req.bindingName = bindingName;
req.callback = callback;
req.hostname = name;
req.oncomplete = onresolve;
req.ttl = !!(options && options.ttl);
const err = this._handle[bindingName](req, toASCII(name));
if (err) throw dnsException(err, bindingName, name);
if (hasObserver('dns')) {
startPerf(req, kPerfHooksDnsLookupResolveContext, {
type: 'dns',
name: bindingName,
detail: {
host: name,
ttl: req.ttl,
},
});
}
return req;
}
ObjectDefineProperty(query, 'name', { __proto__: null, value: bindingName });
return query;
}

// This is the callback-based resolver. There is another similar
// resolver in dns/promises.js with resolve methods that are based
// on promises instead.
const { Resolver, resolveMap } = createResolverClass(resolver);
Resolver.prototype.resolve = resolve;

function resolve(hostname, rrtype, callback) {
let resolver;
if (typeof rrtype === 'string') {
resolver = resolveMap[rrtype];
} else if (typeof rrtype === 'function') {
resolver = resolveMap.A;
callback = rrtype;
} else {
throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype);
}

if (typeof resolver === 'function') {
return ReflectApply(resolver, this, [hostname, callback]);
}
throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);
}

module.exports = {
Resolver
};
44 changes: 8 additions & 36 deletions lib/internal/dns/promises.js
@@ -1,7 +1,6 @@
'use strict';
const {
ArrayPrototypeMap,
ObjectCreate,
ObjectDefineProperty,
Promise,
ReflectApply,
Expand All @@ -10,16 +9,15 @@ const {

const {
bindDefaultResolver,
Resolver: CallbackResolver,
createResolverClass,
validateHints,
validateTimeout,
validateTries,
emitInvalidHostnameWarning,
getDefaultVerbatim,
errorCodes: dnsErrorCodes,
setDefaultResultOrder,
setDefaultResolver,
} = require('internal/dns/utils');

const {
NODATA,
FORMERR,
Expand Down Expand Up @@ -52,7 +50,6 @@ const { isIP } = require('internal/net');
const {
getaddrinfo,
getnameinfo,
ChannelWrap,
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
QueryReqWrap
Expand Down Expand Up @@ -305,36 +302,7 @@ function resolver(bindingName) {
return query;
}


const resolveMap = ObjectCreate(null);

// Resolver instances correspond 1:1 to c-ares channels.
class Resolver {
constructor(options = undefined) {
const timeout = validateTimeout(options);
const tries = validateTries(options);
this._handle = new ChannelWrap(timeout, tries);
}
}

Resolver.prototype.getServers = CallbackResolver.prototype.getServers;
Resolver.prototype.setServers = CallbackResolver.prototype.setServers;
Resolver.prototype.cancel = CallbackResolver.prototype.cancel;
Resolver.prototype.setLocalAddress = CallbackResolver.prototype.setLocalAddress;
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
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 = function resolve(hostname, rrtype) {
function resolve(hostname, rrtype) {
let resolver;

if (rrtype !== undefined) {
Expand All @@ -349,7 +317,11 @@ Resolver.prototype.resolve = function resolve(hostname, rrtype) {
}

return ReflectApply(resolver, this, [hostname]);
};
}

// Promise-based resolver.
const { Resolver, resolveMap } = createResolverClass(resolver);
Resolver.prototype.resolve = resolve;

function defaultResolverSetServers(servers) {
const resolver = new Resolver();
Expand Down

0 comments on commit 729dd95

Please sign in to comment.