• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2
3const {
4  ArrayIsArray,
5  ArrayPrototypeForEach,
6  ArrayPrototypeJoin,
7  ArrayPrototypeMap,
8  ArrayPrototypePush,
9  FunctionPrototypeBind,
10  NumberParseInt,
11  StringPrototypeMatch,
12  StringPrototypeReplace,
13} = primordials;
14
15const errors = require('internal/errors');
16const { isIP } = require('internal/net');
17const { getOptionValue } = require('internal/options');
18const {
19  validateInt32,
20  validateOneOf,
21  validateString,
22} = require('internal/validators');
23const {
24  ChannelWrap,
25  strerror,
26  AI_ADDRCONFIG,
27  AI_ALL,
28  AI_V4MAPPED,
29} = internalBinding('cares_wrap');
30const IANA_DNS_PORT = 53;
31const IPv6RE = /^\[([^[\]]*)\]/;
32const addrSplitRE = /(^.+?)(?::(\d+))?$/;
33const {
34  ERR_DNS_SET_SERVERS_FAILED,
35  ERR_INVALID_ARG_TYPE,
36  ERR_INVALID_IP_ADDRESS,
37  ERR_INVALID_OPT_VALUE
38} = errors.codes;
39
40function validateTimeout(options) {
41  const { timeout = -1 } = { ...options };
42  validateInt32(timeout, 'options.timeout', -1, 2 ** 31 - 1);
43  return timeout;
44}
45
46function validateTries(options) {
47  const { tries = 4 } = { ...options };
48  validateInt32(tries, 'options.tries', 1, 2 ** 31 - 1);
49  return tries;
50}
51
52// Resolver instances correspond 1:1 to c-ares channels.
53class Resolver {
54  constructor(options = undefined) {
55    const timeout = validateTimeout(options);
56    const tries = validateTries(options);
57    this._handle = new ChannelWrap(timeout, tries);
58  }
59
60  cancel() {
61    this._handle.cancel();
62  }
63
64  getServers() {
65    return ArrayPrototypeMap(this._handle.getServers(), (val) => {
66      if (!val[1] || val[1] === IANA_DNS_PORT)
67        return val[0];
68
69      const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
70      return `${host}:${val[1]}`;
71    });
72  }
73
74  setServers(servers) {
75    if (!ArrayIsArray(servers)) {
76      throw new ERR_INVALID_ARG_TYPE('servers', 'Array', servers);
77    }
78
79    // Cache the original servers because in the event of an error while
80    // setting the servers, c-ares won't have any servers available for
81    // resolution.
82    const orig = this._handle.getServers();
83    const newSet = [];
84
85    ArrayPrototypeForEach(servers, (serv, index) => {
86      validateString(serv, `servers[${index}]`);
87      let ipVersion = isIP(serv);
88
89      if (ipVersion !== 0)
90        return ArrayPrototypePush(newSet, [ipVersion, serv, IANA_DNS_PORT]);
91
92      const match = StringPrototypeMatch(serv, IPv6RE);
93
94      // Check for an IPv6 in brackets.
95      if (match) {
96        ipVersion = isIP(match[1]);
97
98        if (ipVersion !== 0) {
99          const port = NumberParseInt(
100            StringPrototypeReplace(serv, addrSplitRE, '$2')) || IANA_DNS_PORT;
101          return ArrayPrototypePush(newSet, [ipVersion, match[1], port]);
102        }
103      }
104
105      // addr::port
106      const addrSplitMatch = StringPrototypeMatch(serv, addrSplitRE);
107
108      if (addrSplitMatch) {
109        const hostIP = addrSplitMatch[1];
110        const port = addrSplitMatch[2] || IANA_DNS_PORT;
111
112        ipVersion = isIP(hostIP);
113
114        if (ipVersion !== 0) {
115          return ArrayPrototypePush(
116            newSet, [ipVersion, hostIP, NumberParseInt(port)]);
117        }
118      }
119
120      throw new ERR_INVALID_IP_ADDRESS(serv);
121    });
122
123    const errorNumber = this._handle.setServers(newSet);
124
125    if (errorNumber !== 0) {
126      // Reset the servers to the old servers, because ares probably unset them.
127      this._handle.setServers(ArrayPrototypeJoin(orig, ','));
128      const err = strerror(errorNumber);
129      throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
130    }
131  }
132
133  setLocalAddress(ipv4, ipv6) {
134    validateString(ipv4, 'ipv4');
135
136    if (typeof ipv6 !== 'string' && ipv6 !== undefined) {
137      throw new ERR_INVALID_ARG_TYPE('ipv6', ['String', 'undefined'], ipv6);
138    }
139
140    this._handle.setLocalAddress(ipv4, ipv6);
141  }
142}
143
144let defaultResolver = new Resolver();
145const resolverKeys = [
146  'getServers',
147  'resolve',
148  'resolve4',
149  'resolve6',
150  'resolveAny',
151  'resolveCaa',
152  'resolveCname',
153  'resolveMx',
154  'resolveNaptr',
155  'resolveNs',
156  'resolvePtr',
157  'resolveSoa',
158  'resolveSrv',
159  'resolveTxt',
160  'reverse',
161];
162
163function getDefaultResolver() {
164  return defaultResolver;
165}
166
167function setDefaultResolver(resolver) {
168  defaultResolver = resolver;
169}
170
171function bindDefaultResolver(target, source) {
172  ArrayPrototypeForEach(resolverKeys, (key) => {
173    target[key] = FunctionPrototypeBind(source[key], defaultResolver);
174  });
175}
176
177function validateHints(hints) {
178  if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
179    throw new ERR_INVALID_OPT_VALUE('hints', hints);
180  }
181}
182
183let invalidHostnameWarningEmitted = false;
184
185function emitInvalidHostnameWarning(hostname) {
186  if (invalidHostnameWarningEmitted) {
187    return;
188  }
189  invalidHostnameWarningEmitted = true;
190  process.emitWarning(
191    `The provided hostname "${hostname}" is not a valid ` +
192    'hostname, and is supported in the dns module solely for compatibility.',
193    'DeprecationWarning',
194    'DEP0118'
195  );
196}
197
198let dnsOrder = getOptionValue('--dns-result-order') || 'ipv4first';
199
200function getDefaultVerbatim() {
201  switch (dnsOrder) {
202    case 'verbatim':
203      return true;
204    case 'ipv4first':
205    default:
206      return false;
207  }
208}
209
210function setDefaultResultOrder(value) {
211  validateOneOf(value, 'dnsOrder', ['verbatim', 'ipv4first']);
212  dnsOrder = value;
213}
214
215module.exports = {
216  bindDefaultResolver,
217  getDefaultResolver,
218  setDefaultResolver,
219  validateHints,
220  validateTimeout,
221  validateTries,
222  Resolver,
223  emitInvalidHostnameWarning,
224  getDefaultVerbatim,
225  setDefaultResultOrder,
226};
227