lts/lib/dns.js
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const {
ObjectCreate,
ObjectDefineProperties,
ObjectDefineProperty,
} = primordials;
const cares = internalBinding('cares_wrap');
const { toASCII } = require('internal/idna');
const { isIP } = require('internal/net');
const { customPromisifyArgs } = require('internal/util');
const errors = require('internal/errors');
const {
bindDefaultResolver,
getDefaultResolver,
setDefaultResolver,
Resolver,
validateHints,
emitInvalidHostnameWarning,
} = require('internal/dns/utils');
const {
ERR_INVALID_ARG_TYPE,
ERR_INVALID_CALLBACK,
ERR_INVALID_OPT_VALUE,
ERR_MISSING_ARGS,
} = errors.codes;
const {
validatePort,
validateString,
} = require('internal/validators');
const {
GetAddrInfoReqWrap,
GetNameInfoReqWrap,
QueryReqWrap,
} = cares;
const dnsException = errors.dnsException;
let promises = null; // Lazy loaded
function onlookup(err, addresses) {
if (err) {
return this.callback(dnsException(err, 'getaddrinfo', this.hostname));
}
this.callback(null, addresses[0], this.family || isIP(addresses[0]));
}
function onlookupall(err, addresses) {
if (err) {
return this.callback(dnsException(err, 'getaddrinfo', this.hostname));
}
const family = this.family;
for (let i = 0; i < addresses.length; i++) {
const addr = addresses[i];
addresses[i] = {
address: addr,
family: family || isIP(addr)
};
}
this.callback(null, addresses);
}
// Easy DNS A/AAAA look up
// lookup(hostname, [options,] callback)
function lookup(hostname, options, callback) {
let hints = 0;
let family = -1;
let all = false;
let verbatim = false;
// Parse arguments
if (hostname && typeof hostname !== 'string') {
throw new ERR_INVALID_ARG_TYPE('hostname', 'string', hostname);
} else if (typeof options === 'function') {
callback = options;
family = 0;
} else if (typeof callback !== 'function') {
throw new ERR_INVALID_CALLBACK(callback);
} else if (options !== null && typeof options === 'object') {
hints = options.hints >>> 0;
family = options.family >>> 0;
all = options.all === true;
verbatim = options.verbatim === true;
validateHints(hints);
} else {
family = options >>> 0;
}
if (family !== 0 && family !== 4 && family !== 6)
throw new ERR_INVALID_OPT_VALUE('family', family);
if (!hostname) {
emitInvalidHostnameWarning(hostname);
if (all) {
process.nextTick(callback, null, []);
} else {
process.nextTick(callback, null, null, family === 6 ? 6 : 4);
}
return {};
}
const matchedFamily = isIP(hostname);
if (matchedFamily) {
if (all) {
process.nextTick(
callback, null, [{ address: hostname, family: matchedFamily }]);
} else {
process.nextTick(callback, null, hostname, matchedFamily);
}
return {};
}
const req = new GetAddrInfoReqWrap();
req.callback = callback;
req.family = family;
req.hostname = hostname;
req.oncomplete = all ? onlookupall : onlookup;
const err = cares.getaddrinfo(
req, toASCII(hostname), family, hints, verbatim
);
if (err) {
process.nextTick(callback, dnsException(err, 'getaddrinfo', hostname));
return {};
}
return req;
}
ObjectDefineProperty(lookup, customPromisifyArgs,
{ value: ['address', 'family'], enumerable: false });
function onlookupservice(err, hostname, service) {
if (err)
return this.callback(dnsException(err, 'getnameinfo', this.hostname));
this.callback(null, hostname, service);
}
function lookupService(address, port, callback) {
if (arguments.length !== 3)
throw new ERR_MISSING_ARGS('address', 'port', 'callback');
if (isIP(address) === 0)
throw new ERR_INVALID_OPT_VALUE('address', address);
validatePort(port);
if (typeof callback !== 'function')
throw new ERR_INVALID_CALLBACK(callback);
port = +port;
const req = new GetNameInfoReqWrap();
req.callback = callback;
req.hostname = address;
req.port = port;
req.oncomplete = onlookupservice;
const err = cares.getnameinfo(req, address, port);
if (err) throw dnsException(err, 'getnameinfo', address);
return req;
}
ObjectDefineProperty(lookupService, customPromisifyArgs,
{ value: ['hostname', 'service'], enumerable: false });
function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = result.map((address, index) => ({ address, ttl: ttls[index] }));
if (err)
this.callback(dnsException(err, this.bindingName, this.hostname));
else
this.callback(null, result);
}
function resolver(bindingName) {
function query(name, /* options, */ callback) {
let options;
if (arguments.length > 2) {
options = callback;
callback = arguments[2];
}
validateString(name, 'name');
if (typeof callback !== 'function') {
throw new ERR_INVALID_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);
return req;
}
ObjectDefineProperty(query, 'name', { 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.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 resolver.call(this, hostname, callback);
}
throw new ERR_INVALID_OPT_VALUE('rrtype', rrtype);
}
function defaultResolverSetServers(servers) {
const resolver = new Resolver();
resolver.setServers(servers);
setDefaultResolver(resolver);
bindDefaultResolver(module.exports, Resolver.prototype);
if (promises !== null)
bindDefaultResolver(promises, promises.Resolver.prototype);
}
module.exports = {
lookup,
lookupService,
Resolver,
setServers: defaultResolverSetServers,
// uv_getaddrinfo flags
ADDRCONFIG: cares.AI_ADDRCONFIG,
ALL: cares.AI_ALL,
V4MAPPED: cares.AI_V4MAPPED,
// ERROR CODES
NODATA: 'ENODATA',
FORMERR: 'EFORMERR',
SERVFAIL: 'ESERVFAIL',
NOTFOUND: 'ENOTFOUND',
NOTIMP: 'ENOTIMP',
REFUSED: 'EREFUSED',
BADQUERY: 'EBADQUERY',
BADNAME: 'EBADNAME',
BADFAMILY: 'EBADFAMILY',
BADRESP: 'EBADRESP',
CONNREFUSED: 'ECONNREFUSED',
TIMEOUT: 'ETIMEOUT',
EOF: 'EOF',
FILE: 'EFILE',
NOMEM: 'ENOMEM',
DESTRUCTION: 'EDESTRUCTION',
BADSTR: 'EBADSTR',
BADFLAGS: 'EBADFLAGS',
NONAME: 'ENONAME',
BADHINTS: 'EBADHINTS',
NOTINITIALIZED: 'ENOTINITIALIZED',
LOADIPHLPAPI: 'ELOADIPHLPAPI',
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
CANCELLED: 'ECANCELLED'
};
bindDefaultResolver(module.exports, getDefaultResolver());
ObjectDefineProperties(module.exports, {
promises: {
configurable: true,
enumerable: true,
get() {
if (promises === null) {
promises = require('internal/dns/promises');
promises.setServers = defaultResolverSetServers;
}
return promises;
}
}
});