1// Copyright Joyent, Inc. and other Node contributors. 2// 3// Permission is hereby granted, free of charge, to any person obtaining a 4// copy of this software and associated documentation files (the 5// "Software"), to deal in the Software without restriction, including 6// without limitation the rights to use, copy, modify, merge, publish, 7// distribute, sublicense, and/or sell copies of the Software, and to permit 8// persons to whom the Software is furnished to do so, subject to the 9// following conditions: 10// 11// The above copyright notice and this permission notice shall be included 12// in all copies or substantial portions of the Software. 13// 14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20// USE OR OTHER DEALINGS IN THE SOFTWARE. 21 22'use strict'; 23 24const { 25 ArrayPrototypeMap, 26 ObjectCreate, 27 ObjectDefineProperties, 28 ObjectDefineProperty, 29 ReflectApply, 30} = primordials; 31 32const cares = internalBinding('cares_wrap'); 33const { toASCII } = require('internal/idna'); 34const { isIP } = require('internal/net'); 35const { customPromisifyArgs } = require('internal/util'); 36const errors = require('internal/errors'); 37const { 38 bindDefaultResolver, 39 getDefaultResolver, 40 setDefaultResolver, 41 Resolver, 42 validateHints, 43 emitInvalidHostnameWarning, 44 getDefaultVerbatim, 45 setDefaultResultOrder, 46} = require('internal/dns/utils'); 47const { 48 ERR_INVALID_ARG_TYPE, 49 ERR_INVALID_CALLBACK, 50 ERR_INVALID_OPT_VALUE, 51 ERR_MISSING_ARGS, 52} = errors.codes; 53const { 54 validatePort, 55 validateString, 56 validateOneOf, 57} = require('internal/validators'); 58 59const { 60 GetAddrInfoReqWrap, 61 GetNameInfoReqWrap, 62 QueryReqWrap, 63} = cares; 64 65const dnsException = errors.dnsException; 66 67let promises = null; // Lazy loaded 68 69function onlookup(err, addresses) { 70 if (err) { 71 return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); 72 } 73 this.callback(null, addresses[0], this.family || isIP(addresses[0])); 74} 75 76 77function onlookupall(err, addresses) { 78 if (err) { 79 return this.callback(dnsException(err, 'getaddrinfo', this.hostname)); 80 } 81 82 const family = this.family; 83 for (let i = 0; i < addresses.length; i++) { 84 const addr = addresses[i]; 85 addresses[i] = { 86 address: addr, 87 family: family || isIP(addr) 88 }; 89 } 90 91 this.callback(null, addresses); 92} 93 94 95// Easy DNS A/AAAA look up 96// lookup(hostname, [options,] callback) 97function lookup(hostname, options, callback) { 98 let hints = 0; 99 let family = -1; 100 let all = false; 101 let verbatim = getDefaultVerbatim(); 102 103 // Parse arguments 104 if (hostname && typeof hostname !== 'string') { 105 throw new ERR_INVALID_ARG_TYPE('hostname', 'string', hostname); 106 } else if (typeof options === 'function') { 107 callback = options; 108 family = 0; 109 } else if (typeof callback !== 'function') { 110 throw new ERR_INVALID_CALLBACK(callback); 111 } else if (options !== null && typeof options === 'object') { 112 hints = options.hints >>> 0; 113 family = options.family >>> 0; 114 all = options.all === true; 115 if (typeof options.verbatim === 'boolean') { 116 verbatim = options.verbatim === true; 117 } 118 119 validateHints(hints); 120 } else { 121 family = options >>> 0; 122 } 123 124 validateOneOf(family, 'family', [0, 4, 6], true); 125 126 if (!hostname) { 127 emitInvalidHostnameWarning(hostname); 128 if (all) { 129 process.nextTick(callback, null, []); 130 } else { 131 process.nextTick(callback, null, null, family === 6 ? 6 : 4); 132 } 133 return {}; 134 } 135 136 const matchedFamily = isIP(hostname); 137 if (matchedFamily) { 138 if (all) { 139 process.nextTick( 140 callback, null, [{ address: hostname, family: matchedFamily }]); 141 } else { 142 process.nextTick(callback, null, hostname, matchedFamily); 143 } 144 return {}; 145 } 146 147 const req = new GetAddrInfoReqWrap(); 148 req.callback = callback; 149 req.family = family; 150 req.hostname = hostname; 151 req.oncomplete = all ? onlookupall : onlookup; 152 153 const err = cares.getaddrinfo( 154 req, toASCII(hostname), family, hints, verbatim 155 ); 156 if (err) { 157 process.nextTick(callback, dnsException(err, 'getaddrinfo', hostname)); 158 return {}; 159 } 160 return req; 161} 162 163ObjectDefineProperty(lookup, customPromisifyArgs, 164 { value: ['address', 'family'], enumerable: false }); 165 166 167function onlookupservice(err, hostname, service) { 168 if (err) 169 return this.callback(dnsException(err, 'getnameinfo', this.hostname)); 170 171 this.callback(null, hostname, service); 172} 173 174 175function lookupService(address, port, callback) { 176 if (arguments.length !== 3) 177 throw new ERR_MISSING_ARGS('address', 'port', 'callback'); 178 179 if (isIP(address) === 0) 180 throw new ERR_INVALID_OPT_VALUE('address', address); 181 182 validatePort(port); 183 184 if (typeof callback !== 'function') 185 throw new ERR_INVALID_CALLBACK(callback); 186 187 port = +port; 188 189 const req = new GetNameInfoReqWrap(); 190 req.callback = callback; 191 req.hostname = address; 192 req.port = port; 193 req.oncomplete = onlookupservice; 194 195 const err = cares.getnameinfo(req, address, port); 196 if (err) throw dnsException(err, 'getnameinfo', address); 197 return req; 198} 199 200ObjectDefineProperty(lookupService, customPromisifyArgs, 201 { value: ['hostname', 'service'], enumerable: false }); 202 203 204function onresolve(err, result, ttls) { 205 if (ttls && this.ttl) 206 result = ArrayPrototypeMap( 207 result, (address, index) => ({ address, ttl: ttls[index] })); 208 209 if (err) 210 this.callback(dnsException(err, this.bindingName, this.hostname)); 211 else 212 this.callback(null, result); 213} 214 215function resolver(bindingName) { 216 function query(name, /* options, */ callback) { 217 let options; 218 if (arguments.length > 2) { 219 options = callback; 220 callback = arguments[2]; 221 } 222 223 validateString(name, 'name'); 224 if (typeof callback !== 'function') { 225 throw new ERR_INVALID_CALLBACK(callback); 226 } 227 228 const req = new QueryReqWrap(); 229 req.bindingName = bindingName; 230 req.callback = callback; 231 req.hostname = name; 232 req.oncomplete = onresolve; 233 req.ttl = !!(options && options.ttl); 234 const err = this._handle[bindingName](req, toASCII(name)); 235 if (err) throw dnsException(err, bindingName, name); 236 return req; 237 } 238 ObjectDefineProperty(query, 'name', { value: bindingName }); 239 return query; 240} 241 242const resolveMap = ObjectCreate(null); 243Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny'); 244Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA'); 245Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa'); 246Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa'); 247Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname'); 248Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx'); 249Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs'); 250Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt'); 251Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv'); 252Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr'); 253Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr'); 254Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa'); 255Resolver.prototype.reverse = resolver('getHostByAddr'); 256 257Resolver.prototype.resolve = resolve; 258 259function resolve(hostname, rrtype, callback) { 260 let resolver; 261 if (typeof rrtype === 'string') { 262 resolver = resolveMap[rrtype]; 263 } else if (typeof rrtype === 'function') { 264 resolver = resolveMap.A; 265 callback = rrtype; 266 } else { 267 throw new ERR_INVALID_ARG_TYPE('rrtype', 'string', rrtype); 268 } 269 270 if (typeof resolver === 'function') { 271 return ReflectApply(resolver, this, [hostname, callback]); 272 } 273 throw new ERR_INVALID_OPT_VALUE('rrtype', rrtype); 274} 275 276function defaultResolverSetServers(servers) { 277 const resolver = new Resolver(); 278 279 resolver.setServers(servers); 280 setDefaultResolver(resolver); 281 bindDefaultResolver(module.exports, Resolver.prototype); 282 283 if (promises !== null) 284 bindDefaultResolver(promises, promises.Resolver.prototype); 285} 286 287module.exports = { 288 lookup, 289 lookupService, 290 291 Resolver, 292 setDefaultResultOrder, 293 setServers: defaultResolverSetServers, 294 295 // uv_getaddrinfo flags 296 ADDRCONFIG: cares.AI_ADDRCONFIG, 297 ALL: cares.AI_ALL, 298 V4MAPPED: cares.AI_V4MAPPED, 299 300 // ERROR CODES 301 NODATA: 'ENODATA', 302 FORMERR: 'EFORMERR', 303 SERVFAIL: 'ESERVFAIL', 304 NOTFOUND: 'ENOTFOUND', 305 NOTIMP: 'ENOTIMP', 306 REFUSED: 'EREFUSED', 307 BADQUERY: 'EBADQUERY', 308 BADNAME: 'EBADNAME', 309 BADFAMILY: 'EBADFAMILY', 310 BADRESP: 'EBADRESP', 311 CONNREFUSED: 'ECONNREFUSED', 312 TIMEOUT: 'ETIMEOUT', 313 EOF: 'EOF', 314 FILE: 'EFILE', 315 NOMEM: 'ENOMEM', 316 DESTRUCTION: 'EDESTRUCTION', 317 BADSTR: 'EBADSTR', 318 BADFLAGS: 'EBADFLAGS', 319 NONAME: 'ENONAME', 320 BADHINTS: 'EBADHINTS', 321 NOTINITIALIZED: 'ENOTINITIALIZED', 322 LOADIPHLPAPI: 'ELOADIPHLPAPI', 323 ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS', 324 CANCELLED: 'ECANCELLED' 325}; 326 327bindDefaultResolver(module.exports, getDefaultResolver()); 328 329ObjectDefineProperties(module.exports, { 330 promises: { 331 configurable: true, 332 enumerable: true, 333 get() { 334 if (promises === null) { 335 promises = require('internal/dns/promises'); 336 promises.setServers = defaultResolverSetServers; 337 promises.setDefaultResultOrder = setDefaultResultOrder; 338 } 339 return promises; 340 } 341 } 342}); 343