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