3 const net = require('net');
4 const util = require('util');
6 const cares = process.binding('cares_wrap');
7 const uv = process.binding('uv');
9 const GetAddrInfoReqWrap = cares.GetAddrInfoReqWrap;
10 const GetNameInfoReqWrap = cares.GetNameInfoReqWrap;
11 const QueryReqWrap = cares.QueryReqWrap;
13 const isIp = net.isIP;
16 function errnoException(err, syscall, hostname) {
17 // FIXME(bnoordhuis) Remove this backwards compatibility nonsense and pass
18 // the true error to the user. ENOTFOUND is not even a proper POSIX error!
19 if (err === uv.UV_EAI_MEMORY ||
20 err === uv.UV_EAI_NODATA ||
21 err === uv.UV_EAI_NONAME) {
25 if (typeof err === 'string') { // c-ares error code.
26 ex = new Error(syscall + ' ' + err + (hostname ? ' ' + hostname : ''));
31 ex = util._errnoException(err, syscall);
34 ex.hostname = hostname;
40 // c-ares invokes a callback either synchronously or asynchronously,
41 // but the dns API should always invoke a callback asynchronously.
43 // This function makes sure that the callback is invoked asynchronously.
44 // It returns a function that invokes the callback within nextTick().
46 // To avoid invoking unnecessary nextTick(), `immediately` property of
47 // returned function should be set to true after c-ares returned.
51 // function someAPI(callback) {
52 // callback = makeAsync(callback);
53 // channel.someAPI(..., callback);
54 // callback.immediately = true;
56 function makeAsync(callback) {
57 if (typeof callback !== 'function') {
60 return function asyncCallback() {
61 if (asyncCallback.immediately) {
62 // The API already returned, we can invoke the callback immediately.
63 callback.apply(null, arguments);
65 var args = new Array(arguments.length + 1);
67 for (var i = 1, a = 0; a < arguments.length; ++i, ++a)
68 args[i] = arguments[a];
69 process.nextTick.apply(null, args);
75 function onlookup(err, addresses) {
77 return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
80 this.callback(null, addresses[0], this.family);
82 this.callback(null, addresses[0], addresses[0].indexOf(':') >= 0 ? 6 : 4);
87 function onlookupall(err, addresses) {
90 return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
93 for (var i = 0; i < addresses.length; i++) {
95 address: addresses[i],
96 family: this.family || (addresses[i].indexOf(':') >= 0 ? 6 : 4)
100 this.callback(null, results);
104 // Easy DNS A/AAAA look up
105 // lookup(hostname, [options,] callback)
106 exports.lookup = function lookup(hostname, options, callback) {
112 if (hostname && typeof hostname !== 'string') {
113 throw new TypeError('invalid arguments: ' +
114 'hostname must be a string or falsey');
115 } else if (typeof options === 'function') {
118 } else if (typeof callback !== 'function') {
119 throw new TypeError('invalid arguments: callback must be passed');
120 } else if (options !== null && typeof options === 'object') {
121 hints = options.hints >>> 0;
122 family = options.family >>> 0;
123 all = options.all === true;
126 hints !== exports.ADDRCONFIG &&
127 hints !== exports.V4MAPPED &&
128 hints !== (exports.ADDRCONFIG | exports.V4MAPPED)) {
129 throw new TypeError('invalid argument: hints must use valid flags');
132 family = options >>> 0;
135 if (family !== 0 && family !== 4 && family !== 6)
136 throw new TypeError('invalid argument: family must be 4 or 6');
138 callback = makeAsync(callback);
144 callback(null, null, family === 6 ? 6 : 4);
149 var matchedFamily = net.isIP(hostname);
152 callback(null, [{address: hostname, family: matchedFamily}]);
154 callback(null, hostname, matchedFamily);
159 var req = new GetAddrInfoReqWrap();
160 req.callback = callback;
162 req.hostname = hostname;
163 req.oncomplete = all ? onlookupall : onlookup;
165 var err = cares.getaddrinfo(req, hostname, family, hints);
167 callback(errnoException(err, 'getaddrinfo', hostname));
171 callback.immediately = true;
176 function onlookupservice(err, host, service) {
178 return this.callback(errnoException(err, 'getnameinfo', this.host));
180 this.callback(null, host, service);
184 // lookupService(address, port, callback)
185 exports.lookupService = function(host, port, callback) {
186 if (arguments.length !== 3)
187 throw new Error('invalid arguments');
189 if (cares.isIP(host) === 0)
190 throw new TypeError('host needs to be a valid IP address');
192 callback = makeAsync(callback);
194 var req = new GetNameInfoReqWrap();
195 req.callback = callback;
198 req.oncomplete = onlookupservice;
200 var err = cares.getnameinfo(req, host, port);
201 if (err) throw errnoException(err, 'getnameinfo', host);
203 callback.immediately = true;
208 function onresolve(err, result) {
210 this.callback(errnoException(err, this.bindingName, this.hostname));
212 this.callback(null, result);
216 function resolver(bindingName) {
217 var binding = cares[bindingName];
219 return function query(name, callback) {
220 if (typeof name !== 'string') {
221 throw new Error('Name must be a string');
222 } else if (typeof callback !== 'function') {
223 throw new Error('Callback must be a function');
226 callback = makeAsync(callback);
227 var req = new QueryReqWrap();
228 req.bindingName = bindingName;
229 req.callback = callback;
231 req.oncomplete = onresolve;
232 var err = binding(req, name);
233 if (err) throw errnoException(err, bindingName);
234 callback.immediately = true;
241 exports.resolve4 = resolveMap.A = resolver('queryA');
242 exports.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
243 exports.resolveCname = resolveMap.CNAME = resolver('queryCname');
244 exports.resolveMx = resolveMap.MX = resolver('queryMx');
245 exports.resolveNs = resolveMap.NS = resolver('queryNs');
246 exports.resolveTxt = resolveMap.TXT = resolver('queryTxt');
247 exports.resolveSrv = resolveMap.SRV = resolver('querySrv');
248 exports.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
249 exports.resolveSoa = resolveMap.SOA = resolver('querySoa');
250 exports.reverse = resolveMap.PTR = resolver('getHostByAddr');
253 exports.resolve = function(hostname, type_, callback_) {
254 var resolver, callback;
255 if (typeof type_ === 'string') {
256 resolver = resolveMap[type_];
257 callback = callback_;
258 } else if (typeof type_ === 'function') {
259 resolver = exports.resolve4;
262 throw new Error('Type must be a string');
265 if (typeof resolver === 'function') {
266 return resolver(hostname, callback);
268 throw new Error('Unknown type "' + type_ + '"');
273 exports.getServers = function() {
274 return cares.getServers();
278 exports.setServers = function(servers) {
279 // cache the original servers because in the event of an error setting the
280 // servers cares won't have any servers available for resolution
281 var orig = cares.getServers();
285 servers.forEach(function(serv) {
286 var ver = isIp(serv);
289 return newSet.push([ver, serv]);
291 var match = serv.match(/\[(.*)\](:\d+)?/);
293 // we have an IPv6 in brackets
295 ver = isIp(match[1]);
297 return newSet.push([ver, match[1]]);
300 var s = serv.split(/:\d+$/)[0];
304 return newSet.push([ver, s]);
306 throw new Error('IP address is not properly formatted: ' + serv);
309 var r = cares.setServers(newSet);
312 // reset the servers to the old servers, because ares probably unset them
313 cares.setServers(orig.join(','));
315 var err = cares.strerror(r);
316 throw new Error('c-ares failed to set servers: "' + err +
317 '" [' + servers + ']');
321 // uv_getaddrinfo flags
322 exports.ADDRCONFIG = cares.AI_ADDRCONFIG;
323 exports.V4MAPPED = cares.AI_V4MAPPED;
326 exports.NODATA = 'ENODATA';
327 exports.FORMERR = 'EFORMERR';
328 exports.SERVFAIL = 'ESERVFAIL';
329 exports.NOTFOUND = 'ENOTFOUND';
330 exports.NOTIMP = 'ENOTIMP';
331 exports.REFUSED = 'EREFUSED';
332 exports.BADQUERY = 'EBADQUERY';
333 exports.ADNAME = 'EADNAME';
334 exports.BADNAME = 'EBADNAME';
335 exports.BADFAMILY = 'EBADFAMILY';
336 exports.BADRESP = 'EBADRESP';
337 exports.CONNREFUSED = 'ECONNREFUSED';
338 exports.TIMEOUT = 'ETIMEOUT';
340 exports.FILE = 'EFILE';
341 exports.NOMEM = 'ENOMEM';
342 exports.DESTRUCTION = 'EDESTRUCTION';
343 exports.BADSTR = 'EBADSTR';
344 exports.BADFLAGS = 'EBADFLAGS';
345 exports.NONAME = 'ENONAME';
346 exports.BADHINTS = 'EBADHINTS';
347 exports.NOTINITIALIZED = 'ENOTINITIALIZED';
348 exports.LOADIPHLPAPI = 'ELOADIPHLPAPI';
349 exports.ADDRGETNETWORKPARAMS = 'EADDRGETNETWORKPARAMS';
350 exports.CANCELLED = 'ECANCELLED';