• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayPrototypeForEach,
5  ArrayPrototypeJoin,
6  ArrayPrototypeMap,
7  ArrayPrototypePush,
8  FunctionPrototypeBind,
9  NumberParseInt,
10  RegExpPrototypeExec,
11  RegExpPrototypeSymbolReplace,
12  ObjectCreate,
13  Symbol,
14} = primordials;
15
16const errors = require('internal/errors');
17const { isIP } = require('internal/net');
18const { getOptionValue } = require('internal/options');
19const {
20  validateArray,
21  validateInt32,
22  validateOneOf,
23  validateString,
24} = require('internal/validators');
25let binding;
26function lazyBinding() {
27  binding ??= internalBinding('cares_wrap');
28  return binding;
29}
30const IANA_DNS_PORT = 53;
31const IPv6RE = /^\[([^[\]]*)\]/;
32const addrSplitRE = /(^.+?)(?::(\d+))?$/;
33const {
34  ERR_DNS_SET_SERVERS_FAILED,
35  ERR_INVALID_ARG_VALUE,
36  ERR_INVALID_IP_ADDRESS,
37} = errors.codes;
38
39const {
40  namespace: {
41    addSerializeCallback,
42    addDeserializeCallback,
43    isBuildingSnapshot,
44  },
45} = require('internal/v8/startup_snapshot');
46
47function validateTimeout(options) {
48  const { timeout = -1 } = { ...options };
49  validateInt32(timeout, 'options.timeout', -1);
50  return timeout;
51}
52
53function validateTries(options) {
54  const { tries = 4 } = { ...options };
55  validateInt32(tries, 'options.tries', 1);
56  return tries;
57}
58
59const kSerializeResolver = Symbol('dns:resolver:serialize');
60const kDeserializeResolver = Symbol('dns:resolver:deserialize');
61const kSnapshotStates = Symbol('dns:resolver:config');
62const kInitializeHandle = Symbol('dns:resolver:initializeHandle');
63const kSetServersInteral = Symbol('dns:resolver:setServers');
64
65// Resolver instances correspond 1:1 to c-ares channels.
66
67class ResolverBase {
68  constructor(options = undefined) {
69    const timeout = validateTimeout(options);
70    const tries = validateTries(options);
71    // If we are building snapshot, save the states of the resolver along
72    // the way.
73    if (isBuildingSnapshot()) {
74      this[kSnapshotStates] = { timeout, tries };
75    }
76    this[kInitializeHandle](timeout, tries);
77  }
78
79  [kInitializeHandle](timeout, tries) {
80    const { ChannelWrap } = lazyBinding();
81    this._handle = new ChannelWrap(timeout, tries);
82  }
83
84  cancel() {
85    this._handle.cancel();
86  }
87
88  getServers() {
89    return ArrayPrototypeMap(this._handle.getServers() || [], (val) => {
90      if (!val[1] || val[1] === IANA_DNS_PORT)
91        return val[0];
92
93      const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
94      return `${host}:${val[1]}`;
95    });
96  }
97
98  setServers(servers) {
99    validateArray(servers, 'servers');
100
101    // Cache the original servers because in the event of an error while
102    // setting the servers, c-ares won't have any servers available for
103    // resolution.
104    const newSet = [];
105    ArrayPrototypeForEach(servers, (serv, index) => {
106      validateString(serv, `servers[${index}]`);
107      let ipVersion = isIP(serv);
108
109      if (ipVersion !== 0)
110        return ArrayPrototypePush(newSet, [ipVersion, serv, IANA_DNS_PORT]);
111
112      const match = RegExpPrototypeExec(IPv6RE, serv);
113
114      // Check for an IPv6 in brackets.
115      if (match) {
116        ipVersion = isIP(match[1]);
117
118        if (ipVersion !== 0) {
119          const port = NumberParseInt(
120            RegExpPrototypeSymbolReplace(addrSplitRE, serv, '$2')) || IANA_DNS_PORT;
121          return ArrayPrototypePush(newSet, [ipVersion, match[1], port]);
122        }
123      }
124
125      // addr::port
126      const addrSplitMatch = RegExpPrototypeExec(addrSplitRE, serv);
127
128      if (addrSplitMatch) {
129        const hostIP = addrSplitMatch[1];
130        const port = addrSplitMatch[2] || IANA_DNS_PORT;
131
132        ipVersion = isIP(hostIP);
133
134        if (ipVersion !== 0) {
135          return ArrayPrototypePush(
136            newSet, [ipVersion, hostIP, NumberParseInt(port)]);
137        }
138      }
139
140      throw new ERR_INVALID_IP_ADDRESS(serv);
141    });
142
143    this[kSetServersInteral](newSet, servers);
144  }
145
146  [kSetServersInteral](newSet, servers) {
147    const orig = this._handle.getServers() || [];
148    const errorNumber = this._handle.setServers(newSet);
149
150    if (errorNumber !== 0) {
151      // Reset the servers to the old servers, because ares probably unset them.
152      this._handle.setServers(ArrayPrototypeJoin(orig, ','));
153      const { strerror } = lazyBinding();
154      const err = strerror(errorNumber);
155      throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
156    }
157
158    if (isBuildingSnapshot()) {
159      this[kSnapshotStates].servers = newSet;
160    }
161  }
162
163
164  setLocalAddress(ipv4, ipv6) {
165    validateString(ipv4, 'ipv4');
166
167    if (ipv6 !== undefined) {
168      validateString(ipv6, 'ipv6');
169    }
170
171    this._handle.setLocalAddress(ipv4, ipv6);
172
173    if (isBuildingSnapshot()) {
174      this[kSnapshotStates].localAddress = { ipv4, ipv6 };
175    }
176  }
177
178  // TODO(joyeecheung): consider exposing this if custom DNS resolvers
179  // end up being useful for snapshot users.
180  [kSerializeResolver]() {
181    this._handle = null;  // We'll restore it during deserialization.
182    addDeserializeCallback(function deserializeResolver(resolver) {
183      resolver[kDeserializeResolver]();
184    }, this);
185  }
186
187  [kDeserializeResolver]() {
188    const { timeout, tries, localAddress, servers } = this[kSnapshotStates];
189    this[kInitializeHandle](timeout, tries);
190    if (localAddress) {
191      const { ipv4, ipv6 } = localAddress;
192      this._handle.setLocalAddress(ipv4, ipv6);
193    }
194    if (servers) {
195      this[kSetServersInteral](servers, servers);
196    }
197  }
198}
199
200let defaultResolver;
201let dnsOrder;
202
203function initializeDns() {
204  const orderFromCLI = getOptionValue('--dns-result-order');
205  if (!orderFromCLI) {
206    dnsOrder ??= 'verbatim';
207  } else {
208    // Allow the deserialized application to override order from CLI.
209    dnsOrder = orderFromCLI;
210  }
211
212  if (!isBuildingSnapshot()) {
213    return;
214  }
215
216  addSerializeCallback(() => {
217    defaultResolver?.[kSerializeResolver]();
218  });
219}
220
221const resolverKeys = [
222  'getServers',
223  'resolve',
224  'resolve4',
225  'resolve6',
226  'resolveAny',
227  'resolveCaa',
228  'resolveCname',
229  'resolveMx',
230  'resolveNaptr',
231  'resolveNs',
232  'resolvePtr',
233  'resolveSoa',
234  'resolveSrv',
235  'resolveTxt',
236  'reverse',
237];
238
239function getDefaultResolver() {
240  // We do this here instead of pre-execution so that the default resolver is
241  // only ever created when the user loads any dns module.
242  if (defaultResolver === undefined) {
243    defaultResolver = new ResolverBase();
244  }
245  return defaultResolver;
246}
247
248function setDefaultResolver(resolver) {
249  defaultResolver = resolver;
250}
251
252function bindDefaultResolver(target, source) {
253  const defaultResolver = getDefaultResolver();
254  ArrayPrototypeForEach(resolverKeys, (key) => {
255    target[key] = FunctionPrototypeBind(source[key], defaultResolver);
256  });
257}
258
259function validateHints(hints) {
260  const { AI_ADDRCONFIG, AI_ALL, AI_V4MAPPED } = lazyBinding();
261  if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
262    throw new ERR_INVALID_ARG_VALUE('hints', hints);
263  }
264}
265
266let invalidHostnameWarningEmitted = false;
267function emitInvalidHostnameWarning(hostname) {
268  if (!invalidHostnameWarningEmitted) {
269    process.emitWarning(
270      `The provided hostname "${hostname}" is not a valid ` +
271      'hostname, and is supported in the dns module solely for compatibility.',
272      'DeprecationWarning',
273      'DEP0118',
274    );
275    invalidHostnameWarningEmitted = true;
276  }
277}
278
279function getDefaultVerbatim() {
280  return dnsOrder !== 'ipv4first';
281}
282
283function setDefaultResultOrder(value) {
284  validateOneOf(value, 'dnsOrder', ['verbatim', 'ipv4first']);
285  dnsOrder = value;
286}
287
288function getDefaultResultOrder() {
289  return dnsOrder;
290}
291
292function createResolverClass(resolver) {
293  const resolveMap = ObjectCreate(null);
294
295  class Resolver extends ResolverBase {}
296
297  Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
298  Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
299  Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
300  Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
301  Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
302  Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
303  Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
304  Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
305  Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
306  Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
307  Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
308  Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
309  Resolver.prototype.reverse = resolver('getHostByAddr');
310
311  return {
312    resolveMap,
313    Resolver,
314  };
315}
316
317// ERROR CODES
318const errorCodes = {
319  NODATA: 'ENODATA',
320  FORMERR: 'EFORMERR',
321  SERVFAIL: 'ESERVFAIL',
322  NOTFOUND: 'ENOTFOUND',
323  NOTIMP: 'ENOTIMP',
324  REFUSED: 'EREFUSED',
325  BADQUERY: 'EBADQUERY',
326  BADNAME: 'EBADNAME',
327  BADFAMILY: 'EBADFAMILY',
328  BADRESP: 'EBADRESP',
329  CONNREFUSED: 'ECONNREFUSED',
330  TIMEOUT: 'ETIMEOUT',
331  EOF: 'EOF',
332  FILE: 'EFILE',
333  NOMEM: 'ENOMEM',
334  DESTRUCTION: 'EDESTRUCTION',
335  BADSTR: 'EBADSTR',
336  BADFLAGS: 'EBADFLAGS',
337  NONAME: 'ENONAME',
338  BADHINTS: 'EBADHINTS',
339  NOTINITIALIZED: 'ENOTINITIALIZED',
340  LOADIPHLPAPI: 'ELOADIPHLPAPI',
341  ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
342  CANCELLED: 'ECANCELLED',
343};
344
345module.exports = {
346  bindDefaultResolver,
347  getDefaultResolver,
348  setDefaultResolver,
349  validateHints,
350  validateTimeout,
351  validateTries,
352  emitInvalidHostnameWarning,
353  getDefaultVerbatim,
354  getDefaultResultOrder,
355  setDefaultResultOrder,
356  errorCodes,
357  createResolverClass,
358  initializeDns,
359};
360