You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

393 lines
11 KiB

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const util = require('util');
const cares = process.binding('cares_wrap');
const uv = process.binding('uv');
const internalNet = require('internal/net');
const { customPromisifyArgs } = require('internal/util');
const GetAddrInfoReqWrap = cares.GetAddrInfoReqWrap;
const GetNameInfoReqWrap = cares.GetNameInfoReqWrap;
const QueryReqWrap = cares.QueryReqWrap;
const isIP = cares.isIP;
const isLegalPort = internalNet.isLegalPort;
function errnoException(err, syscall, hostname) {
// FIXME(bnoordhuis) Remove this backwards compatibility nonsense and pass
// the true error to the user. ENOTFOUND is not even a proper POSIX error!
if (err === uv.UV_EAI_MEMORY ||
err === uv.UV_EAI_NODATA ||
err === uv.UV_EAI_NONAME) {
err = 'ENOTFOUND';
}
var ex = null;
if (typeof err === 'string') { // c-ares error code.
const errHost = hostname ? ' ' + hostname : '';
ex = new Error(`${syscall} ${err}${errHost}`);
ex.code = err;
ex.errno = err;
ex.syscall = syscall;
} else {
ex = util._errnoException(err, syscall);
}
if (hostname) {
ex.hostname = hostname;
}
return ex;
}
const digits = [
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0-15
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16-31
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 32-47
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48-63
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 64-79
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80-95
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 96-111
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 112-127
];
function isIPv4(str) {
if (!digits[str.charCodeAt(0)]) return false;
if (str.length === 1) return false;
if (str.charCodeAt(1) === 46/*'.'*/)
return true;
else if (!digits[str.charCodeAt(1)])
return false;
if (str.length === 2) return false;
if (str.charCodeAt(2) === 46/*'.'*/)
return true;
else if (!digits[str.charCodeAt(2)])
return false;
return (str.length > 3 && str.charCodeAt(3) === 46/*'.'*/);
}
function onlookup(err, addresses) {
if (err) {
return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
}
if (this.family) {
this.callback(null, addresses[0], this.family);
} else {
this.callback(null, addresses[0], isIPv4(addresses[0]) ? 4 : 6);
}
}
function onlookupall(err, addresses) {
if (err) {
return this.callback(errnoException(err, 'getaddrinfo', this.hostname));
}
var family = this.family;
for (var i = 0; i < addresses.length; i++) {
const addr = addresses[i];
addresses[i] = {
address: addr,
family: family || (isIPv4(addr) ? 4 : 6)
};
}
this.callback(null, addresses);
}
// Easy DNS A/AAAA look up
// lookup(hostname, [options,] callback)
function lookup(hostname, options, callback) {
var hints = 0;
var family = -1;
var all = false;
// Parse arguments
if (hostname && typeof hostname !== 'string') {
throw new TypeError('Invalid arguments: ' +
'hostname must be a string or falsey');
} else if (typeof options === 'function') {
callback = options;
family = 0;
} else if (typeof callback !== 'function') {
throw new TypeError('Invalid arguments: callback must be passed');
} else if (options !== null && typeof options === 'object') {
hints = options.hints >>> 0;
family = options.family >>> 0;
all = options.all === true;
if (hints !== 0 &&
hints !== cares.AI_ADDRCONFIG &&
hints !== cares.AI_V4MAPPED &&
hints !== (cares.AI_ADDRCONFIG | cares.AI_V4MAPPED)) {
throw new TypeError('Invalid argument: hints must use valid flags');
}
} else {
family = options >>> 0;
}
if (family !== 0 && family !== 4 && family !== 6)
throw new TypeError('Invalid argument: family must be 4 or 6');
if (!hostname) {
if (all) {
process.nextTick(callback, null, []);
} else {
process.nextTick(callback, null, null, family === 6 ? 6 : 4);
}
return {};
}
var matchedFamily = isIP(hostname);
if (matchedFamily) {
if (all) {
process.nextTick(
callback, null, [{address: hostname, family: matchedFamily}]);
} else {
process.nextTick(callback, null, hostname, matchedFamily);
}
return {};
}
var req = new GetAddrInfoReqWrap();
req.callback = callback;
req.family = family;
req.hostname = hostname;
req.oncomplete = all ? onlookupall : onlookup;
var err = cares.getaddrinfo(req, hostname, family, hints);
if (err) {
process.nextTick(callback, errnoException(err, 'getaddrinfo', hostname));
return {};
}
return req;
}
Object.defineProperty(lookup, customPromisifyArgs,
{ value: ['address', 'family'], enumerable: false });
function onlookupservice(err, host, service) {
if (err)
return this.callback(errnoException(err, 'getnameinfo', this.host));
this.callback(null, host, service);
}
// lookupService(address, port, callback)
function lookupService(host, port, callback) {
if (arguments.length !== 3)
throw new Error('Invalid arguments');
if (isIP(host) === 0)
throw new TypeError('"host" argument needs to be a valid IP address');
if (!isLegalPort(port))
throw new TypeError(`"port" should be >= 0 and < 65536, got "${port}"`);
if (typeof callback !== 'function')
throw new TypeError('"callback" argument must be a function');
port = +port;
var req = new GetNameInfoReqWrap();
req.callback = callback;
req.host = host;
req.port = port;
req.oncomplete = onlookupservice;
var err = cares.getnameinfo(req, host, port);
if (err) throw errnoException(err, 'getnameinfo', host);
return req;
}
Object.defineProperty(lookupService, customPromisifyArgs,
{ value: ['hostname', 'service'], enumerable: false });
function onresolve(err, result, ttls) {
if (ttls && this.ttl)
result = result.map((address, index) => ({ address, ttl: ttls[index] }));
if (err)
this.callback(errnoException(err, this.bindingName, this.hostname));
else
this.callback(null, result);
}
function resolver(bindingName) {
var binding = cares[bindingName];
return function query(name, /* options, */ callback) {
var options;
if (arguments.length > 2) {
options = callback;
callback = arguments[2];
}
if (typeof name !== 'string') {
throw new Error('"name" argument must be a string');
} else if (typeof callback !== 'function') {
throw new Error('"callback" argument must be a function');
}
var req = new QueryReqWrap();
req.bindingName = bindingName;
req.callback = callback;
req.hostname = name;
req.oncomplete = onresolve;
req.ttl = !!(options && options.ttl);
var err = binding(req, name);
if (err) throw errnoException(err, bindingName);
return req;
};
}
var resolveMap = Object.create(null);
resolveMap.A = resolver('queryA');
resolveMap.AAAA = resolver('queryAaaa');
resolveMap.CNAME = resolver('queryCname');
resolveMap.MX = resolver('queryMx');
resolveMap.NS = resolver('queryNs');
resolveMap.TXT = resolver('queryTxt');
resolveMap.SRV = resolver('querySrv');
resolveMap.PTR = resolver('queryPtr');
resolveMap.NAPTR = resolver('queryNaptr');
resolveMap.SOA = resolver('querySoa');
function resolve(hostname, rrtype, callback) {
var resolver;
if (typeof rrtype === 'string') {
resolver = resolveMap[rrtype];
} else if (typeof rrtype === 'function') {
resolver = resolveMap.A;
callback = rrtype;
} else {
throw new TypeError('"rrtype" argument must be a string');
}
if (typeof resolver === 'function') {
return resolver(hostname, callback);
} else {
throw new Error(`Unknown type "${rrtype}"`);
}
}
function getServers() {
return cares.getServers();
}
function setServers(servers) {
// cache the original servers because in the event of an error setting the
// servers cares won't have any servers available for resolution
const orig = cares.getServers();
const newSet = [];
servers.forEach((serv) => {
var ipVersion = isIP(serv);
if (ipVersion !== 0)
return newSet.push([ipVersion, serv]);
const match = serv.match(/\[(.*)\](?::\d+)?/);
// we have an IPv6 in brackets
if (match) {
ipVersion = isIP(match[1]);
if (ipVersion !== 0)
return newSet.push([ipVersion, match[1]]);
}
const s = serv.split(/:\d+$/)[0];
ipVersion = isIP(s);
if (ipVersion !== 0)
return newSet.push([ipVersion, s]);
throw new Error(`IP address is not properly formatted: ${serv}`);
});
const errorNumber = cares.setServers(newSet);
if (errorNumber !== 0) {
// reset the servers to the old servers, because ares probably unset them
cares.setServers(orig.join(','));
var err = cares.strerror(errorNumber);
throw new Error(`c-ares failed to set servers: "${err}" [${servers}]`);
}
}
module.exports = {
lookup,
lookupService,
getServers,
setServers,
resolve,
resolve4: resolveMap.A,
resolve6: resolveMap.AAAA,
resolveCname: resolveMap.CNAME,
resolveMx: resolveMap.MX,
resolveNs: resolveMap.NS,
resolveTxt: resolveMap.TXT,
resolveSrv: resolveMap.SRV,
resolvePtr: resolveMap.PTR,
resolveNaptr: resolveMap.NAPTR,
resolveSoa: resolveMap.SOA,
reverse: resolver('getHostByAddr'),
// uv_getaddrinfo flags
ADDRCONFIG: cares.AI_ADDRCONFIG,
V4MAPPED: cares.AI_V4MAPPED,
// ERROR CODES
NODATA: 'ENODATA',
FORMERR: 'EFORMERR',
SERVFAIL: 'ESERVFAIL',
NOTFOUND: 'ENOTFOUND',
NOTIMP: 'ENOTIMP',
REFUSED: 'EREFUSED',
BADQUERY: 'EBADQUERY',
BADNAME: 'EBADNAME',
BADFAMILY: 'EBADFAMILY',
BADRESP: 'EBADRESP',
CONNREFUSED: 'ECONNREFUSED',
TIMEOUT: 'ETIMEOUT',
EOF: 'EOF',
FILE: 'EFILE',
NOMEM: 'ENOMEM',
DESTRUCTION: 'EDESTRUCTION',
BADSTR: 'EBADSTR',
BADFLAGS: 'EBADFLAGS',
NONAME: 'ENONAME',
BADHINTS: 'EBADHINTS',
NOTINITIALIZED: 'ENOTINITIALIZED',
LOADIPHLPAPI: 'ELOADIPHLPAPI',
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
CANCELLED: 'ECANCELLED'
};