xref: /third_party/node/lib/internal/dns/promises.js (revision 1cb0ef41)
1'use strict';
2const {
3  ArrayPrototypeMap,
4  ObjectDefineProperty,
5  Promise,
6  ReflectApply,
7  Symbol,
8} = primordials;
9
10const {
11  bindDefaultResolver,
12  createResolverClass,
13  validateHints,
14  emitInvalidHostnameWarning,
15  getDefaultVerbatim,
16  errorCodes: dnsErrorCodes,
17  getDefaultResultOrder,
18  setDefaultResultOrder,
19  setDefaultResolver,
20} = require('internal/dns/utils');
21
22const {
23  NODATA,
24  FORMERR,
25  SERVFAIL,
26  NOTFOUND,
27  NOTIMP,
28  REFUSED,
29  BADQUERY,
30  BADNAME,
31  BADFAMILY,
32  BADRESP,
33  CONNREFUSED,
34  TIMEOUT,
35  EOF,
36  FILE,
37  NOMEM,
38  DESTRUCTION,
39  BADSTR,
40  BADFLAGS,
41  NONAME,
42  BADHINTS,
43  NOTINITIALIZED,
44  LOADIPHLPAPI,
45  ADDRGETNETWORKPARAMS,
46  CANCELLED,
47} = dnsErrorCodes;
48const { codes, dnsException } = require('internal/errors');
49const { isIP } = require('internal/net');
50const {
51  getaddrinfo,
52  getnameinfo,
53  GetAddrInfoReqWrap,
54  GetNameInfoReqWrap,
55  QueryReqWrap,
56} = internalBinding('cares_wrap');
57const {
58  ERR_INVALID_ARG_TYPE,
59  ERR_INVALID_ARG_VALUE,
60  ERR_MISSING_ARGS,
61} = codes;
62const {
63  validateBoolean,
64  validateNumber,
65  validateOneOf,
66  validatePort,
67  validateString,
68} = require('internal/validators');
69
70const kPerfHooksDnsLookupContext = Symbol('kPerfHooksDnsLookupContext');
71const kPerfHooksDnsLookupServiceContext = Symbol('kPerfHooksDnsLookupServiceContext');
72const kPerfHooksDnsLookupResolveContext = Symbol('kPerfHooksDnsLookupResolveContext');
73
74const {
75  hasObserver,
76  startPerf,
77  stopPerf,
78} = require('internal/perf/observe');
79
80function onlookup(err, addresses) {
81  if (err) {
82    this.reject(dnsException(err, 'getaddrinfo', this.hostname));
83    return;
84  }
85
86  const family = this.family || isIP(addresses[0]);
87  this.resolve({ address: addresses[0], family });
88  if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
89    stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
90  }
91}
92
93function onlookupall(err, addresses) {
94  if (err) {
95    this.reject(dnsException(err, 'getaddrinfo', this.hostname));
96    return;
97  }
98
99  const family = this.family;
100
101  for (let i = 0; i < addresses.length; i++) {
102    const address = addresses[i];
103
104    addresses[i] = {
105      address,
106      family: family || isIP(addresses[i]),
107    };
108  }
109
110  this.resolve(addresses);
111  if (this[kPerfHooksDnsLookupContext] && hasObserver('dns')) {
112    stopPerf(this, kPerfHooksDnsLookupContext, { detail: { addresses } });
113  }
114}
115
116/**
117 * Creates a promise that resolves with the IP address of the given hostname.
118 * @param {0 | 4 | 6} family - The IP address family (4 or 6, or 0 for both).
119 * @param {string} hostname - The hostname to resolve.
120 * @param {boolean} all - Whether to resolve with all IP addresses for the hostname.
121 * @param {number} hints - One or more supported getaddrinfo flags (supply multiple via
122 * bitwise OR).
123 * @param {boolean} verbatim - Whether to use the hostname verbatim.
124 * @returns {Promise<DNSLookupResult | DNSLookupResult[]>} The IP address(es) of the hostname.
125 * @typedef {object} DNSLookupResult
126 * @property {string} address - The IP address.
127 * @property {0 | 4 | 6} family - The IP address type. 4 for IPv4 or 6 for IPv6, or 0 (for both).
128 */
129function createLookupPromise(family, hostname, all, hints, verbatim) {
130  return new Promise((resolve, reject) => {
131    if (!hostname) {
132      emitInvalidHostnameWarning(hostname);
133      resolve(all ? [] : { address: null, family: family === 6 ? 6 : 4 });
134      return;
135    }
136
137    const matchedFamily = isIP(hostname);
138
139    if (matchedFamily !== 0) {
140      const result = { address: hostname, family: matchedFamily };
141      resolve(all ? [result] : result);
142      return;
143    }
144
145    const req = new GetAddrInfoReqWrap();
146
147    req.family = family;
148    req.hostname = hostname;
149    req.oncomplete = all ? onlookupall : onlookup;
150    req.resolve = resolve;
151    req.reject = reject;
152
153    const err = getaddrinfo(req, hostname, family, hints, verbatim);
154
155    if (err) {
156      reject(dnsException(err, 'getaddrinfo', hostname));
157    } else if (hasObserver('dns')) {
158      const detail = {
159        hostname,
160        family,
161        hints,
162        verbatim,
163      };
164      startPerf(req, kPerfHooksDnsLookupContext, { type: 'dns', name: 'lookup', detail });
165    }
166  });
167}
168
169const validFamilies = [0, 4, 6];
170/**
171 * Get the IP address for a given hostname.
172 * @param {string} hostname - The hostname to resolve (ex. 'nodejs.org').
173 * @param {object} [options] - Optional settings.
174 * @param {boolean} [options.all=false] - Whether to return all or just the first resolved address.
175 * @param {0 | 4 | 6} [options.family=0] - The record family. Must be 4, 6, or 0 (for both).
176 * @param {number} [options.hints] - One or more supported getaddrinfo flags (supply multiple via
177 * bitwise OR).
178 * @param {boolean} [options.verbatim=false] - Return results in same order DNS resolved them;
179 * otherwise IPv4 then IPv6. New code should supply `true`.
180 */
181function lookup(hostname, options) {
182  let hints = 0;
183  let family = 0;
184  let all = false;
185  let verbatim = getDefaultVerbatim();
186
187  // Parse arguments
188  if (hostname) {
189    validateString(hostname, 'hostname');
190  }
191
192  if (typeof options === 'number') {
193    validateOneOf(options, 'family', validFamilies);
194    family = options;
195  } else if (options !== undefined && typeof options !== 'object') {
196    throw new ERR_INVALID_ARG_TYPE('options', ['integer', 'object'], options);
197  } else {
198    if (options?.hints != null) {
199      validateNumber(options.hints, 'options.hints');
200      hints = options.hints >>> 0;
201      validateHints(hints);
202    }
203    if (options?.family != null) {
204      validateOneOf(options.family, 'options.family', validFamilies);
205      family = options.family;
206    }
207    if (options?.all != null) {
208      validateBoolean(options.all, 'options.all');
209      all = options.all;
210    }
211    if (options?.verbatim != null) {
212      validateBoolean(options.verbatim, 'options.verbatim');
213      verbatim = options.verbatim;
214    }
215  }
216
217  return createLookupPromise(family, hostname, all, hints, verbatim);
218}
219
220
221function onlookupservice(err, hostname, service) {
222  if (err) {
223    this.reject(dnsException(err, 'getnameinfo', this.host));
224    return;
225  }
226
227  this.resolve({ hostname, service });
228  if (this[kPerfHooksDnsLookupServiceContext] && hasObserver('dns')) {
229    stopPerf(this, kPerfHooksDnsLookupServiceContext, { detail: { hostname, service } });
230  }
231}
232
233function createLookupServicePromise(hostname, port) {
234  return new Promise((resolve, reject) => {
235    const req = new GetNameInfoReqWrap();
236
237    req.hostname = hostname;
238    req.port = port;
239    req.oncomplete = onlookupservice;
240    req.resolve = resolve;
241    req.reject = reject;
242
243    const err = getnameinfo(req, hostname, port);
244
245    if (err)
246      reject(dnsException(err, 'getnameinfo', hostname));
247    else if (hasObserver('dns')) {
248      startPerf(req, kPerfHooksDnsLookupServiceContext, {
249        type: 'dns',
250        name: 'lookupService',
251        detail: {
252          host: hostname,
253          port,
254        },
255      });
256    }
257  });
258}
259
260function lookupService(address, port) {
261  if (arguments.length !== 2)
262    throw new ERR_MISSING_ARGS('address', 'port');
263
264  if (isIP(address) === 0)
265    throw new ERR_INVALID_ARG_VALUE('address', address);
266
267  validatePort(port);
268
269  return createLookupServicePromise(address, +port);
270}
271
272
273function onresolve(err, result, ttls) {
274  if (err) {
275    this.reject(dnsException(err, this.bindingName, this.hostname));
276    return;
277  }
278
279  if (ttls && this.ttl)
280    result = ArrayPrototypeMap(
281      result, (address, index) => ({ address, ttl: ttls[index] }));
282
283  this.resolve(result);
284  if (this[kPerfHooksDnsLookupResolveContext] && hasObserver('dns')) {
285    stopPerf(this, kPerfHooksDnsLookupResolveContext, { detail: { result } });
286  }
287}
288
289function createResolverPromise(resolver, bindingName, hostname, ttl) {
290  return new Promise((resolve, reject) => {
291    const req = new QueryReqWrap();
292
293    req.bindingName = bindingName;
294    req.hostname = hostname;
295    req.oncomplete = onresolve;
296    req.resolve = resolve;
297    req.reject = reject;
298    req.ttl = ttl;
299
300    const err = resolver._handle[bindingName](req, hostname);
301
302    if (err)
303      reject(dnsException(err, bindingName, hostname));
304    else if (hasObserver('dns')) {
305      startPerf(req, kPerfHooksDnsLookupResolveContext, {
306        type: 'dns',
307        name: bindingName,
308        detail: {
309          host: hostname,
310          ttl,
311        },
312      });
313    }
314  });
315}
316
317function resolver(bindingName) {
318  function query(name, options) {
319    validateString(name, 'name');
320
321    const ttl = !!(options && options.ttl);
322    return createResolverPromise(this, bindingName, name, ttl);
323  }
324
325  ObjectDefineProperty(query, 'name', { __proto__: null, value: bindingName });
326  return query;
327}
328
329function resolve(hostname, rrtype) {
330  let resolver;
331
332  if (rrtype !== undefined) {
333    validateString(rrtype, 'rrtype');
334
335    resolver = resolveMap[rrtype];
336
337    if (typeof resolver !== 'function')
338      throw new ERR_INVALID_ARG_VALUE('rrtype', rrtype);
339  } else {
340    resolver = resolveMap.A;
341  }
342
343  return ReflectApply(resolver, this, [hostname]);
344}
345
346// Promise-based resolver.
347const { Resolver, resolveMap } = createResolverClass(resolver);
348Resolver.prototype.resolve = resolve;
349
350function defaultResolverSetServers(servers) {
351  const resolver = new Resolver();
352
353  resolver.setServers(servers);
354  setDefaultResolver(resolver);
355  bindDefaultResolver(module.exports, Resolver.prototype);
356}
357
358module.exports = {
359  lookup,
360  lookupService,
361  Resolver,
362  getDefaultResultOrder,
363  setDefaultResultOrder,
364  setServers: defaultResolverSetServers,
365
366  // ERROR CODES
367  NODATA,
368  FORMERR,
369  SERVFAIL,
370  NOTFOUND,
371  NOTIMP,
372  REFUSED,
373  BADQUERY,
374  BADNAME,
375  BADFAMILY,
376  BADRESP,
377  CONNREFUSED,
378  TIMEOUT,
379  EOF,
380  FILE,
381  NOMEM,
382  DESTRUCTION,
383  BADSTR,
384  BADFLAGS,
385  NONAME,
386  BADHINTS,
387  NOTINITIALIZED,
388  LOADIPHLPAPI,
389  ADDRGETNETWORKPARAMS,
390  CANCELLED,
391};
392bindDefaultResolver(module.exports, Resolver.prototype);
393