summaryrefslogtreecommitdiff
path: root/lib/internal/dns
diff options
context:
space:
mode:
authorcjihrig <cjihrig@gmail.com>2018-06-11 14:56:33 -0400
committercjihrig <cjihrig@gmail.com>2018-06-20 13:35:27 -0400
commit7486c4d71060e2ae3e754cf01e8fb02696eacd13 (patch)
tree8b48c47271d474800f1222f729e93f477f3fd395 /lib/internal/dns
parentfea3595c2f92beb0d31feb93da1c972cc30e6fec (diff)
downloadandroid-node-v8-7486c4d71060e2ae3e754cf01e8fb02696eacd13.tar.gz
android-node-v8-7486c4d71060e2ae3e754cf01e8fb02696eacd13.tar.bz2
android-node-v8-7486c4d71060e2ae3e754cf01e8fb02696eacd13.zip
dns: add promisified dns module
PR-URL: https://github.com/nodejs/node/pull/21264 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Diffstat (limited to 'lib/internal/dns')
-rw-r--r--lib/internal/dns/promises.js249
-rw-r--r--lib/internal/dns/utils.js141
2 files changed, 390 insertions, 0 deletions
diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js
new file mode 100644
index 0000000000..45e6c5ec64
--- /dev/null
+++ b/lib/internal/dns/promises.js
@@ -0,0 +1,249 @@
+'use strict';
+const {
+ bindDefaultResolver,
+ Resolver: CallbackResolver,
+ validateHints
+} = require('internal/dns/utils');
+const { codes, dnsException } = require('internal/errors');
+const { isIP, isIPv4, isLegalPort } = require('internal/net');
+const {
+ getaddrinfo,
+ getnameinfo,
+ ChannelWrap,
+ GetAddrInfoReqWrap,
+ GetNameInfoReqWrap,
+ QueryReqWrap
+} = process.binding('cares_wrap');
+const {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_OPT_VALUE,
+ ERR_MISSING_ARGS,
+ ERR_SOCKET_BAD_PORT
+} = codes;
+
+
+function onlookup(err, addresses) {
+ if (err) {
+ this.reject(dnsException(err, 'getaddrinfo', this.hostname));
+ return;
+ }
+
+ const family = this.family ? this.family : isIPv4(addresses[0]) ? 4 : 6;
+ this.resolve({ address: addresses[0], family });
+}
+
+function onlookupall(err, addresses) {
+ if (err) {
+ this.reject(dnsException(err, 'getaddrinfo', this.hostname));
+ return;
+ }
+
+ const family = this.family;
+
+ for (var i = 0; i < addresses.length; i++) {
+ const address = addresses[i];
+
+ addresses[i] = {
+ address,
+ family: family ? family : isIPv4(addresses[i]) ? 4 : 6
+ };
+ }
+
+ this.resolve(addresses);
+}
+
+function createLookupPromise(family, hostname, all, hints, verbatim) {
+ return new Promise((resolve, reject) => {
+ if (!hostname) {
+ if (all)
+ resolve([]);
+ else
+ resolve({ address: null, family: family === 6 ? 6 : 4 });
+
+ return;
+ }
+
+ const matchedFamily = isIP(hostname);
+
+ if (matchedFamily !== 0) {
+ const result = { address: hostname, family: matchedFamily };
+ if (all)
+ resolve([result]);
+ else
+ resolve(result);
+
+ return;
+ }
+
+ const req = new GetAddrInfoReqWrap();
+
+ req.family = family;
+ req.hostname = hostname;
+ req.oncomplete = all ? onlookupall : onlookup;
+ req.resolve = resolve;
+ req.reject = reject;
+
+ const err = getaddrinfo(req, hostname, family, hints, verbatim);
+
+ if (err) {
+ reject(dnsException(err, 'getaddrinfo', hostname));
+ }
+ });
+}
+
+function lookup(hostname, options) {
+ var hints = 0;
+ var family = -1;
+ var all = false;
+ var verbatim = false;
+
+ // Parse arguments
+ if (hostname && typeof hostname !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('hostname', ['string', 'falsy'], hostname);
+ } 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);
+
+ return createLookupPromise(family, hostname, all, hints, verbatim);
+}
+
+
+function onlookupservice(err, hostname, service) {
+ if (err) {
+ this.reject(dnsException(err, 'getnameinfo', this.host));
+ return;
+ }
+
+ this.resolve({ hostname, service });
+}
+
+function createLookupServicePromise(host, port) {
+ return new Promise((resolve, reject) => {
+ const req = new GetNameInfoReqWrap();
+
+ req.host = host;
+ req.port = port;
+ req.oncomplete = onlookupservice;
+ req.resolve = resolve;
+ req.reject = reject;
+
+ const err = getnameinfo(req, host, port);
+
+ if (err)
+ reject(dnsException(err, 'getnameinfo', host));
+ });
+}
+
+function lookupService(host, port) {
+ if (arguments.length !== 2)
+ throw new ERR_MISSING_ARGS('host', 'port');
+
+ if (isIP(host) === 0)
+ throw new ERR_INVALID_OPT_VALUE('host', host);
+
+ if (!isLegalPort(port))
+ throw new ERR_SOCKET_BAD_PORT(port);
+
+ return createLookupServicePromise(host, +port);
+}
+
+
+function onresolve(err, result, ttls) {
+ if (err) {
+ this.reject(dnsException(err, this.bindingName, this.hostname));
+ return;
+ }
+
+ if (ttls && this.ttl)
+ result = result.map((address, index) => ({ address, ttl: ttls[index] }));
+
+ this.resolve(result);
+}
+
+function createResolverPromise(resolver, bindingName, hostname, ttl) {
+ return new Promise((resolve, reject) => {
+ const req = new QueryReqWrap();
+
+ req.bindingName = bindingName;
+ req.hostname = hostname;
+ req.oncomplete = onresolve;
+ req.resolve = resolve;
+ req.reject = reject;
+ req.ttl = ttl;
+
+ const err = resolver._handle[bindingName](req, hostname);
+
+ if (err)
+ reject(dnsException(err, bindingName, hostname));
+ });
+}
+
+function resolver(bindingName) {
+ function query(name, options) {
+ if (typeof name !== 'string') {
+ throw new ERR_INVALID_ARG_TYPE('name', 'string', name);
+ }
+
+ const ttl = !!(options && options.ttl);
+ return createResolverPromise(this, bindingName, name, ttl);
+ }
+
+ Object.defineProperty(query, 'name', { value: bindingName });
+ return query;
+}
+
+
+const resolveMap = Object.create(null);
+
+// Resolver instances correspond 1:1 to c-ares channels.
+class Resolver {
+ constructor() {
+ this._handle = new ChannelWrap();
+ }
+}
+
+Resolver.prototype.cancel = CallbackResolver.prototype.cancel;
+Resolver.prototype.getServers = CallbackResolver.prototype.getServers;
+Resolver.prototype.setServers = CallbackResolver.prototype.setServers;
+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 = function resolve(hostname, rrtype) {
+ var resolver;
+
+ if (typeof rrtype === 'string') {
+ resolver = resolveMap[rrtype];
+
+ if (typeof resolver !== 'function')
+ throw new ERR_INVALID_OPT_VALUE('rrtype', rrtype);
+ } else if (rrtype === undefined) {
+ resolver = resolveMap.A;
+ } else {
+ throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype);
+ }
+
+ return resolver.call(this, hostname);
+};
+
+
+module.exports = { lookup, lookupService, Resolver };
+bindDefaultResolver(module.exports, Resolver.prototype);
diff --git a/lib/internal/dns/utils.js b/lib/internal/dns/utils.js
new file mode 100644
index 0000000000..43b6541884
--- /dev/null
+++ b/lib/internal/dns/utils.js
@@ -0,0 +1,141 @@
+'use strict';
+const errors = require('internal/errors');
+const { isIP } = require('internal/net');
+const {
+ ChannelWrap,
+ strerror,
+ AI_ADDRCONFIG,
+ AI_V4MAPPED
+} = process.binding('cares_wrap');
+const IANA_DNS_PORT = 53;
+const IPv6RE = /^\[([^[\]]*)\]/;
+const addrSplitRE = /(^.+?)(?::(\d+))?$/;
+const {
+ ERR_DNS_SET_SERVERS_FAILED,
+ ERR_INVALID_IP_ADDRESS,
+ ERR_INVALID_OPT_VALUE
+} = errors.codes;
+
+// Resolver instances correspond 1:1 to c-ares channels.
+class Resolver {
+ constructor() {
+ this._handle = new ChannelWrap();
+ }
+
+ cancel() {
+ this._handle.cancel();
+ }
+
+ getServers() {
+ return this._handle.getServers().map((val) => {
+ if (!val[1] || val[1] === IANA_DNS_PORT)
+ return val[0];
+
+ const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
+ return `${host}:${val[1]}`;
+ });
+ }
+
+ setServers(servers) {
+ // Cache the original servers because in the event of an error while
+ // setting the servers, c-ares won't have any servers available for
+ // resolution.
+ const orig = this._handle.getServers();
+ const newSet = [];
+
+ servers.forEach((serv) => {
+ var ipVersion = isIP(serv);
+
+ if (ipVersion !== 0)
+ return newSet.push([ipVersion, serv, IANA_DNS_PORT]);
+
+ const match = serv.match(IPv6RE);
+
+ // Check for an IPv6 in brackets.
+ if (match) {
+ ipVersion = isIP(match[1]);
+
+ if (ipVersion !== 0) {
+ const port =
+ parseInt(serv.replace(addrSplitRE, '$2')) ||
+ IANA_DNS_PORT;
+ return newSet.push([ipVersion, match[1], port]);
+ }
+ }
+
+ // addr::port
+ const addrSplitMatch = serv.match(addrSplitRE);
+
+ if (addrSplitMatch) {
+ const hostIP = addrSplitMatch[1];
+ const port = addrSplitMatch[2] || IANA_DNS_PORT;
+
+ ipVersion = isIP(hostIP);
+
+ if (ipVersion !== 0) {
+ return newSet.push([ipVersion, hostIP, parseInt(port)]);
+ }
+ }
+
+ throw new ERR_INVALID_IP_ADDRESS(serv);
+ });
+
+ const errorNumber = this._handle.setServers(newSet);
+
+ if (errorNumber !== 0) {
+ // Reset the servers to the old servers, because ares probably unset them.
+ this._handle.setServers(orig.join(','));
+ const err = strerror(errorNumber);
+ throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
+ }
+ }
+}
+
+let defaultResolver = new Resolver();
+const resolverKeys = [
+ 'getServers',
+ 'resolve',
+ 'resolveAny',
+ 'resolve4',
+ 'resolve6',
+ 'resolveCname',
+ 'resolveMx',
+ 'resolveNs',
+ 'resolveTxt',
+ 'resolveSrv',
+ 'resolvePtr',
+ 'resolveNaptr',
+ 'resolveSoa',
+ 'reverse'
+];
+
+function getDefaultResolver() {
+ return defaultResolver;
+}
+
+function setDefaultResolver(resolver) {
+ defaultResolver = resolver;
+}
+
+function bindDefaultResolver(target, source) {
+ resolverKeys.forEach((key) => {
+ target[key] = source[key].bind(defaultResolver);
+ });
+}
+
+function validateHints(hints) {
+ if (hints !== 0 &&
+ hints !== AI_ADDRCONFIG &&
+ hints !== AI_V4MAPPED &&
+ hints !== (AI_ADDRCONFIG | AI_V4MAPPED)) {
+ throw new ERR_INVALID_OPT_VALUE('hints', hints);
+ }
+}
+
+module.exports = {
+ bindDefaultResolver,
+ getDefaultResolver,
+ setDefaultResolver,
+ validateHints,
+ Resolver
+};