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