doc: improvements to console.markdown copy
[platform/upstream/nodejs.git] / lib / dns.js
1 'use strict';
2
3 const net = require('net');
4 const util = require('util');
5
6 const cares = process.binding('cares_wrap');
7 const uv = process.binding('uv');
8
9 const GetAddrInfoReqWrap = cares.GetAddrInfoReqWrap;
10 const GetNameInfoReqWrap = cares.GetNameInfoReqWrap;
11 const QueryReqWrap = cares.QueryReqWrap;
12
13 const isIp = net.isIP;
14
15
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) {
22     err = 'ENOTFOUND';
23   }
24   var ex = null;
25   if (typeof err === 'string') {  // c-ares error code.
26     ex = new Error(syscall + ' ' + err + (hostname ? ' ' + hostname : ''));
27     ex.code = err;
28     ex.errno = err;
29     ex.syscall = syscall;
30   } else {
31     ex = util._errnoException(err, syscall);
32   }
33   if (hostname) {
34     ex.hostname = hostname;
35   }
36   return ex;
37 }
38
39
40 // c-ares invokes a callback either synchronously or asynchronously,
41 // but the dns API should always invoke a callback asynchronously.
42 //
43 // This function makes sure that the callback is invoked asynchronously.
44 // It returns a function that invokes the callback within nextTick().
45 //
46 // To avoid invoking unnecessary nextTick(), `immediately` property of
47 // returned function should be set to true after c-ares returned.
48 //
49 // Usage:
50 //
51 // function someAPI(callback) {
52 //   callback = makeAsync(callback);
53 //   channel.someAPI(..., callback);
54 //   callback.immediately = true;
55 // }
56 function makeAsync(callback) {
57   if (typeof callback !== 'function') {
58     return callback;
59   }
60   return function asyncCallback() {
61     if (asyncCallback.immediately) {
62       // The API already returned, we can invoke the callback immediately.
63       callback.apply(null, arguments);
64     } else {
65       var args = new Array(arguments.length + 1);
66       args[0] = callback;
67       for (var i = 1, a = 0; a < arguments.length; ++i, ++a)
68         args[i] = arguments[a];
69       process.nextTick.apply(null, args);
70     }
71   };
72 }
73
74
75 function onlookup(err, addresses) {
76   if (err) {
77     return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
78   }
79   if (this.family) {
80     this.callback(null, addresses[0], this.family);
81   } else {
82     this.callback(null, addresses[0], addresses[0].indexOf(':') >= 0 ? 6 : 4);
83   }
84 }
85
86
87 function onlookupall(err, addresses) {
88   var results = [];
89   if (err) {
90     return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
91   }
92
93   for (var i = 0; i < addresses.length; i++) {
94     results.push({
95       address: addresses[i],
96       family: this.family || (addresses[i].indexOf(':') >= 0 ? 6 : 4)
97     });
98   }
99
100   this.callback(null, results);
101 }
102
103
104 // Easy DNS A/AAAA look up
105 // lookup(hostname, [options,] callback)
106 exports.lookup = function lookup(hostname, options, callback) {
107   var hints = 0;
108   var family = -1;
109   var all = false;
110
111   // Parse arguments
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') {
116     callback = options;
117     family = 0;
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;
124
125     if (hints !== 0 &&
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');
130     }
131   } else {
132     family = options >>> 0;
133   }
134
135   if (family !== 0 && family !== 4 && family !== 6)
136     throw new TypeError('invalid argument: family must be 4 or 6');
137
138   callback = makeAsync(callback);
139
140   if (!hostname) {
141     if (all) {
142       callback(null, []);
143     } else {
144       callback(null, null, family === 6 ? 6 : 4);
145     }
146     return {};
147   }
148
149   var matchedFamily = net.isIP(hostname);
150   if (matchedFamily) {
151     if (all) {
152       callback(null, [{address: hostname, family: matchedFamily}]);
153     } else {
154       callback(null, hostname, matchedFamily);
155     }
156     return {};
157   }
158
159   var req = new GetAddrInfoReqWrap();
160   req.callback = callback;
161   req.family = family;
162   req.hostname = hostname;
163   req.oncomplete = all ? onlookupall : onlookup;
164
165   var err = cares.getaddrinfo(req, hostname, family, hints);
166   if (err) {
167     callback(errnoException(err, 'getaddrinfo', hostname));
168     return {};
169   }
170
171   callback.immediately = true;
172   return req;
173 };
174
175
176 function onlookupservice(err, host, service) {
177   if (err)
178     return this.callback(errnoException(err, 'getnameinfo', this.host));
179
180   this.callback(null, host, service);
181 }
182
183
184 // lookupService(address, port, callback)
185 exports.lookupService = function(host, port, callback) {
186   if (arguments.length !== 3)
187     throw new Error('invalid arguments');
188
189   if (cares.isIP(host) === 0)
190     throw new TypeError('host needs to be a valid IP address');
191
192   callback = makeAsync(callback);
193
194   var req = new GetNameInfoReqWrap();
195   req.callback = callback;
196   req.host = host;
197   req.port = port;
198   req.oncomplete = onlookupservice;
199
200   var err = cares.getnameinfo(req, host, port);
201   if (err) throw errnoException(err, 'getnameinfo', host);
202
203   callback.immediately = true;
204   return req;
205 };
206
207
208 function onresolve(err, result) {
209   if (err)
210     this.callback(errnoException(err, this.bindingName, this.hostname));
211   else
212     this.callback(null, result);
213 }
214
215
216 function resolver(bindingName) {
217   var binding = cares[bindingName];
218
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');
224     }
225
226     callback = makeAsync(callback);
227     var req = new QueryReqWrap();
228     req.bindingName = bindingName;
229     req.callback = callback;
230     req.hostname = name;
231     req.oncomplete = onresolve;
232     var err = binding(req, name);
233     if (err) throw errnoException(err, bindingName);
234     callback.immediately = true;
235     return req;
236   };
237 }
238
239
240 var resolveMap = {};
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');
251
252
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;
260     callback = type_;
261   } else {
262     throw new Error('Type must be a string');
263   }
264
265   if (typeof resolver === 'function') {
266     return resolver(hostname, callback);
267   } else {
268     throw new Error('Unknown type "' + type_ + '"');
269   }
270 };
271
272
273 exports.getServers = function() {
274   return cares.getServers();
275 };
276
277
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();
282
283   var newSet = [];
284
285   servers.forEach(function(serv) {
286     var ver = isIp(serv);
287
288     if (ver)
289       return newSet.push([ver, serv]);
290
291     var match = serv.match(/\[(.*)\](:\d+)?/);
292
293     // we have an IPv6 in brackets
294     if (match) {
295       ver = isIp(match[1]);
296       if (ver)
297         return newSet.push([ver, match[1]]);
298     }
299
300     var s = serv.split(/:\d+$/)[0];
301     ver = isIp(s);
302
303     if (ver)
304       return newSet.push([ver, s]);
305
306     throw new Error('IP address is not properly formatted: ' + serv);
307   });
308
309   var r = cares.setServers(newSet);
310
311   if (r) {
312     // reset the servers to the old servers, because ares probably unset them
313     cares.setServers(orig.join(','));
314
315     var err = cares.strerror(r);
316     throw new Error('c-ares failed to set servers: "' + err +
317                     '" [' + servers + ']');
318   }
319 };
320
321 // uv_getaddrinfo flags
322 exports.ADDRCONFIG = cares.AI_ADDRCONFIG;
323 exports.V4MAPPED = cares.AI_V4MAPPED;
324
325 // ERROR CODES
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';
339 exports.EOF = 'EOF';
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';