From 893d0fcc89bb0f2749ecbc416b2cfebb1571c18c Mon Sep 17 00:00:00 2001 From: Brian White Date: Sat, 30 May 2015 01:08:06 -0400 Subject: [PATCH 01/39] dns: add new DNS resolver and set it as default This adds a new pure JavaScript resolver as the default DNS resolver and makes the old (c-ares) resolver behind a --use-old-dns flag --- lib/dns.js | 5 + lib/internal/dns.js | 2624 +++++++++++++++++++++ node.gyp | 1 + src/node.cc | 13 + test/common.js | 36 + test/internet/test-dns-domain-search.js | 191 ++ test/internet/test-dns-durability.js | 1443 +++++++++++ test/internet/test-dns-truncated.js | 88 + test/internet/test-dns.js | 42 +- test/parallel/test-dns-lookup-cb-error.js | 4 + 10 files changed, 4446 insertions(+), 1 deletion(-) create mode 100644 lib/internal/dns.js create mode 100644 test/internet/test-dns-domain-search.js create mode 100644 test/internet/test-dns-durability.js create mode 100644 test/internet/test-dns-truncated.js diff --git a/lib/dns.js b/lib/dns.js index be735f2cfeac24..2b8226e78c9bd4 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -1,5 +1,10 @@ 'use strict'; +if (!process.oldDNS) { + module.exports = require('internal/dns'); + return; +} + const net = require('net'); const util = require('util'); diff --git a/lib/internal/dns.js b/lib/internal/dns.js new file mode 100644 index 00000000000000..d19941495c38c0 --- /dev/null +++ b/lib/internal/dns.js @@ -0,0 +1,2624 @@ +/* + TODO: + - Reuse sockets (this means maintaining a global state object)? This could + especially help with minimizing the overhead of queries over TCP + connections + - This could include allowing multiple questions per query, although that + kind of reuse would only happen if we had a user-facing API to allow + such kind of queries + - This could also be realized by using some kind of queueing mechanism to + avoid creating too many sockets + - Global query Buffer cache? This is already done locally inside the + main resolver to avoid creating a new Buffer every time we need to retry + the same query on a different protocol or nameserver + - Add file (and registry, on Windows) watchers for updates to system DNS + settings + - If this is implemented, how should this interact with settings + overridden by users (e.g. via setServers())? + - Support EDNS, DNSSEC + - Allow complete override of system-level DNS options in resolve() instead + (or in addition to?) being able to append to existing options + (e.g. system has `use_vc` set, but user wants to un-set that for a + particular resolve()) + + NOTES: + - PR #744 mentioned c-ares could return *mixed* IPv4 and IPv6 when + `family === 0`, but this is not true (it's even stated in c-ares' TODO), + so this implementation does not return mixed results either +*/ +'use strict'; + +const TCPSocket = require('net').Socket; +const UDPSocket = require('dgram').Socket; +const isIP = require('net').isIP; // TODO: move implementation from cares_wrap +const cp = require('child_process'); +const os = require('os'); +const fs = require('fs'); +const pathJoin = require('path').join; + +const HOSTNAME = os.hostname(); +const REGEX_ARRAY = /^\{?(.*)\}?$/; +const REGEX_OPTS = + /^(ndots|timeout|attempts|rotate|inet6|use-vc)(?::(\d{1,2}))?$/; +const REGEX_TYPES = /^(nameserver|domain|search|sortlist|options|lookup) /; +const REGEX_WHITESPACE = /[ \f\t\v]+/g; +const REGEX_COMMENT = /[#;].*$/; +const LOOKUPNS = ['dns']; +const QTYPE_A = 1; +const QTYPE_PTR = 12; +const QTYPE_AAAA = 28; +const FLAG_HAS_IPv4 = 0x1; +const FLAG_HAS_IPv6 = 0x2; +const FLAG_HAS_IPv4_IPv6 = (FLAG_HAS_IPv4 | FLAG_HAS_IPv6); +const V4MAPPED = 0x08; +const ADDRCONFIG = 0x20; +const FLAG_RESOLVE_NSONLY = 0x01; // No local hosts file lookup +const FLAG_RESOLVE_NOSEARCH = 0x02; // No domain searching +const FLAG_RESOLVE_NOALIASES = 0x04; // No HOSTALIASES checking +const FLAG_RESOLVE_IGNTC = 0x08; // Ignore truncation errors (UDP) +const FLAG_RESOLVE_USEVC = 0x10; // Always use TCP a.k.a "Virtual Circuit" +const DNS_DEFAULTS = { + // TODO: improve default nameserver for IPv6-only systems + nameserver: [ '127.0.0.1' ], + search: (~HOSTNAME.indexOf('.') + ? [ HOSTNAME.slice(HOSTNAME.indexOf('.') + 1) ] : null), + sortlist: null, + options: { + ndots: 1, + timeout: 5000, // ms + attempts: 4, // how many tries before giving up + rotate: false, // round-robin nameservers instead of always trying first one + inet6: false, // query AAAA before A + use_vc: false // force TCP for all DNS queries + }, + lookup: [ 'files', 'dns' ] +}; + +// Map DNS RCODE values to sensible string values +const RCODE_V2S = { + // RFC 1035 + 0: 'NOERROR', // No error + 1: 'FORMERR', // Format error + 2: 'SERVFAIL', // Server failure + 3: 'NXDOMAIN', // Non-existent domain + 4: 'NOTIMP', // Not implemented + 5: 'REFUSED', // Query refused +}; +// Map DNS RCODE strings to c-ares +const RCODE_S2CARES = { + 'FORMERR': 'EFORMERR', + 'SERVFAIL': 'ESERVFAIL', + 'NXDOMAIN': 'ENOTFOUND', + 'NOTIMP': 'ENOTIMP', + 'REFUSED': 'EREFUSED' +}; +// Map question/query type numbers to question-specific data and methods +const QTYPE_V2O = { + 1: { + // RFC 1035 - IPv4 host address + name: 'A', + parse: function(rr, buf, p, end) { + if (p + 3 >= end) + return false; + + var addr = buf[p++] + '.' + buf[p++] + '.' + buf[p++] + '.' + buf[p++]; + + rr.data = addr; + + return p; + } + }, + 2: { + // RFC 1035 - An authoritative name server + name: 'NS', + parse: nameRDataParser + }, + 5: { + // RFC 1035 - The canonical name for an alias + name: 'CNAME', + parse: nameRDataParser + }, + 6: { + // RFC 1035 - Marks the start of a zone of authority + name: 'SOA', + parse: function(rr, buf, p, end) { + var mname; + var rname; + var serial; + var refresh; + var retry; + var expire; + var minimum; + + // MNAME + var ret = parseName(buf, p, end); + if (!Array.isArray(ret)) + return false; + p = ret[0]; + mname = ret[1]; + + // RNAME + ret = parseName(buf, p, end); + if (!Array.isArray(ret)) + return false; + p = ret[0]; + rname = ret[1]; + + // SERIAL + if (p + 3 >= end) + return false; + serial = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + // REFRESH + if (p + 3 >= end) + return false; + refresh = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + // RETRY + if (p + 3 >= end) + return false; + retry = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + // EXPIRE + if (p + 3 >= end) + return false; + expire = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + // MINIMUM + if (p + 3 >= end) + return false; + minimum = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + rr.data = { + nsname: mname, + hostmaster: rname, + serial: serial, + refresh: refresh, + retry: retry, + expire: expire, + minttl: minimum + }; + + return p; + } + }, + 12: { + // RFC 1035 - A domain name pointer, used for reverse lookups + name: 'PTR', + parse: nameRDataParser + }, + 15: { + // RFC 1035 - Mail exchange + name: 'MX', + parse: function(rr, buf, p, end) { + if (p + 1 >= end) + return false; + + // PREFERENCE + var pref = (buf[p++] << 8) + buf[p++]; + + // EXCHANGE + var ret = parseName(buf, p, end); + if (!Array.isArray(ret)) + return false; + + rr.data = { priority: pref, exchange: ret[1] }; + + return ret[0]; + } + }, + 16: { + // RFC 1035 - Text strings + name: 'TXT', + parse: function(rr, buf, p, end) { + var texts = []; + var len; + + while (p < end) { + len = buf[p++]; + if (p + (len - 1) >= end) + return false; + + texts.push(buf.toString('binary', p, p += len)); + } + + rr.data = texts; + + return end; + } + }, + 28: { + // RFC 3596 - IPv6 host address + name: 'AAAA', + parse: function(rr, buf, p, end) { + // 128-bit value for IPv6 address + if (p + 15 >= end) + return false; + + // This implementation is more or less based on the inet_ntop from glibc + + var words = new Array(16); + var str = ''; + var zbase = -1; + var zlen = 0; + var zbestbase = -1; + var zbestlen = 0; + + for (var i = 0; p < end; ++i) + words[i] = (buf[p++] << 8) + buf[p++]; + + for (var i = 0; i < 8; ++i) { + if (words[i] === 0) { + if (zbase === -1) { + zbase = i; + zlen = 1; + } else { + ++zlen; + } + } else if (zbase !== -1) { + if (zbestbase === -1 || zlen > zbestlen) { + zbestbase = zbase; + zbestlen = zlen; + } + zbase = -1; + } + } + if (zbase !== -1) { + if (zbestbase === -1 || zlen > zbestlen) { + zbestbase = zbase; + zbestlen = zlen; + } + } + if (zbestbase !== -1 && zbestlen < 2) + zbestbase = -1; + + for (var i = 0; i < 8; ++i) { + // Are we inside the best run of 0x00's? + if (zbestbase !== -1 && i >= zbestbase && i < (zbestbase + zbestlen)) { + if (i === zbestbase) + str += ':'; + continue; + } + // Are we following an initial run of 0x00s or any real hex? + if (i !== 0) + str += ':'; + // Is this address an encapsulated IPv4? + if (i === 6 && zbestbase === 0 && + (zbestlen === 6 || (zbestlen === 5 && words[5] === 0xffff))) { + str += (words[6] >>> 8) + '.' + (words[6] & 0xFF) + '.' + + (words[7] >>> 8) + '.' + (words[7] & 0xFF); + break; + } + str += words[i].toString(16); + } + // Was it a trailing run of 0x00's? + if (zbestbase !== -1 && (zbestbase + zbestlen) === 8) + str += ':'; + + rr.data = str; + + return end; + } + }, + 29: { + // RFC 1876 - Geographical location + name: 'LOC', + parse: function(rr, buf, p, end) { + if (p + 15 >= end || buf[p++] !== 0) + return false; + + var size = buf[p++]; + var horizPrec = buf[p++]; + var vertPrec = buf[p++]; + var latitude = (buf[p++] * 16777216) + (buf[p++] * 65536) + + (buf[p++] * 256) + buf[p++]; + var longitude = (buf[p++] * 16777216) + (buf[p++] * 65536) + + (buf[p++] * 256) + buf[p++]; + var altitude = (buf[p++] * 16777216) + (buf[p++] * 65536) + + (buf[p++] * 256) + buf[p++]; + + var sizeBase = ((size & 0xF0) >> 4); + var sizePower = (size & 0xF); + if (sizeBase > 9 || sizePower > 9) // RFC says these values are undefined + size = NaN; + else + size = sizeBase * Math.pow(10, sizePower); + + rr.data = { + size: size, + horizPrec: horizPrec, + vertPrec: vertPrec, + latitude: latitude, + longitude: longitude, + altitude: altitude + }; + + return end; + } + }, + 33: { + // RFC 2782 - Service locator + name: 'SRV', + parse: function(rr, buf, p, end) { + if (p + 5 >= end) + return false; + + var priority = (buf[p++] << 8) + buf[p++]; + + var weight = (buf[p++] << 8) + buf[p++]; + + var port = (buf[p++] << 8) + buf[p++]; + + var ret = parseName(buf, p, end); + if (!Array.isArray(ret)) // Error + return false; + + rr.data = { + priority: priority, + weight: weight, + port: port, + name: ret[1] + }; + + return ret[0]; + } + }, + 35: { + // RFC 3403 - Naming Authority Pointer + name: 'NAPTR', + parse: function(rr, buf, p, end) { + if (p + 7 >= end) + return false; + + var order = (buf[p++] << 8) + buf[p++]; + var preference = (buf[p++] << 8) + buf[p++]; + var len = buf[p++]; + // c-ares (at least) does not seem to interpret the character string + // fields as UTF-8 as described in RFC 3403, so we emulate c-ares here ... + var flags = ''; + var service = ''; + var regexp = ''; + var replacement; + var ret; + + if (len) { + if (p + (len - 1) >= end) + return false; + flags = buf.toString('binary', p, p += len); + } + + if (p >= end) + return false; + len = buf[p++]; + + if (len) { + if (p + (len - 1) >= end) + return false; + service = buf.toString('binary', p, p += len); + } + + if (p >= end) + return false; + len = buf[p++]; + + if (len) { + if (p + (len - 1) >= end) + return false; + regexp = buf.toString('binary', p, p += len); + } + + if (p >= end) + return false; + ret = parseName(buf, p, end); + if (!Array.isArray(ret)) // Error + return false; + replacement = ret[1]; + + rr.data = { + flags: flags, + service: service, + regexp: regexp, + replacement: replacement, + order: order, + preference: preference + }; + + return ret[0]; + } + }, + 44: { + // RFC 4255 / 6594 / 7479 - Secure shell fingerprint + name: 'SSHFP', + parse: function(rr, buf, p, end) { + if (p + 2 >= end) + return false; + + var algorithm = buf[p++]; + var fpType = buf[p++]; + + switch (algorithm) { + case 0: // RFC 4255 - Reserved + return false; + case 1: // RFC 4255 + algorithm = 'RSA'; + break; + case 2: // RFC 4255 + algorithm = 'DSA'; + break; + case 3: // RFC 6594 + algorithm = 'ECDSA'; + break; + case 4: // RFC 7479 + algorithm = 'Ed25519'; + break; + } + + switch (fpType) { + case 0: // RFC 4255 - Reserved + return false; + case 1: // RFC 4255 + if ((end - p) < 40) + return false; + fpType = 'SHA1'; + break; + case 2: // RFC 6594 + if ((end - p) < 64) + return false; + fpType = 'SHA256'; + break; + } + + rr.data = { + algorithm: algorithm, + fpType: fpType, + fp: buf.toString('hex', p, end) + }; + + return end; + } + } +}; +// Several RDATA layouts all consist merely of a domain name, so we extract that +// functionality for better reuse +function nameRDataParser(rr, buf, p, end) { + var ret = parseName(buf, p, end); + if (!Array.isArray(ret)) + return false; + rr.data = ret[1]; + return ret[0]; +} + +// Map question/query type strings to their numeric DNS protocol values +// This is used to validate user input in the actual resolver +var QTYPE_S2V = { + '*': 255 // All cached records +}; +(function() { + var keys = Object.keys(QTYPE_V2O); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + QTYPE_S2V[QTYPE_V2O[key].name] = parseInt(key, 10); + } +})(); + +// This stores the path to the databases directory on Windows that is evaluated +// only once at startup. Typically this is "%SystemRoot%\system32\drivers\etc" +var win32_dbPath; + +// The current DNS configuration. This is populated at startup but can be +// modified by user-facing functions during runtime (e.g. setServers()) +var dnsConfig = { + nameserver: null, + search: null, + sortlist: null, + options: { + ndots: null, + timeout: null, + attempts: null, + rotate: null, + inet6: null, + use_vc: null + }, + lookup: null +}; + +exports.setServers = function(servers) { + if (!Array.isArray(servers)) + throw new Error('servers argument must be an array of addresses'); + + var newSet = []; + + for (var i = 0, serv; i < servers.length; ++i) { + serv = servers[i]; + var ver = isIP(serv); + + if (ver) { + newSet.push([ver, serv]); + continue; + } + + var match = serv.match(/\[(.*)\](:\d+)?/); + + // we have an IPv6 in brackets + if (match) { + ver = isIP(match[1]); + if (ver) { + newSet.push([ver, match[1]]); + continue; + } + } + + var s = serv.split(/:\d+$/)[0]; + ver = isIP(s); + + if (ver) { + newSet.push([ver, s]); + continue; + } + + throw new Error('IP address is not properly formatted: ' + serv); + } + + dnsConfig.nameserver = newSet; +}; + +exports.getServers = function() { + var nameservers = dnsConfig.nameserver; + var ret = new Array(nameservers.length); + for (var i = 0; i < nameservers.length; ++i) + ret[i] = nameservers[i][1]; + return ret; +}; + +// lookup(hostname, [options,] callback) +exports.lookup = function lookup(hostname, options, callback) { + var triedIPv4 = false; // State for `family === 0` case + var isCustomType = false; + var hints = 0; + var family = -1; + var all = false; + var flags = 0; + var type; + + // 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; + // This should match glibc behavior + hints = (ADDRCONFIG | V4MAPPED); + } else if (typeof callback !== 'function') { + throw new TypeError('invalid arguments: callback must be passed'); + } else if (options !== null && typeof options === 'object') { + if (typeof options.hints === 'number') + hints = (options.hints >>> 0); + else if (options.family === undefined) // This should match glibc behavior + hints = (ADDRCONFIG | V4MAPPED); + family = (options.family >>> 0); + all = (options.all === true); + if (typeof options.flags === 'number') + flags = (options.flags >>> 0); + if (typeof options.type === 'string') { + if (QTYPE_S2V[options.type] === undefined) { + throw new Error( + 'invalid argument: type must be a supported record type' + ); + } + if (options.type !== 'A' && options.type !== 'AAAA') + isCustomType = true; + type = options.type; + } + + if (hints !== 0 && + hints !== ADDRCONFIG && + hints !== V4MAPPED && + hints !== (ADDRCONFIG | V4MAPPED)) { + throw new TypeError('invalid argument: hints must use valid flags'); + } + } else { + family = options >>> 0; + } + + var v4mapped = (hints & V4MAPPED) > 0; + var addrconfig = (hints & ADDRCONFIG) > 0; + var addrtypes = FLAG_HAS_IPv4_IPv6; + + if (!isCustomType) { + if (family !== 0 && family !== 4 && family !== 6) + throw new TypeError('invalid argument: family must be 4 or 6'); + + if (hostname) { + var matchedFamily = isIP(hostname); + if (matchedFamily) { + // TODO: Return IPv4-mapped IPv6 address if `v4mapped === true` and + // `matchedFamily === 4` ? + if (all) { + process.nextTick(callback, + null, + [{address: hostname, family: matchedFamily}]); + } else { + process.nextTick(callback, null, hostname, matchedFamily); + } + return; + } + } + + // If we're limiting the type of query to what network interface address + // types are available, we need to get those address types from the OS now + if (addrconfig) { + addrtypes = getIfaceAddrTypes(); + if (family === 0) { + if (addrtypes & FLAG_HAS_IPv4) + family = 4; + else + family = 6; + } + } + + // Check that we have a hostname AND that we have a network interface + // address type that matches the type of IP address we're looking up (if + // applicable) + if (!hostname || + (addrconfig && family === 4 && !(addrtypes & FLAG_HAS_IPv4)) || + (addrconfig && family !== 4 && !(addrtypes & FLAG_HAS_IPv6))) { + if (all) + process.nextTick(callback, null, []); + else + process.nextTick(callback, null, null, family === 6 ? 6 : 4); + return; + } + } + + if (!type) + type = (family === 4 ? 'A' : 'AAAA'); + + resolve(hostname, type, flags, function resolvecb(err, rep) { + if (err) { + // Try A records for `hostname` only if all of the following are true: + // - User did not request an explicit record type (A or AAAA) OR user + // explicitly requested AAAA and will accept IPv4-mapped IPv6 addresses + // - We haven't already tried for A records + // - We either aren't restricting record types to the types of network + // interface addresses currently enabled on the system OR we have at + // least one IPv4 (non-loopback and non-link-local) address assigned to + // an enabled network interface + // - User did not explicitly request a non-A/AAAA record type + if ((family === 0 || (family === 6 && v4mapped)) && !triedIPv4 && + (!addrconfig || (addrtypes & FLAG_HAS_IPv4)) && !isCustomType) { + triedIPv4 = true; + return resolve(hostname, 'A', 0, resolvecb); + } + return callback(err); + } + + var answers = rep.answers; + if (answers.length === 0) { + // Emulate old DNS behavior of sending an Error on "empty" responses + // instead of sending an empty array like with dns.resolve*() + return resolvecb(errnoException('ENOTFOUND', hostname)); + } + + if (!isCustomType) { + var needv4Mapped = (v4mapped && family === 6 && triedIPv4); + var addr; + if (family === 0) + family = (triedIPv4 ? 4 : 6); + if (all) { + var addrs = new Array(answers.length); + for (var i = 0; i < answers.length; ++i) { + addr = answers[i].data; + if (needv4Mapped) + addr = '::ffff:' + addr; + addrs[i] = { + address: addr, + family: family + }; + } + callback(null, addrs); + } else { + addr = answers[0].data; + if (needv4Mapped) + addr = '::ffff:' + addr; + callback(null, addr, family); + } + } else { + if (all) { + var vals = new Array(answers.length); + for (var i = 0; i < answers.length; ++i) + vals[i] = answers[i].data; + callback(null, vals); + } else { + callback(null, answers[0].data); + } + } + }); +}; + +function resolver(type) { + var returnFirst = (type === 'SOA'); + return function query(name, callback) { + if (typeof name !== 'string') + throw new Error('Name must be a string'); + else if (typeof callback !== 'function') + throw new Error('Callback must be a function'); + + resolve(name, type, FLAG_RESOLVE_NSONLY, function resolvecb(err, rep) { + if (err) + return callback(err); + + var answers = rep.answers; + var addrs; + + if (returnFirst) + addrs = (answers.length ? answers[0].data : null); + else { + addrs = new Array(answers.length); + for (var i = 0; i < answers.length; ++i) + addrs[i] = answers[i].data; + } + + callback(null, addrs); + }); + }; +} + +var resolveMap = {}; +(function() { + var keys = Object.keys(QTYPE_V2O); + for (var i = 0; i < keys.length; ++i) { + var key = keys[i]; + var name = QTYPE_V2O[key].name; + var exportName = 'resolve' + name[0] + name.slice(1).toLowerCase(); + if (name === 'A') + exportName = 'resolve4'; + else if (name === 'AAAA') + exportName = 'resolve6'; + else if (name === 'PTR') + exportName = 'reverse'; + exports[exportName] = resolveMap[name] = resolver(name); + } + exports.resolveAll = resolveMap['*'] = resolver('*'); +})(); + +exports.resolve = function(hostname, type, callback_) { + var resolver; + var callback; + if (typeof type === 'string') { + resolver = resolveMap[type]; + callback = callback_; + } else if (typeof type === 'function') { + resolver = exports.resolve4; + callback = type; + } else { + throw new Error('Type must be a string'); + } + + if (typeof resolver === 'function') + resolver(hostname, callback); + else + throw new Error('Unknown type "' + type + '"'); +}; + +exports.lookupService = function(addr, port, callback) { + if (arguments.length < 3) + throw new Error('missing arguments'); + + if (isIP(addr) === 0) + throw new TypeError('addr needs to be a valid IP address'); + if (typeof port !== 'number') { + port = parseInt(port, 10); + if (port !== port) + throw new TypeError('invalid port'); + } + if (!isFinite(port)) + throw new TypeError('invalid port'); + + exports.lookup(addr, { type: 'PTR' }, function(err, hostname) { + var host = (err || !hostname ? addr : hostname); + var svcPath; + if (process.platform === 'win32') { + if (!win32_dbPath) + return callback(err, host, port); + svcPath = pathJoin(win32_dbPath, 'services'); + } else { + // TODO: check /etc/nsswitch.conf for additional/alternate services file + // path + svcPath = '/etc/services'; + } + searchServicesFile(svcPath, port, function(result) { + callback(err, host, result || port); + }); + }); +}; + +function errnoException(err, hostname) { + var ex; + if (typeof err === 'string') { + ex = new Error(err + (hostname ? ' ' + hostname : '')); + ex.code = err; + ex.errno = err; + } else { + ex = err; + } + if (hostname) + ex.hostname = hostname; + return ex; +} + +// Error codes +exports.NODATA = 'ENODATA'; +exports.FORMERR = 'EFORMERR'; +exports.SERVFAIL = 'ESERVFAIL'; +exports.NOTFOUND = 'ENOTFOUND'; +exports.NOTIMP = 'ENOTIMP'; +exports.REFUSED = 'EREFUSED'; +exports.BADQUERY = 'EBADQUERY'; +exports.BADNAME = 'EBADNAME'; +exports.BADFAMILY = 'EBADFAMILY'; +exports.BADRESP = 'EBADRESP'; +exports.CONNREFUSED = 'ECONNREFUSED'; +exports.TIMEOUT = 'ETIMEOUT'; +exports.EOF = 'EOF'; +exports.FILE = 'EFILE'; +exports.NOMEM = 'ENOMEM'; +exports.DESTRUCTION = 'EDESTRUCTION'; +exports.BADSTR = 'EBADSTR'; +exports.BADFLAGS = 'EBADFLAGS'; +exports.NONAME = 'ENONAME'; +exports.BADHINTS = 'EBADHINTS'; +exports.NOTINITIALIZED = 'ENOTINITIALIZED'; +exports.LOADIPHLPAPI = 'ELOADIPHLPAPI'; +exports.ADDRGETNETWORKPARAMS = 'EADDRGETNETWORKPARAMS'; +exports.CANCELLED = 'ECANCELLED'; + +// Hints for dns.lookup() +exports.V4MAPPED = V4MAPPED; +exports.ADDRCONFIG = ADDRCONFIG; + +// Flags for dns.lookup() that are passed directly to resolve() +exports.FLAG_RESOLVE_NSONLY = FLAG_RESOLVE_NSONLY; +exports.FLAG_RESOLVE_NOSEARCH = FLAG_RESOLVE_NOSEARCH; +exports.FLAG_RESOLVE_NOALIASES = FLAG_RESOLVE_NOALIASES; +exports.FLAG_RESOLVE_IGNTC = FLAG_RESOLVE_IGNTC; +exports.FLAG_RESOLVE_USEVC = FLAG_RESOLVE_USEVC; + + + +// The main resolver implementation +function resolve(name, type, flags, cb) { + var nameservers = dnsConfig.nameserver; + var nsIdx = -1; + var TCPOnly = (dnsConfig.options.use_vc || (flags & FLAG_RESOLVE_USEVC) > 0); + var attempts = dnsConfig.options.attempts; + var lookups = (flags & FLAG_RESOLVE_NSONLY ? LOOKUPNS : dnsConfig.lookup); + var lookupIdx = 0; + var timeout = dnsConfig.options.timeout; + var search = dnsConfig.search; + var optNDots = dnsConfig.options.ndots; + var searchIdx = 0; + var cbCalled = false; + var triedAsIs = false; + var retryingOnTCP = false; + var triedTCP = false; + var reqIsPtr = false; + var sawEmptyAnswers = false; + var reqBufCache = {}; + var tcpBuf = null; + var tcpBufLen = 0; + var tcpLen = -0; + var tcpSendLenBuf = null; + var ndots; + var qtype; + var qtypeName; + var curName; + var socket; + var reqBuf; + var timer; + + // For DNS nameserver requests + qtype = QTYPE_S2V[type]; + if (!qtype) + return process.nextTick(cb, errnoException(exports.BADQUERY, name)); + reqIsPtr = (qtype === QTYPE_PTR); + qtypeName = type; + if (typeof name !== 'string') + return process.nextTick(cb, errnoException(exports.BADQUERY, name)); + if (name.charCodeAt(name.length - 1) === 46) { // '.' + name = name.slice(0, -1); + flags |= FLAG_RESOLVE_NOSEARCH; + } + + ndots = -1; + if (!reqIsPtr) { + // Count dots in domain name + var i = 0; + ndots = 0; + while ((i = name.indexOf('.', i)) > -1) { + ++ndots; + ++i; + } + + if (ndots === 0 && process.env.HOSTALIASES && + !(flags & FLAG_RESOLVE_NOALIASES)) { + // Domain name could be an alias, so check first before trying to resolve + return searchHostAliases(name, function(newName) { + resolve(newName, type, flags | FLAG_RESOLVE_NOALIASES, cb); + }); + } + } + + nextName(); + + // Check that everything is OK with our initial request before trying anything + if (cbCalled) + return; + + nextLookup(false); + + + + function doCb(err, ret, async) { + if (cbCalled) + return; + cbCalled = true; + + // Convert actual DNS error names to c-ares-compatible error names. The + // error name conversion is done here at the very end to make it much easier + // to switch to passing actual DNS error names to the user in the future. + if (!err && ret && ret.rcode !== 'NOERROR') { + ret.rcode = RCODE_S2CARES[ret.rcode] || ret.rcode; + err = errnoException(ret.rcode, name); + ret = undefined; + } + + if (async) + process.nextTick(cb, err, ret); + else + cb(err, ret); + } + + // Cleanup on socket errors or query timeouts + function cleanup(err) { + if (socket instanceof UDPSocket) + socket.close(); + else { + tcpBuf = null; + tcpBufLen = 0; + tcpLen = -0; + socket.removeListener('data', tcpOnData); + socket.destroy(); + } + } + + function udpMsgHandler(msg, rinfo) { + if (rinfo.address !== nameservers[nsIdx][1] || rinfo.port !== 53) + return; + clearTimeout(timer); + socket.removeListener('message', udpMsgHandler); + socket.removeListener('error', cleanup); + socket.removeListener('close', nextLookup); + socket.close(); + + onReply(parseReply(qtypeName, msg)); + } + + function udpListening() { + var ns = nameservers[nsIdx]; + timer = setTimeout(cleanup, timeout); + socket.send(reqBuf, 0, reqBuf.length, 53, ns[1]); + } + + function tcpOnData(data) { + if (tcpLen === 0 && (1 / tcpLen) < 0) { + // We have no length bytes yet + if (data.length === 1) { + tcpLen = -data[0]; + return; + } else if (data.length === 2) { + tcpLen = (data[0] << 8) + data[1]; + return; + } + tcpLen = (data[0] << 8) + data[1]; + data = data.slice(2); + } else if (tcpLen < 0) { + // We have one length byte + tcpLen = ((tcpLen * -1) << 8) + data[0]; + if (data.length === 1) + return; + else + data = data.slice(1); + } + + tcpBufLen += data.length; + if (tcpBufLen > tcpLen) { + // We received too much from the server + return cleanup(); + } + if (!tcpBuf) + tcpBuf = [ data ]; + else + tcpBuf.push(data); + + if (tcpBufLen === tcpLen) { + // Complete packet + + clearTimeout(timer); + socket.removeListener('data', tcpOnData); + socket.removeListener('error', cleanup); + socket.removeListener('close', tcpOnClose); + socket.end(); + + var msg = Buffer.concat(tcpBuf, tcpBufLen); + tcpBuf = null; + tcpBufLen = 0; + tcpLen = -0; + + onReply(parseReply(qtypeName, msg)); + } + } + + function tcpOnConnect() { + // Lazy create 2-byte `reqBuf` length Buffer + if (!tcpSendLenBuf) + tcpSendLenBuf = new Buffer(2); + + tcpSendLenBuf[0] = ((reqBuf.length >>> 8) & 0xFF); + tcpSendLenBuf[1] = (reqBuf.length & 0xFF); + + socket.cork(); + socket.write(tcpSendLenBuf); + socket.write(reqBuf); + socket.uncork(); + } + + function tcpOnClose() { + retryingOnTCP = false; + nextLookup(false); + } + + // Receives and interprets the result from parseReply() + function onReply(rep) { + if (rep === false) { + // Parse or similar critical error + + // Reset some state + searchIdx = 0; + triedAsIs = false; + } else { + // We got an actual reply + + if (rep.rcode === 'NOTIMP' || rep.rcode === 'SERVFAIL' || + rep.rcode === 'REFUSED' || (!rep.answers.length && !rep.truncated)) { + if (!sawEmptyAnswers && rep.rcode !== 'NOTIMP' && + rep.rcode !== 'SERVFAIL' && rep.rcode !== 'REFUSED') + sawEmptyAnswers = true; + + if (reqIsPtr || !nextName()) { + // No more alternate names to try for the current server + + if (rep.rcode === 'NOERROR') + return doCb(null, rep, false); + + searchIdx = 0; + triedAsIs = false; + } else { + // Make sure we retry on TCP if we are already on TCP + // TODO: revert back to UDP if we are on TCP because of a truncated + // response? Either way we must use TCP if `TCPOnly === true` + triedTCP = false; + + // Try next name with current server + return nextLookup(true); + } + } else { + if (rep.truncated && !(flags & FLAG_RESOLVE_IGNTC) && !retryingOnTCP) { + retryingOnTCP = true; + // Retry query on TCP with current parameters + return nextLookup(true); + } + + // Stop searching + + // If we saw a non-fatal error response with an empty answer set before, + // make sure we do not treat the last response as an error (if there was + // one) + if (rep.rcode !== 'NOERROR' && sawEmptyAnswers) + rep.rcode = 'NOERROR'; + + if (rep.answers.length) { + var sortlist = dnsConfig.sortlist; + if (sortlist && sortlist.length) { + if (qtype === QTYPE_A) + rep.answers.sort(addressSortIPv4); + else if (qtype === QTYPE_AAAA) + rep.answers.sort(addressSortIPv6); + } + } + + return doCb(null, rep, false); + } + } + + // Advance to next server + nextLookup(false); + } + + // Determine the next domain name to be tried + function nextName() { + var domain; + if ((!search || ndots === -1 || ndots >= optNDots || + searchIdx === search.length || (flags & FLAG_RESOLVE_NOSEARCH)) && + !triedAsIs) { + triedAsIs = true; + reqBuf = reqBufCache[name]; + if (!reqBuf) { + reqBuf = reqToBuf(qtype, name); + if (!Buffer.isBuffer(reqBuf)) // Error + return doCb(reqBuf, undefined, true); + reqBufCache[name] = reqBuf; + } + curName = name; + return true; + } else if (!(flags & FLAG_RESOLVE_NOSEARCH) && ndots > -1 && search && + (domain = search[searchIdx++])) { + // Try the next domain + var newName = name + '.' + domain; + if (newName.charCodeAt(name.length - 1) === 46) // '.' + newName = newName.slice(0, -1); + reqBuf = reqBufCache[newName]; + if (!reqBuf) { + reqBuf = reqToBuf(qtype, newName); + if (!Buffer.isBuffer(reqBuf)) // Error + return doCb(reqBuf, undefined, true); + reqBufCache[newName] = reqBuf; + } + curName = newName; + return true; + } + return false; + } + + // Try the next lookup method, nameserver, or retry on a different protocol + // (e.g. TCP) + function nextLookup(retry) { + var lookupMethod = lookups[lookupIdx]; + + clearTimeout(timer); + + if (attempts-- === 0 || !lookupMethod) + return doCb(errnoException('ENOTFOUND', name), undefined, false); + + switch (lookupMethod) { + case 'dns': + if (!retry) { + // Advance to the next nameserver + ++nsIdx; + triedTCP = false; + if (TCPOnly) + retryingOnTCP = true; + } + var ns = nameservers[nsIdx]; + if (!ns) { + // No more nameservers left, try next lookup method + ++lookupIdx; + return nextLookup(false); + } + + if (retryingOnTCP) { + if (triedTCP) + return nextLookup(false); + triedTCP = true; + timer = setTimeout(cleanup, timeout); + socket = new TCPSocket(); + socket.on('connect', tcpOnConnect); + socket.on('data', tcpOnData); + socket.on('error', cleanup); + socket.on('close', tcpOnClose); + socket.connect(53, ns[1]); + } else { + socket = new UDPSocket(ns[0] === 4 ? 'udp4' : 'udp6'); + socket.on('listening', udpListening); + socket.on('message', udpMsgHandler); + socket.on('error', cleanup); + socket.on('close', nextLookup); + socket.bind(0); + } + break; + case 'files': + // File lookups can only answer A, AAAA, and PTR questions + if (reqIsPtr || qtype === QTYPE_A || qtype === QTYPE_AAAA) { + fileLookup(name, qtype, function(ret) { + if (ret !== false) { + return doCb(null, { + rcode: 'NOERROR', + authoritative: true, + truncated: false, + recurseAvail: true, + authenticated: false, + answers: [{ + name: name, + type: type, + ttl: 0, + data: ret + }], + authorities: [], + additional: [] + }, false); + } + ++lookupIdx; + nextLookup(false); + }); + } else { + ++lookupIdx; + nextLookup(false); + } + break; + } + } +} + +// Create a properly formatted Buffer from DNS request parameters +function reqToBuf(qtype, name) { + var origName = name; + var namelen = 1; // Account for terminating length byte + var buf; + + // Convert IP addresses to the appropriate reversed dotted notation + if (qtype === QTYPE_PTR) + name = createPtrAddr(name); + + name = name.split('.'); + + for (var i = 0; i < name.length; ++i) { + if (name[i].length > 63) + return errnoException(exports.NOTFOUND, origName); + // Label length byte + label content + namelen += 1 + Buffer.byteLength(name[i], 'ascii'); + } + if (namelen > 255) + return errnoException(exports.NOTFOUND, origName); + + buf = new Buffer(12 + namelen + 2 + 2); + + // ID + // We currently use 0 for all DNS queries because we do not use the same UDP + // socket for multiple (outstanding) queries + buf[0] = buf[1] = 0; + + // QR + OPCODE + AA + TC + RD + // 0 0 - - 1 + buf[2] = 1; + + // RA + Z + AD + CD + RCODE + // - 0 0 0 - + buf[3] = 0; + + // # Questions (1 for now) + buf[4] = 0; + buf[5] = 1; + + // # Answer RRs + buf[6] = buf[7] = 0; + + // # Authority RRs + buf[8] = buf[9] = 0; + + // # Additional RRs + buf[10] = buf[11] = 0; + + // Question: + // - QNAME + var p = 12; + for (var i = 0; i < name.length; ++i) { + buf[p++] = name[i].length; + buf.write(name[i], p, p += name[i].length, 'ascii'); + } + buf[p++] = 0; // Terminating length byte + + // - QTYPE + buf[p++] = 0; + buf[p++] = qtype; + + // - QCLASS + buf[p++] = 0; + buf[p++] = 1; // Internet + + return buf; +} + +// Convert an IPv4 or IPv6 address to its appropriate reverse dotted notation +// for reverse lookups +function createPtrAddr(name) { + var ipType = isIP(name); + + if (ipType === 4) { + var octets = name.split('.').reverse(); + // Remove any leading zeros + for (var i = 0; i < 4; ++i) + octets[i] = parseInt(octets[i], 10); + return octets.join('.') + '.in-addr.arpa'; + } else if (ipType === 6) { + var nibbles = new Array(32); // Individual hex digits + var chunks = name.split(':'); + + // Convert a possible embedded IPv4 address to IPv6 format + var lastChunk = chunks[chunks.length - 1]; + if (lastChunk.indexOf('.') > -1) { + var octets = lastChunk.split('.'); + chunks[chunks.length - 1] = ((parseInt(octets[0], 10) << 8) + + parseInt(octets[1], 10)).toString(16); + chunks.push(((parseInt(octets[2], 10) << 8) + + parseInt(octets[3], 10)).toString(16)); + } + + // Count existing bits in case of '::' to help us know how many 0's to add + var bits = 0; + var zeros; + for (var i = 0; i < chunks.length; ++i) { + if (chunks[i].length) + bits += 16; + } + zeros = (128 - bits) / 4; + + // Fill in nibbles + //var lastChunkIdx = chunks.length - 1; + for (var i = 0, n = 31; i < chunks.length; ++i) { + var val = chunks[i]; + var fill = (4 - val.length); + if (fill === 4 && zeros > -1) { + // '::' + for (var j = 0; j < zeros; ++j, --n) + nibbles[n] = '0'; + zeros = -1; + if (!chunks[i + 1].length) + ++i; // Skip over next empty string if ending on '::' + /*++i; // Skip over next empty string + // Check that we're not skipping over a valid chunk + if (i === lastChunkIdx) + --i;*/ + } else { + // Fill in leading zeros first + for (var j = 0; j < fill; ++j, --n) + nibbles[n] = '0'; + + for (var j = 0; j < val.length; ++j, --n) + nibbles[n] = val[j]; + } + } + + var ret = nibbles.join('.') + '.ip6.arpa'; + return ret; + } else if (!/\.(?:in-addr|ip6)\.arpa$/i.test(name)) { + throw errnoException('EINVAL', name); + } + + // `name` already contains the appropriate suffix, so we assume the rest of + // it is formatted correctly + return name; +} + +// Parse a raw reply from a nameserver +function parseReply(qtypename, buf) { + var buflen = buf.length; + var p = 0; + var flags1; + var AA; + var TC; + var flags2; + var RA; + var AD; + var RCODE; + var nquestions; + var answers; + var authorities; + var additional; + var ret; + + if (buf.length < 12) + return false; + + // Check ID field, we're currently only using 0 for any DNS query + if (buf[p++] !== 0 || buf[p++] !== 0) + return false; + + // QR + OPCODE + AA + TC + RD + flags1 = buf[p++]; + if (!(flags1 & 0x80)) // QR should be 1 for a response + return false; + if (flags1 & 0x78) // OPCODE should be 0 for standard query + return false; + AA = ((flags1 & 0x04) > 0); + TC = ((flags1 & 0x02) > 0); + + // RA + Z + AD + CD + RCODE + flags2 = buf[p++]; + RA = ((flags2 & 0x80) > 0); + AD = ((flags2 & 0x20) > 0); + RCODE = RCODE_V2S[(flags2 & 0x0F)] || 'UNKNOWN'; + + // # Questions + nquestions = (buf[p++] << 8) + buf[p++]; + + // # Answer RRs + answers = new Array((buf[p++] << 8) + buf[p++]); + + // # Authority RRs + authorities = new Array((buf[p++] << 8) + buf[p++]); + + // # Additional RRs + additional = new Array((buf[p++] << 8) + buf[p++]); + + // Question: + // For responses, this contains a copy of the original question(s), so we skip + // it altogether + for (var i = 0; i < nquestions; ++i) { + ret = parseName(buf, p, buflen); + if (!Array.isArray(ret)) // Error + return false; + p = ret[0]; + p += 4; // Skip over QTYPE and QCLASS too + } + + // Answers + for (var i = 0, alen = answers.length; i < alen; ++i) { + p = parseRR(buf, p, answers, i); + if (typeof p !== 'number') // Error + return false; + // Check for unsupported, irrelevant, and/or duplicate answers + var ans = answers[i]; + var shouldRemove = (ans === undefined || ans.type !== qtypename); + if (!shouldRemove) { + for (var j = 0; j < i; ++j) { + if (deepEqual(answers[j], ans)) { + shouldRemove = true; + break; + } + } + } + if (shouldRemove) { + spliceOne(answers, i); + --alen; + --i; + } + } + + // Authorities + for (var i = 0; i < authorities.length; ++i) { + p = parseRR(buf, p, authorities, i); + if (typeof p !== 'number') // Error + return false; + } + + // Additional + for (var i = 0; i < additional.length; ++i) { + p = parseRR(buf, p, additional, i); + if (typeof p !== 'number') // Error + return false; + } + + return { + rcode: RCODE, + authoritative: AA, + truncated: TC, + recurseAvail: RA, + authenticated: AD, + answers: answers, + authorities: authorities, + additional: additional + }; +} + +// Parse an individual Resource Record from a nameserver reply +function parseRR(buf, p, storage, stidx) { + var buflen = buf.length; + var typeObj; + var name; + var type; + var cls; + var ttl; + var rdlen; + var end; + var rr; + var ret; + + // NAME + ret = parseName(buf, p, buflen); + if (!Array.isArray(ret)) // Error + return false; + p = ret[0]; + name = ret[1]; + + if (p + 9 >= buflen) // `buf` too small + return false; + + // TYPE + type = (buf[p++] << 8) + buf[p++]; + + // CLASS + cls = (buf[p++] << 8) + buf[p++]; + + // TTL + ttl = (buf[p++] * 16777216) + (buf[p++] * 65536) + (buf[p++] * 256) + + buf[p++]; + + // RDLENGTH + rdlen = (buf[p++] << 8) + buf[p++]; + + end = p + rdlen; + if (end > buflen) // `buf` too small + return false; + + // Skip RRs of classes other than Internet + if (cls !== 1) // 1 === Internet + return end; + + // Skip RRs that have a type that we do not support + typeObj = QTYPE_V2O[type]; + if (!typeObj) + return end; + + rr = { + name: name, + type: typeObj.name, + ttl: ttl, + data: null + }; + + // RDATA + if (rdlen > 0) { + p = typeObj.parse(rr, buf, p, end); + if (p === false) // Error + return false; + } + + // Append the result directly to the caller's storage array to avoid having to + // return an array containing the result *and* the current position + storage[stidx] = rr; + + return p; +} + +// Parse a domain name string +function parseName(buf, p, end) { + var visited = []; + var name = ''; + var oldp; + var len; + + while (len = buf[p++]) { + // 63 is the max length for a non-pointer length byte (00111111) + if (len > 63) { + // Pointer + if ((len & 192) === 192) { + // Both of the 2 leftmost bits are set, this is the type of pointer + // described by RFC 1035 + + if (p >= end) + return false; + + // If this is our first pointer encounter, make sure we store the + // current position in the Buffer so that we know where to continue from + // once we reach the last pointer + if (!oldp) + oldp = p + 1; + + p = ((len & 63) << 8) + buf[p]; + + if (p >= end) + return false; + + // Check for a pointer loop + if (visited.indexOf(p) > -1) + return false; + visited.push(p); + } else { + // Unknown pointer type (e.g. EDNS defined a new binary label/pointer + // type (now deprecated as of RFC 6891)) + return false; + } + } else { + // Simple label + if (p + (len - 1) >= end) + return false; + if (name) + name += '.'; + name += buf.toString('ascii', p, p += len); + } + } + + // Reset our position if we followed a pointer at some point + if (oldp) + p = oldp; + + // Remove root from parsed name + if (name[name.length - 1] === '.') + name = name.slice(0, -1); + + return [ p, name ]; +} + +// Utility function for merging only properties unset on one object that are +// set on another object. +function fillIn(base, values) { + var keys = Object.keys(base); + for (var i = 0, key; i < keys.length; ++i) { + key = keys[i]; + if (typeof base[key] === 'object' && base[key] !== null && + typeof values[key] === 'object' && values[key] !== null) + fillIn(base[key], values[key]); + else if (base[key] === null) { + if (values[key] !== null && values[key] !== undefined) + base[key] = values[key]; + } + } +} + +// Perform a lookup in the local hosts file +function fileLookup(str, qtype, cb) { + if (process.platform === 'win32') { + if (!win32_dbPath) + return process.nextTick(cb, false); + searchHostsFile(pathJoin(win32_dbPath, 'hosts'), str, qtype, cb); + } else { + // *nix + searchHostsFile('/etc/hosts', str, qtype, cb); + } +} + +// This performs the actual search through a local hosts file, for either normal +// or reverse lookups +function searchHostsFile(path, str, qtype, cb) { + var ret = false; + var ipver = (qtype === QTYPE_A ? 4 : qtype === QTYPE_AAAA ? 6 : 0); + var reverse = (isIP(str) !== 0); + var data = new Buffer(128); + var regexp = new RegExp(regexpEscape(str), 'i'); + var last = ''; + var addrParsed = (reverse ? parseAddrMask(str, true) : null); + var lines; + var host; + + function closecb(err) { + cb(ret); + } + fs.open(path, 'r', function(err, fd) { + if (err) + return closecb(); + fs.read(fd, data, 0, data.length, null, function readcb(err, nb) { + if (err) + return fs.close(fd, closecb); + + if (nb) { + lines = (last + data.toString('utf8', 0, nb)).split(/\r?\n/g); + last = lines[lines.length - 1]; + for (var i = 0; i < lines.length - 1; ++i) { + host = parseHostLine(lines[i]); + if (host !== false) { + if (reverse && + addrsEqual(parseAddrMask(host[0], true), addrParsed)) { + ret = host[1]; + return fs.close(fd, closecb); + } else if (!reverse) { + for (var k = 1, hostval; k < host.length; ++k) { + hostval = host[k]; + if (hostval.length !== str.length) + continue; + if (hostval.match(regexp) !== null) { + // We found a matching hostname, but is the first value on the + // line a real IP address and is the right IP version we're + // searching for? + var hostipver = isIP(host[0]); + if (hostipver === ipver || (hostipver && ipver === 0)) { + ret = host[0]; + return fs.close(fd, closecb); + } + } + } + } + } + } + fs.read(fd, data, 0, data.length, null, readcb); + } else { + // If the file didn't end on a line ending, we interpret what was left + // in our buffer as a line + if (ret === false && last) { + host = parseHostLine(last); + if (host !== false) { + if (reverse && + addrsEqual(parseAddrMask(host[0], true), addrParsed)) { + ret = host[1]; + } else if (!reverse) { + for (var k = 1, hostval; k < host.length; ++k) { + hostval = host[k]; + if (hostval.length !== str.length) + continue; + if (hostval.match(regexp) !== null) { + var hostipver = isIP(host[0]); + if (hostipver === ipver || (hostipver && ipver === 0)) { + ret = host[0]; + break; + } + } + } + } + } + } + fs.close(fd, closecb); + } + }); + }); +} + +// Byte/Word-wise equality check of two IP addresses +function addrsEqual(a, b) { + if (a === false || b === false || a.length !== b.length) + return false; + for (var i = 0; i < a.length; ++i) { + if (a[i] !== b[i]) + return false; + } + return true; +} + +function regexpEscape(str) { + return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); +} + +function parseHostLine(line) { + line = line.replace(/#.*$/, '').trim(); + var fields = line.split(REGEX_WHITESPACE); + if (fields.length < 2) + return false; + return fields; +} + +// This searches a services file for the service name for a particular port +// number +function searchServicesFile(path, port, cb) { + var ret = false; + var data = new Buffer(128); + var last = ''; + var lines; + + function closecb(err) { + cb(ret); + } + fs.open(path, 'r', function(err, fd) { + if (err) + return closecb(); + fs.read(fd, data, 0, data.length, null, function readcb(err, nb) { + if (err) + return fs.close(fd, closecb); + + if (nb) { + lines = (last + data.toString('ascii', 0, nb)).split(/\r?\n/g); + last = lines[lines.length - 1]; + for (var i = 0; i < lines.length - 1; ++i) { + ret = checkPort(lines[i], port); + if (ret !== false) + return fs.close(fd, closecb); + } + fs.read(fd, data, 0, data.length, null, readcb); + } else { + if (ret === false && last) + ret = checkPort(last, port); + fs.close(fd, closecb); + } + }); + }); +} + +function checkPort(line, port) { + var m = /^([A-Za-z0-9\-]+)[ \t]+(\d+)\/[A-Za-z]+/.exec(line); + if (m && m[2] == port) + return m[1]; + return false; +} + +// Helper function currently used to check if two DNS answers are equal +function deepEqual(a, b) { + if (typeof a === 'object' && typeof b === 'object' && + a !== null && b !== null) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + var alen = keysA.length; + var blen = keysB.length; + if (alen !== blen) + return false; + for (var i = 0; i < alen; ++i) { + if (!deepEqual(a[keysA[i]], b[keysB[i]])) + return false; + } + return true; + } + return (a === b); +} + +// Parses strings of the format:
[/netmask] +// Into [parsedAddr, [parsedFullNetMask]] +function parseAddrMask(network, addrOnly) { + var ret; + var ipver; + + if (!addrOnly) { + // Check for explicit netmask + if (network.indexOf('/') > -1) { + network = network.split('/', 2); + ipver = isIP(network[0]); + var mask = network[1]; + if (ipver === 4) { + if (/^\d{1,2}$/.test(mask)) { + // CIDR notation + mask = parseInt(mask, 10); + if (mask >= 0 && mask <= 32) { + if (mask > 0) + mask = (0xFFFFFFFF ^ (1 << 32 - mask) - 1); + network[1] = [mask >>> 24, (mask >>> 16) & 0xFF, + (mask >>> 8) & 0xFF, mask & 0xFF]; + ret = network; + } + } else if (isIP(network[1]) === 4) { + // Dotted notation + var octets = mask.split('.'); + for (var k = 0; k < 4; ++k) + octets[k] = parseInt(octets[k], 10); + network[1] = octets; + ret = network; + } + } else if (ipver === 6) { + // CIDR notation + if (/^\d{1,3}$/.test(mask)) { + mask = parseInt(mask, 10); + if (mask >= 0 && mask <= 128) { + var newMask = [0, 0, 0, 0, 0, 0, 0, 0]; + var idx = 0; + while (mask && idx < 8) { + var bits = (mask > 16 ? 16 : mask); + newMask[idx++] = (0xFFFF ^ (1 << 16 - bits) - 1); + mask -= bits; + } + network[1] = newMask; + ret = network; + } + } + } + } else { + // No explicit netmask given, use default + ipver = isIP(network); + if (ipver === 4) { + var octet = parseInt(network.split('.')[0], 10); + if (octet >= 0 && octet < 128) + ret = [network, [255, 0, 0, 0]]; + else if (octet >= 128 && octet < 192) + ret = [network, [255, 255, 0, 0]]; + else if (octet >= 192 && octet < 224) + ret = [network, [255, 255, 255, 0]]; + } else if (ipver === 6) { + // Use "standard" mask of /64 for IPv6 + ret = [network, [65535, 65535, 65535, 65535, 0, 0, 0, 0]]; + } + } + } + + // Now parse address for convenience + if (ret || addrOnly) { + var addr; + if (typeof network === 'string') { + addr = network; + ipver = isIP(addr); + } else { + addr = network[0]; + } + if (ipver === 4) { + addr = addr.split('.'); + for (var k = 0; k < 4; ++k) + addr[k] = parseInt(addr[k], 10); + } else if (ipver === 6) { + var chunks = addr.split(':'); + var a = 0; + addr = new Array(8); + + // Convert a possible embedded IPv4 dotted decimals to IPv6 format + var lastChunk = chunks[chunks.length - 1]; + if (lastChunk.indexOf('.') > -1) { + var octets = lastChunk.split('.'); + chunks[chunks.length - 1] = ((parseInt(octets[0], 10) << 8) + + parseInt(octets[1], 10)).toString(16); + chunks.push(((parseInt(octets[2], 10) << 8) + + parseInt(octets[3], 10)).toString(16)); + } + + // Count existing bits in case of '::' to help us know how many zero words + // to add + var bits = 0; + for (var i = 0; i < chunks.length; ++i) { + if (chunks[i].length) + bits += 16; + } + var zeros = (128 - bits) / 16; + + // Parse chunks + for (var i = 0; i < chunks.length; ++i) { + var val = chunks[i]; + if (val.length === 0 && zeros > -1) { + // '::' + for (var j = 0; j < zeros; ++j) + addr[a++] = 0; + zeros = -1; + if (!chunks[i + 1].length) + ++i; // Skip over next empty string if ending on '::' + } else { + addr[a++] = parseInt(val, 16); + } + } + } + if (addrOnly) + ret = addr; + else if (typeof network === 'string') + ret[0] = addr; + else + network[0] = addr; + } + + return ret; +} + +// Compares two IPv4 addresses according to a system-defined sortlist +function addressSortIPv4(a, b) { + var sortlist = dnsConfig.sortlist; + var ai = -1; + var bi = -1; + + a = parseAddrMask(a, true); + b = parseAddrMask(b, true); + + // Find matching network/mask for both addresses + for (var i = 0; i < sortlist.length; ++i) { + var network = sortlist[i][0]; + var mask = sortlist[i][1]; + var matchedA = true; + var matchedB = true; + if (network.length !== 4) + continue; + + for (var j = 0; j < 4; ++j) { + if (matchedA && ai === -1 && (a[j] & mask[j]) !== network[j]) + matchedA = false; + if (matchedB && bi === -1 && (b[j] & mask[j]) !== network[j]) + matchedB = false; + if (!matchedA && !matchedB) + break; + } + + if (matchedA && ai === -1) + ai = i; + if (matchedB && bi === -1) + bi = i; + if (ai > -1 && b > -1) + break; + } + + if (ai === bi) { + var d = a[0] - b[0]; + if (!d) { + d = a[1] - b[1]; + if (!d) { + d = a[2] - b[2]; + if (!d) + return (a[3] - b[3]); + } + } + return d; + } else if (ai < 0) { + return 1; + } else if (bi < 0) { + return -1; + } + return ai - bi; +} + +// Compares two IPv6 addresses according to a system-defined sortlist +function addressSortIPv6(a, b) { + var sortlist = dnsConfig.sortlist; + var ai = -1; + var bi = -1; + + a = parseAddrMask(a, true); + b = parseAddrMask(b, true); + + // Find matching network/mask for both addresses + for (var i = 0; i < sortlist.length; ++i) { + var network = sortlist[i][0]; + var mask = sortlist[i][1]; + var matchedA = true; + var matchedB = true; + if (network.length !== 8) + continue; + + for (var j = 0; j < 8; ++j) { + if (matchedA && ai === -1 && (a[j] & mask[j]) !== network[j]) + matchedA = false; + if (matchedB && bi === -1 && (b[j] & mask[j]) !== network[j]) + matchedB = false; + if (!matchedA && !matchedB) + break; + } + + if (matchedA && ai === -1) + ai = i; + if (matchedB && bi === -1) + bi = i; + if (ai > -1 && b > -1) + break; + } + + if (ai === bi) { + var i = 0; + var d; + while (i < 8) { + d = a[i] - b[i]; + if (d) + return d; + ++i; + } + return 0; + } else if (ai < 0) { + return 1; + } else if (bi < 0) { + return -1; + } + return ai - bi; +} + +// Check to see what types of interface addresses currently exist in the system. +// This is used when the ADDRCONFIG flag is set to determine what kind of +// IP addresses should be returned to the user +function getIfaceAddrTypes() { + var ret = 0; + var interfaces = os.networkInterfaces(); + var ifkeys = Object.keys(interfaces); + var hasIPv6LL = false; + + for (var i = 0, addrs; i < ifkeys.length; ++i) { + addrs = interfaces[ifkeys[i]]; + for (var j = 0, addr; j < addrs.length; ++j) { + addr = addrs[j]; + // Do not consider loopback addresses + if (!addr.internal) { + switch (addr.family) { + case 'IPv4': + // Also do not consider link-local addresses + if (addr.address.slice(0, 8) !== '169.254.') + ret |= FLAG_HAS_IPv4; + break; + case 'IPv6': + if (addr.address.slice(0, 6) === 'fe80::') + hasIPv6LL = true; + else + ret |= FLAG_HAS_IPv6; + break; + } + } + } + if (ret === FLAG_HAS_IPv4_IPv6) // No need to search any more + break; + } + + // Only consider IPv6 link-local addresses if there are no IPv4 addresses + // See: https://fedoraproject.org/wiki/Networking/NameResolution/ADDRCONFIG + if (ret === 0 && hasIPv6LL) + ret = FLAG_HAS_IPv6; + + return ret; +} + +function spliceOne(list, index) { + for (var k = index + 1, n = list.length; k < n; index += 1, k += 1) + list[index] = list[k]; + list.pop(); +} + + +// Platform-specific helper methods: +function win32_wmiDNSInfo() { + // TODO: check AppendToMultiLabelName registry value before setting + // `DNSDomainSuffixSearchOrder` + // http://blogs.technet.com/b/networking/archive/2009/04/16/dns-client-name- + // resolution-behavior-in-windows-vista-vs-windows-xp.aspx + var ret = false; + var cmd = [ + 'wmic', + 'nicconfig', + 'where "MACAddress is not null and IPEnabled = true"', + 'get', + ['DNSDomain', + 'DNSDomainSuffixSearchOrder', + 'DNSServerSearchOrder', + 'IPConnectionMetric' + ].join(', '), + '/format:csv' + ].join(' '); + + try { + var r = execSync(cmd, { encoding: 'utf8' }); + var lines = r.split(/\r?\n/g); + var path = 'HKLM\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters'; + var header; + var dnsinfo; + for (var i = 0, line; i < lines.length; ++i) { + line = lines[i].trim(); + if (line) { + line = line.split(','); + if (!header) + header = line; + else { + var row = makeObj(header, line); + + if (row.DNSServerSearchOrder) { + // There is at least one DNS server set for this connection + + row.IPConnectionMetric = parseInt(row.IPConnectionMetric, 10); + // Only choose the parameters from the connection with the lowest + // connection metric + if (!dnsinfo || row.IPConnectionMetric < dnsinfo.IPConnectionMetric) + dnsinfo = row; + } + } + } + } + if (dnsinfo) { + var ns = REGEX_ARRAY.exec(dnsinfo.DNSServerSearchOrder)[1].split(';'); + var search = null; + if (dnsinfo.DNSDomainSuffixSearchOrder) + dnsinfo.DNSDomainSuffixSearchOrder = null; + if (dnsinfo.DNSDomainSuffixSearchOrder) + search = dnsinfo.DNSDomainSuffixSearchOrder; + else { + var primarySuffix = win32_regQuery(path, 'NV Domain', false); + if (dnsinfo.DNSDomain) { + search = dnsinfo.DNSDomain; + if (primarySuffix) + search = primarySuffix + ';' + search; + } else if (primarySuffix) { + search = primarySuffix; + } + } + if (search) + search = REGEX_ARRAY.exec(search)[1].split(';'); + ret = { + nameserver: ns, + search: search, + sortlist: null, + options: { + // Windows always tries a multi-label name as-is before using suffixes + ndots: 1, + // TODO: support `timeout` via DNSQueryTimeouts registry value? + // https://technet.microsoft.com/library/Cc977482 + timeout: null, + attempts: null, + rotate: null, + inet6: null, + use_vc: null + }, + // This is the order that both c-ares (on Windows) and the Windows DNS + // client use + lookup: ['files', 'dns'] + }; + } + } catch (ex) {} + + return ret; +} + +// Used by the WMI-based DNS settings retrieval method on Windows for creating +// an object from CSV output +function makeObj(keys, vals) { + var obj = {}; + for (var i = 0; i < keys.length; ++i) { + if (vals[i][0] === '{') + vals[i] = vals[i].slice(1, -1); + obj[keys[i]] = vals[i]; + } + return obj; +} + +function win32_getRegNameServers() { + var r; + var path; + + // Try global DNS servers first + path = 'HKLM\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters'; + r = win32_regQuery(path, 'NameServer', false); + if (r !== false) + return r.split(/[ \f\t\v,]+/g); + r = win32_regQuery(path, 'DhcpNameServer', false); + if (r !== false) + return r.split(/[ \f\t\v,]+/g); + + // Next try to find the first interface DNS address + // TODO: make this smarter/better rather than just using first interface? + path += '\\Interfaces'; + r = win32_regQuery(path, 'NameServer', true); + if (r !== false) + return r.split(/[ \f\t\v,]+/g); + r = win32_regQuery(path, 'DhcpNameServer', true); + if (r !== false) + return r.split(/[ \f\t\v,]+/g); + + return false; +} + +function win32_regQuery(path, key, recurse) { + var cmd = [ + 'reg', + 'query', + path, + (recurse ? '/s ' : '') + '/v ' + key, + '2>NUL', + '|', + 'findstr ' + key + ].join(' '); + + try { + var r = execSync(cmd, { encoding: 'utf8' }); + r = /^([^ \t]+)[ \t]+([^ \t]+)[ \t]+(.+)$/mg.exec(r.trim()); + if (r && r[3]) + return r[3]; + } catch (ex) {} + return false; +} + +function win32_expandEnvironmentStrings(str) { + var envVars = Object.keys(process.env); + return str.replace(/%([^%]+)%/g, function(match, varName) { + varName = varName.toUpperCase(); + for (var i = 0; i < envVars.length; ++i) { + if (varName === envVars[i].toUpperCase()) { + return process.env[envVars[i]]; + } + } + return match; + }); +} + +function android_getNameServers() { + var servers = false; + for (var i = 0; i < 8; ++i) { + var cmd = 'getprop net.dns' + (i + 1); + + try { + var r = execSync(cmd, { encoding: 'utf8' }); + r = r.trim(); + if (r) { + if (!servers) + servers = [ r ]; + else + servers.push(r); + } + } catch (ex) {} + } + return servers; +} + +function parseResolvConf() { + var ret = false; + var lines; + + try { + lines = fs.readFileSync('/etc/resolv.conf', 'utf8').split(/\r?\n/g); + ret = { + nameserver: null, + search: null, + sortlist: null, + options: { + ndots: null, + timeout: null, + attempts: null, + rotate: null, + inet6: null, + use_vc: null + }, + lookup: null + }; + for (var i = 0; i < lines.length; ++i) { + var line = lines[i].replace(REGEX_COMMENT, '') + .trim() + .replace(REGEX_WHITESPACE, ' '); + var m = REGEX_TYPES.exec(line); + if (m) { + var val = line.slice(m[0].length); + if (!val.length) + continue; + switch (m[1]) { + case 'nameserver': + if (!isIP(val)) + continue; + if (!ret.nameserver) + ret.nameserver = [val]; + else if (ret.nameserver.length < 3) + ret.nameserver.push(val); + break; + case 'domain': + ret.search = [val]; + break; + case 'search': + val = val.split(' '); + if (val.length > 6) + val = val.slice(0, 6); + ret.search = val; + break; + case 'sortlist': + val = val.split(' '); + if (val.length > 10) + val = val.slice(0, 10); + for (var j = 0, valLen = val.length; j < valLen; ++j) { + var network = parseAddrMask(val[j], false); + if (network !== undefined) + val[j] = network; + else { + val.splice(j, 1); + --j; + --valLen; + } + } + if (val.length) + ret.sortlist = val; + break; + case 'options': + fillIn(ret.options, parseOptions(val.split(' '))); + break; + case 'lookup': + val = val.split(' '); + for (var i = 0, loc; i < val.length; ++i) { + loc = val[i].toLowerCase(); + if (loc === 'bind') + loc = 'dns'; + else if (loc === 'file') + loc = 'files'; + if (loc === 'dns' || loc === 'files') { + if (!ret.lookup) + ret.lookup = [loc]; + else if (ret.lookup.indexOf(loc) === -1) + ret.lookup.push(loc); + } + } + break; + } + } + } + } catch (ex) {} + + return ret; +} + +function parseOptions(options) { + var ret = { + ndots: null, + timeout: null, + attempts: null, + rotate: null, + inet6: null, + use_vc: null + }; + for (var j = 0; j < options.length; ++j) { + m = REGEX_OPTS.exec(options[i]); + if (m) { + val = m[2]; + switch (m[1]) { + case 'ndots': + if (val) { + val = parseInt(val, 10); + if (val <= 15) + ret.ndots = val; + } + break; + case 'timeout': + if (val) { + val = parseInt(val, 10); + if (val <= 30) + ret.timeout = val; + } + break; + case 'attempts': + if (val) { + val = parseInt(val, 10); + if (val <= 5) + ret.attempts = val; + } + break; + case 'rotate': + ret.rotate = true; + break; + case 'inet6': + ret.inet6 = true; + break; + case 'use-vc': + ret.use_vc = true; + break; + } + } + } + return ret; +} + +function parseNsswitchConf() { + // TODO: search for explicitly defined services file path too + var ret = false; + + try { + var data = fs.readFileSync('/etc/nsswitch.conf', 'utf8'); + var m = /^hosts:[ \f\t\v]*(.+)$/m.exec(data); + if (m) { + var order = m[1].replace(REGEX_COMMENT, '').trim().split(/[ \f\t\v,]+/g); + for (var i = 0, loc; i < order.length; ++i) { + loc = order[i].toLowerCase(); + if ((loc === 'dns' || loc === 'files') && ret.indexOf(loc) === -1) { + if (!ret) + ret = [loc]; + else + ret.push(loc); + } + } + } + } catch (ex) {} + + return ret; +} + +function parseHostConf() { + var ret = false; + + try { + var data = fs.readFileSync('/etc/host.conf', 'utf8'); + var m = /^order[ \f\t\v]+(.+)$/m.exec(data); + if (m) { + var order = m[1].replace(REGEX_COMMENT, '') + .trim().split(REGEX_WHITESPACE); + for (var i = 0, loc; i < order.length; ++i) { + loc = order[i].toLowerCase(); + if (loc === 'bind' && ret.indexOf('dns') === -1) { + if (!ret) + ret = ['dns']; + else + ret.push('dns'); + } else if (loc === 'hosts' && ret.indexOf('files') === -1) { + if (!ret) + ret = ['files']; + else + ret.push('files'); + } + } + } + } catch (ex) {} + + return ret; +} + +function searchHostAliases(name, cb) { + var ret = false; + var regexp = new RegExp( + '^' + regexpEscape(name) + '[ \\t]+([A-Za-z0-9\\-\\.]+)', 'i' + ); + var data = new Buffer(128); + var last = ''; + var lines; + var m; + + function closecb(err) { + cb(ret || name); + } + fs.open(process.env.HOSTALIASES, 'r', function(err, fd) { + if (err) + return closecb(); + fs.read(fd, data, 0, data.length, null, function readcb(err, nb) { + if (err) + return fs.close(fd, closecb); + + if (nb) { + lines = (last + data.toString('ascii', 0, nb)).split(/\r?\n/g); + last = lines[lines.length - 1]; + for (var i = 0; i < lines.length - 1; ++i) { + m = regexp.exec(lines[i]); + if (m) { + ret = m[1]; + return fs.close(fd, closecb); + } + } + fs.read(fd, data, 0, data.length, null, readcb); + } else { + if (ret === false && last) { + m = regexp.exec(last); + if (m) + ret = m[1]; + } + fs.close(fd, closecb); + } + }); + }); +} + +// By default child_process.execSync() writes stderr data directly to the parent +// process's stderr. We don't want that, so we use our own wrapper ... +function execSync(cmd, opts) { + opts.stdio = ['pipe', 'pipe', 'ignore']; + return cp.execSync(cmd, opts); +} + + +// Initialize DNS settings +(function initDNS() { + var r; + + if (process.platform === 'win32') { + // Store the DatabasePath to avoid looking it up every time in + // lookupService() and fileLookup() + var path = 'HKLM\\System\\CurrentControlSet\\Services\\Tcpip\\Parameters'; + win32_dbPath = win32_regQuery(path, 'DataBasePath', false); + if (win32_dbPath) + win32_dbPath = win32_expandEnvironmentStrings(win32_dbPath); + // TODO: create fallbacks for win32_dbPath if it's not set in registry for + // some reason (e.g. try "%SystemRoot%\system32\drivers\etc" then + // "C:\Windows\system32\drivers\etc")? + + // Try WMI first + r = win32_wmiDNSInfo(); + if (r !== false) + fillIn(dnsConfig, r); + else { + // Try registry next, resulting in only nameserver info for now ... + r = win32_getRegNameServers(); + if (r !== false) + fillIn(dnsConfig, { nameserver: r }); + } + } else if (process.platform === 'android') { + // Only nameserver info for now ... + r = android_getNameServers(); + if (r !== false) + fillIn(dnsConfig, { nameserver: r }); + } else { + // *nix + + // Check environment + if (process.env.LOCALDOMAIN) { + r = process.env.LOCALDOMAIN.split(/[ \t]+/); + if (r.length > 6) + r = r.slice(0, 6); + if (r.length && dnsConfig.search === null) + dnsConfig.search = r; + } + if (process.env.RES_OPTIONS) + fillIn(dnsConfig.options, parseOptions(process.env.RES_OPTIONS)); + + r = parseResolvConf(); + if (r !== false) + fillIn(dnsConfig, r); + + if (!dnsConfig.lookup) { + r = parseNsswitchConf(); + if (r !== false) + fillIn(dnsConfig, r); + } + + if (!dnsConfig.lookup) { + r = parseHostConf(); + if (r !== false) + fillIn(dnsConfig, r); + } + } + + // Set missing parameters to some sensible defaults + fillIn(dnsConfig, DNS_DEFAULTS); + + // Reformat nameservers to cache IP type + for (var i = 0; i < dnsConfig.nameserver.length; ++i) { + dnsConfig.nameserver[i] = [ isIP(dnsConfig.nameserver[i]), + dnsConfig.nameserver[i] ]; + } +})(); diff --git a/node.gyp b/node.gyp index 4f096f580ab1d7..0c0977de754a68 100644 --- a/node.gyp +++ b/node.gyp @@ -71,6 +71,7 @@ 'lib/zlib.js', 'lib/internal/child_process.js', 'lib/internal/cluster.js', + 'lib/internal/dns.js', 'lib/internal/freelist.js', 'lib/internal/linkedlist.js', 'lib/internal/module.js', diff --git a/src/node.cc b/src/node.cc index 894c8f76416275..b37cc7330a6e0f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -138,6 +138,7 @@ static bool trace_deprecation = false; static bool throw_deprecation = false; static bool trace_sync_io = false; static bool track_heap_objects = false; +static bool use_old_dns = false; static const char* eval_string = nullptr; static unsigned int preload_module_count = 0; static const char** preload_modules = nullptr; @@ -3035,6 +3036,11 @@ void SetupProcessObject(Environment* env, READONLY_PROPERTY(process, "traceDeprecation", True(env->isolate())); } + // --use-old-dns + if (use_old_dns) { + READONLY_PROPERTY(process, "oldDNS", True(env->isolate())); + } + size_t exec_path_len = 2 * PATH_MAX; char* exec_path = new char[exec_path_len]; Local exec_path_value; @@ -3273,6 +3279,11 @@ static void PrintHelp() { #if HAVE_OPENSSL " --tls-cipher-list=val use an alternative default TLS cipher list\n" #endif + " --trace-deprecation show stack traces on deprecations\n" + " --trace-sync-io show stack trace when use of sync IO\n" + " is detected after the first tick\n" + " --use-old-dns use c-ares for DNS resolution\n" + " --v8-options print v8 command line options\n" #if defined(NODE_HAVE_I18N_SUPPORT) " --icu-data-dir=dir set ICU data load path to dir\n" " (overrides NODE_ICU_DATA)\n" @@ -3402,6 +3413,8 @@ static void ParseArgs(int* argc, track_heap_objects = true; } else if (strcmp(arg, "--throw-deprecation") == 0) { throw_deprecation = true; + } else if (strcmp(arg, "--use-old-dns") == 0) { + use_old_dns = true; } else if (strcmp(arg, "--prof-process") == 0) { prof_process = true; short_circuit = true; diff --git a/test/common.js b/test/common.js index bd9d4956c901ce..9c478af716062b 100644 --- a/test/common.js +++ b/test/common.js @@ -451,6 +451,42 @@ exports.getServiceName = function getServiceName(port, protocol) { return serviceName; }; +exports.hasIPv6 = function() { + var interfaces = require('os').networkInterfaces(); + var ifkeys = Object.keys(interfaces); + var hasIPv6LL = false; + var hasIPv4 = false; + + for (var i = 0, addrs; i < ifkeys.length; ++i) { + addrs = interfaces[ifkeys[i]]; + for (var j = 0, addr; j < addrs.length; ++j) { + addr = addrs[j]; + // Do not consider loopback addresses + if (!addr.internal) { + switch (addr.family) { + case 'IPv4': + // Also do not consider link-local addresses + if (addr.address.slice(0, 8) !== '169.254.') + hasIPv4 = true; + break; + case 'IPv6': + if (addr.address.slice(0, 6) === 'fe80::') + hasIPv6LL = true; + else + return true; + break; + } + } + } + } + + // Only consider IPv6 link-local addresses if there are no IPv4 addresses + if (!hasIPv4 && hasIPv6LL) + return true; + + return false; +}; + exports.hasMultiLocalhost = function hasMultiLocalhost() { var TCP = process.binding('tcp_wrap').TCP; var t = new TCP(); diff --git a/test/internet/test-dns-domain-search.js b/test/internet/test-dns-domain-search.js new file mode 100644 index 00000000000000..350aff2a20e1a6 --- /dev/null +++ b/test/internet/test-dns-domain-search.js @@ -0,0 +1,191 @@ +'use strict'; + +if (process.oldDNS || process.platform === 'win32') + process.exit(0); + +process.env.LOCALDOMAIN = 'example.com example.net example.org'; + +var assert = require('assert'), + dns = require('dns'), + dgram = require('dgram'), + net = require('net'), + format = require('util').format; + +var UDPServer = dgram.createSocket('udp4'), + TCPServer = net.createServer(), + expected = 0, + completed = 0, + current = null, + running = false, + queue = [], + listening = 0; + + +function TEST(f) { + function next() { + current = queue.shift(); + if (current) { + running = true; + if (current.name) + console.log(current.name); + current(done); + } else { + try { + UDPServer.close(); + } catch (ex) {} + try { + TCPServer.close(); + } catch (ex) {} + } + } + + function done() { + running = false; + completed++; + process.nextTick(next); + } + + expected++; + queue.push(f); + + if (!running) { + next(); + } +} + +function UDPServerReply(replybuf, expbuf, nreqs) { + if (typeof expbuf === 'number') { + nreqs = expbuf; + expbuf = undefined; + } else if (nreqs === undefined) { + nreqs = 1; + } + var testName = current.name; + var timer = setTimeout(function() { + throw new Error(format('Timeout on UDP test (%s)', testName)); + }, 100); + UDPServer.once('message', function(buf, rinfo) { + clearTimeout(timer); + if (expbuf) { + if (Array.isArray(expbuf)) { + assert.deepEqual(buf, expbuf.shift()); + if (expbuf.length === 1) + expbuf = expbuf[0]; + else if (!expbuf.length) + expbuf = undefined; + } else { + assert.deepEqual(buf, expbuf); + expbuf = undefined; + } + } + UDPServer.send(replybuf, 0, replybuf.length, rinfo.port, rinfo.address); + if (--nreqs > 0) + UDPServerReply(replybuf, expbuf, nreqs); + }); +} + +function TCPServerReply(replybuf, expbuf, nreqs) { + if (typeof expbuf === 'number') { + nreqs = expbuf; + expbuf = undefined; + } else if (nreqs === undefined) { + nreqs = 1; + } + var testName = current.name; + var timer = setTimeout(function() { + throw new Error(format('Timeout on TCP test (%s)', testName)); + }, 100); + function readN(s, n, cb) { + var r = s.read(n); + if (r === null) + return s.once('readable', readN.bind(null, s, n, cb)); + cb(r); + } + TCPServer.once('connection', function(s) { + readN(s, 2, function readLength(buf) { + readN(s, buf.readUInt16BE(0), function readBytes(buf) { + clearTimeout(timer); + if (expbuf) { + if (Array.isArray(expbuf)) { + assert.deepEqual(buf, expbuf.shift()); + if (expbuf.length === 1) + expbuf = expbuf[0]; + else if (!expbuf.length) + expbuf = undefined; + } else { + assert.deepEqual(buf, expbuf); + expbuf = undefined; + } + } + var lenbuf = new Buffer(2); + lenbuf.writeUInt16BE(replybuf.length, 0); + s.write(lenbuf); + s.write(replybuf); + if (--nreqs > 0) + TCPServerReply(replybuf, expbuf, nreqs); + }); + }); + }); +} + +dns.setServers(['127.0.0.1']); +UDPServer.bind(53, '127.0.0.1', addTests); +TCPServer.listen(53, '127.0.0.1', addTests); + +function addTests() { + if (++listening < 2) + return; + + ['UDP', 'TCP'].forEach(function(connType) { + var isTCP = (connType === 'TCP'); + var serverReply = (isTCP ? TCPServerReply : UDPServerReply); + var opts = { + hints: 0, + flags: dns.FLAG_RESOLVE_NSONLY | (isTCP ? dns.FLAG_RESOLVE_USEVC : 0), + family: 4 + }; + + TEST(function(done) { + console.log('%s tests', connType); + console.log('========='); + process.nextTick(done); + }); + + + TEST(function test_search(done) { + // Should be 4 attempts, 1 for each domain in LOCALDOMAIN and then as-is + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + ]), 4); + dns.lookup('foo', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + }); +} + + +process.on('exit', function() { + if (expected > 0) + console.log('%d/%d tests completed', completed, expected); + assert.equal(running, false); + assert.strictEqual(expected, completed); +}); diff --git a/test/internet/test-dns-durability.js b/test/internet/test-dns-durability.js new file mode 100644 index 00000000000000..b49c6bd74e9e1a --- /dev/null +++ b/test/internet/test-dns-durability.js @@ -0,0 +1,1443 @@ +'use strict'; + +if (process.oldDNS) + process.exit(0); + +var assert = require('assert'), + dns = require('dns'), + dgram = require('dgram'), + net = require('net'), + format = require('util').format; + +var UDPServer = dgram.createSocket('udp4'), + TCPServer = net.createServer(), + expected = 0, + completed = 0, + current = null, + running = false, + queue = [], + listening = 0; + + +function TEST(f) { + function next() { + current = queue.shift(); + if (current) { + running = true; + if (current.name) + console.log(current.name); + current(done); + } else { + try { + UDPServer.close(); + } catch (ex) {} + try { + TCPServer.close(); + } catch (ex) {} + } + } + + function done() { + running = false; + completed++; + process.nextTick(next); + } + + expected++; + queue.push(f); + + if (!running) { + next(); + } +} + +function UDPServerReply(replybuf, expbuf) { + var testName = current.name; + var timer = setTimeout(function() { + throw new Error(format('Timeout on UDP test (%s)', testName)); + }, 100); + UDPServer.once('message', function(buf, rinfo) { + clearTimeout(timer); + if (expbuf) + assert.deepEqual(buf, expbuf); + UDPServer.send(replybuf, 0, replybuf.length, rinfo.port, rinfo.address); + }); +} + +function TCPServerReply(replybuf, expbuf) { + var testName = current.name; + var timer = setTimeout(function() { + throw new Error(format('Timeout on TCP test (%s)', testName)); + }, 100); + function readN(s, n, cb) { + var r = s.read(n); + if (r === null) + return s.once('readable', readN.bind(null, s, n, cb)); + cb(r); + } + TCPServer.once('connection', function(s) { + readN(s, 2, function readLength(buf) { + readN(s, buf.readUInt16BE(0), function readBytes(buf) { + clearTimeout(timer); + if (expbuf) + assert.deepEqual(buf, expbuf); + var lenbuf = new Buffer(2); + lenbuf.writeUInt16BE(replybuf.length, 0); + s.write(lenbuf); + s.write(replybuf); + }); + }); + }); +} + +dns.setServers(['127.0.0.1']); +UDPServer.bind(53, '127.0.0.1', addTests); +TCPServer.listen(53, '127.0.0.1', addTests); + +function addTests() { + if (++listening < 2) + return; + + ['UDP', 'TCP'].forEach(function(connType) { + var isTCP = (connType === 'TCP'); + var serverReply = (isTCP ? TCPServerReply : UDPServerReply); + var opts = { + hints: 0, + flags: dns.FLAG_RESOLVE_NSONLY | dns.FLAG_RESOLVE_NOSEARCH | + (isTCP ? dns.FLAG_RESOLVE_USEVC : 0), + family: 4 + }; + + function customOpts(type) { + return { + hints: 0, + flags: dns.FLAG_RESOLVE_NSONLY | dns.FLAG_RESOLVE_NOSEARCH | + (isTCP ? dns.FLAG_RESOLVE_USEVC : 0), + type: type, + family: (type === 'AAAA' ? 6 : 4) + }; + } + + TEST(function(done) { + console.log('%s tests', connType); + console.log('========='); + process.nextTick(done); + }); + + + TEST(function test_shortheader(done) { + serverReply(new Buffer([ + 0x00 + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badid(done) { + serverReply(new Buffer([ + 0x00, 0x05, // ID + parseInt([ + '0', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badqr(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '0', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badopcode(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '1111', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badqnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x01, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x07, // RR name + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badansnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x07, // RR name + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badnsnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x01, // Authority records count + 0x00, 0x00, // Additional records count + 0x07, // RR name + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badaddlnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x01, // Additional records count + 0x07, // RR name + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badrrlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x01, // RR type + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badrrclass(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x01, // RR type + 0x00, 0x00, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x00, // RR rdata length + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badrrrdlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x01, // RR type + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x10, // RR rdata length + 0x0A, 0x01 + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badrrtype(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0xFF, 0xFF, // RR type + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x10, // RR rdata length + 0x0A, 0x01 + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badA_addrlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x01, // RR type (A) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x0A, 0x01, // ADDRESS + ])); + dns.lookup('example.org', opts, function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNS_namelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x02, // RR type (NS) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x0C, 0x61, // NAME + ])); + dns.lookup('example.org', customOpts('NS'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badCNAME_namelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x05, // RR type (CNAME) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x0C, 0x61, // NAME + ])); + dns.lookup('example.org', customOpts('NS'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_mnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x0C, 0x61, // NAME + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_rnamelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x03, // RR rdata length + 0x00, // MNAME + 0x0C, 0x61, // RNAME + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_seriallen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x03, // RR rdata length + 0x00, // MNAME + 0x00, // RNAME + 0x00, // SERIAL + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_refreshlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x07, // RR rdata length + 0x00, // MNAME + 0x00, // RNAME + 0x00, 0x00, 0x00, 0x00, // SERIAL + 0x00, // REFRESH + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_retrylen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x0B, // RR rdata length + 0x00, // MNAME + 0x00, // RNAME + 0x00, 0x00, 0x00, 0x00, // SERIAL + 0x00, 0x00, 0x00, 0x00, // REFRESH + 0x00, // RETRY + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_expirelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x0F, // RR rdata length + 0x00, // MNAME + 0x00, // RNAME + 0x00, 0x00, 0x00, 0x00, // SERIAL + 0x00, 0x00, 0x00, 0x00, // REFRESH + 0x00, 0x00, 0x00, 0x00, // RETRY + 0x00, // EXPIRE + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSOA_expirelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x06, // RR type (SOA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x13, // RR rdata length + 0x00, // MNAME + 0x00, // RNAME + 0x00, 0x00, 0x00, 0x00, // SERIAL + 0x00, 0x00, 0x00, 0x00, // REFRESH + 0x00, 0x00, 0x00, 0x00, // RETRY + 0x00, 0x00, 0x00, 0x00, // EXPIRE + 0x00, // MINIMUM + ])); + dns.lookup('example.org', customOpts('SOA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badPTR_namelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x0C, // RR type (PTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x04, // NAME + ])); + dns.lookup('8.8.8.8', customOpts('PTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badMX_preflen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x0F, // RR type (MX) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x00, // PREFERENCE + ])); + dns.lookup('example.org', customOpts('MX'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badMX_exchglen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x0F, // RR type (MX) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x03, // RR rdata length + 0x00, 0x01, // PREFERENCE + 0x07, // EXCHANGE + ])); + dns.lookup('example.org', customOpts('MX'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badTXT_stringlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x10, // RR type (TXT) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x05, // STRING + ])); + dns.lookup('example.org', customOpts('TXT'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badAAAA_addrlen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x1C, // RR type (AAAA) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0xFE, 0x80, // address + ])); + dns.lookup('example.org', customOpts('AAAA'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badLOC_len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x1D, // RR type (LOC) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x00, // VERSION + ])); + dns.lookup('example.org', customOpts('LOC'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badLOC_ver(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x1D, // RR type (LOC) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x10, // RR rdata length + 0x05, // VERSION + 0x00, // SIZE + 0x00, // HORIZONTAL PRECISION + 0x00, // VERTICAL PRECISION + 0x00, 0x00, 0x00, 0x00, // LATITUDE + 0x00, 0x00, 0x00, 0x00, // LONGITUDE + 0x00, 0x00, 0x00, 0x00, // ALTITUDE + ])); + dns.lookup('example.org', customOpts('LOC'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSRV_len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x21, // RR type (SRV) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x00, // PRIORITY + ])); + dns.lookup('example.org', customOpts('SRV'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSRV_namelen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x21, // RR type (SRV) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x07, // RR rdata length + 0x00, 0x01, // PRIORITY + 0x00, 0x01, // WEIGHT + 0x00, 0x16, // PORT + 0x07, // NAME + ])); + dns.lookup('example.org', customOpts('SRV'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNAPTR_len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x00, // ORDER + ])); + dns.lookup('example.org', customOpts('NAPTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNAPTR_flagslen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x05, // RR rdata length + 0x00, 0x01, // ORDER + 0x00, 0x01, // PREFERENCE + 0x07, // FLAGS STRING + ])); + dns.lookup('example.org', customOpts('NAPTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNAPTR_flagslen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x06, // RR rdata length + 0x00, 0x01, // ORDER + 0x00, 0x01, // PREFERENCE + 0x00, // FLAGS STRING + 0x07, // SERVICE STRING + ])); + dns.lookup('example.org', customOpts('NAPTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNAPTR_flagslen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x07, // RR rdata length + 0x00, 0x01, // ORDER + 0x00, 0x01, // PREFERENCE + 0x00, // FLAGS STRING + 0x00, // SERVICE STRING + 0x07, // REGEXP STRING + ])); + dns.lookup('example.org', customOpts('NAPTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badNAPTR_flagslen(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x08, // RR rdata length + 0x00, 0x01, // ORDER + 0x00, 0x01, // PREFERENCE + 0x00, // FLAGS STRING + 0x00, // SERVICE STRING + 0x00, // REGEXP STRING + 0x07, // REPLACEMENT STRING + ])); + dns.lookup('example.org', customOpts('NAPTR'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSSHFP_len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x01, // RR rdata length + 0x01, // ALGORITHM + ])); + dns.lookup('example.org', customOpts('SSHFP'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSSHFP_sha1len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x01, // ALGORITHM + 0x01, // FINGERPRINT TYPE (SHA-1) + ])); + dns.lookup('example.org', customOpts('SSHFP'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + + + TEST(function test_badSSHFP_sha256len(done) { + serverReply(new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '0', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x01, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + 0x00, // RR name + 0x00, 0x23, // RR type (NAPTR) + 0x00, 0x01, // RR class + 0x00, 0x00, 0x00, 0x00, // RR ttl + 0x00, 0x02, // RR rdata length + 0x01, // ALGORITHM + 0x02, // FINGERPRINT TYPE (SHA-256) + ])); + dns.lookup('example.org', customOpts('SSHFP'), function(err, rep) { + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + done(); + }); + }); + }); +} + + +process.on('exit', function() { + if (expected > 0) + console.log('%d/%d tests completed', completed, expected); + assert.equal(running, false); + assert.strictEqual(expected, completed); +}); diff --git a/test/internet/test-dns-truncated.js b/test/internet/test-dns-truncated.js new file mode 100644 index 00000000000000..41571305e85893 --- /dev/null +++ b/test/internet/test-dns-truncated.js @@ -0,0 +1,88 @@ +'use strict'; + +if (process.oldDNS) + process.exit(0); + +var assert = require('assert'), + dns = require('dns'), + dgram = require('dgram'), + net = require('net'); + +var UDPServer = dgram.createSocket('udp4'), + TCPServer = net.createServer(), + listening = 0, + results = []; + + +dns.setServers(['127.0.0.1']); +UDPServer.bind(53, '127.0.0.1', addTests); +TCPServer.listen(53, '127.0.0.1', addTests); + +function addTests() { + if (++listening < 2) + return; + + var replybuf = new Buffer([ + 0x00, 0x00, // ID + parseInt([ + '1', // QR + '0000', // OPCODE + '1', // AA + '1', // TC + '1', // RD + ].join(''), 2), + parseInt([ + '1', // RA + '000', // Z + '0000', // RCODE + ].join(''), 2), + 0x00, 0x00, // Question records count + 0x00, 0x00, // Answer records count + 0x00, 0x00, // Authority records count + 0x00, 0x00, // Additional records count + ]); + var opts = { + hints: 0, + flags: dns.FLAG_RESOLVE_NSONLY, + family: 4 + }; + + UDPServer.on('message', function(buf, rinfo) { + results.push('udp'); + UDPServer.send(replybuf, 0, replybuf.length, rinfo.port, rinfo.address); + }); + + function readN(s, n, cb) { + var r = s.read(n); + if (r === null) + return s.once('readable', readN.bind(null, s, n, cb)); + cb(r); + } + TCPServer.on('connection', function(s) { + results.push('tcp'); + readN(s, 2, function readLength(buf) { + readN(s, buf.readUInt16BE(0), function readBytes(buf) { + var lenbuf = new Buffer(2); + lenbuf.writeUInt16BE(replybuf.length, 0); + s.write(lenbuf); + s.write(replybuf); + }); + }); + }); + + var timer = setTimeout(function() { + throw new Error('Timeout'); + }, 100); + dns.lookup('example.org', opts, function(err, rep) { + clearTimeout(timer); + UDPServer.close(); + TCPServer.close(); + assert.ok(err && err.code === 'ENOTFOUND'); + assert.ok(rep === undefined); + }); +} + + +process.on('exit', function() { + assert.deepEqual(results, ['udp', 'tcp']); +}); diff --git a/test/internet/test-dns.js b/test/internet/test-dns.js index 2a423f97dee7b0..4b3ec6bedff8d3 100644 --- a/test/internet/test-dns.js +++ b/test/internet/test-dns.js @@ -40,7 +40,8 @@ function TEST(f) { function checkWrap(req) { - assert.ok(typeof req === 'object'); + if (process.oldDNS) + assert.ok(typeof req === 'object'); } @@ -130,6 +131,7 @@ TEST(function test_resolveSrv(done) { checkWrap(req); }); + TEST(function test_resolveNaptr(done) { var req = dns.resolveNaptr('sip2sip.info', function(err, result) { if (err) throw err; @@ -155,6 +157,7 @@ TEST(function test_resolveNaptr(done) { checkWrap(req); }); + TEST(function test_resolveSoa(done) { var req = dns.resolveSoa('nodejs.org', function(err, result) { if (err) throw err; @@ -189,6 +192,7 @@ TEST(function test_resolveSoa(done) { checkWrap(req); }); + TEST(function test_resolveCname(done) { var req = dns.resolveCname('www.microsoft.com', function(err, names) { if (err) throw err; @@ -221,6 +225,42 @@ TEST(function test_resolveTxt(done) { }); +TEST(function test_resolveLoc(done) { + if (!dns.resolveLoc) + return done(); + + dns.resolveLoc('statdns.net', function(err, records) { + if (err) throw err; + assert.equal(records.length, 1); + assert.deepEqual(records[0], { + size: 0, + horizPrec: 22, + vertPrec: 19, + latitude: 2336026648, + longitude: 2165095648, + altitude: 9999800 + }); + done(); + }); +}); + +TEST(function test_resolveSshp(done) { + if (!dns.resolveSshp) + return done(); + + dns.resolveSshp('statdns.net', function(err, records) { + if (err) throw err; + assert.equal(records.length, 1); + assert.deepEqual(records[0], { + algorithm: 'DSA', + fpType: 'SHA1', + fp: '123456789abcdef67890123456789abcdef67890' + }); + done(); + }); +}); + + TEST(function test_lookup_failure(done) { var req = dns.lookup('does.not.exist', 4, function(err, ip, family) { assert.ok(err instanceof Error); diff --git a/test/parallel/test-dns-lookup-cb-error.js b/test/parallel/test-dns-lookup-cb-error.js index 330dfb5d57092a..93ec302c68f1de 100644 --- a/test/parallel/test-dns-lookup-cb-error.js +++ b/test/parallel/test-dns-lookup-cb-error.js @@ -1,4 +1,8 @@ 'use strict'; + +if (!process.oldDNS) + process.exit(0); + var common = require('../common'); var assert = require('assert'); var cares = process.binding('cares_wrap'); From 935d2d01170bbb6d9f84d1546969ac749b9463fa Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 30 Sep 2015 00:07:59 -0400 Subject: [PATCH 02/39] lib,src: add nss module compatibility Before this, nss modules that the system resolver would ordinarily call out to would be ignored by the js dns implementation. This commit changes that and now tries non-dns/files nss modules to minimize thread pool usage. --- lib/internal/dns.js | 175 +++++++++++++- node.gyp | 4 + src/env.h | 2 + src/nss_module.cc | 218 +++++++++++++++++ src/nss_module.h | 66 ++++++ src/nss_wrap.cc | 560 ++++++++++++++++++++++++++++++++++++++++++++ src/nss_wrap.h | 84 +++++++ 7 files changed, 1097 insertions(+), 12 deletions(-) create mode 100644 src/nss_module.cc create mode 100644 src/nss_module.h create mode 100644 src/nss_wrap.cc create mode 100644 src/nss_wrap.h diff --git a/lib/internal/dns.js b/lib/internal/dns.js index d19941495c38c0..be5370fb52096e 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -30,13 +30,25 @@ const TCPSocket = require('net').Socket; const UDPSocket = require('dgram').Socket; + const isIP = require('net').isIP; // TODO: move implementation from cares_wrap +const NSSReqWrap = process.binding('nss_wrap').NSSReqWrap; +const NSSModule = process.binding('nss_module').NSSModule; + const cp = require('child_process'); const os = require('os'); const fs = require('fs'); const pathJoin = require('path').join; +const NSS_REQ_ERR_SUCCESS = 0; +const NSS_REQ_ERR_NOTFOUND = 1; +const NSS_REQ_ERR_UNAVAIL = 2; +const NSS_REQ_ERR_TRYAGAIN = 3; +const NSS_REQ_ERR_INTERNAL = 4; +const NSS_REQ_ERR_MALLOC = 5; const HOSTNAME = os.hostname(); +const REGEX_NSSWITCH_ACTION = + /^\[(!)?(success|notfound|unavail|tryagain)=(return|continue)\]$/i; const REGEX_ARRAY = /^\{?(.*)\}?$/; const REGEX_OPTS = /^(ndots|timeout|attempts|rotate|inet6|use-vc)(?::(\d{1,2}))?$/; @@ -510,6 +522,9 @@ var QTYPE_S2V = { // only once at startup. Typically this is "%SystemRoot%\system32\drivers\etc" var win32_dbPath; +// This stores nss module instances for nss modules other than 'dns' and 'files' +var nssModules = {}; + // The current DNS configuration. This is populated at startup but can be // modified by user-facing functions during runtime (e.g. setServers()) var dnsConfig = { @@ -914,6 +929,8 @@ function resolve(name, type, flags, cb) { var tcpBufLen = 0; var tcpLen = -0; var tcpSendLenBuf = null; + var nssReq; + var lastStatus; var ndots; var qtype; var qtypeName; @@ -986,6 +1003,9 @@ function resolve(name, type, flags, cb) { // Cleanup on socket errors or query timeouts function cleanup(err) { + // For nsswitch actions + lastStatus = NSS_REQ_ERR_UNAVAIL; + if (socket instanceof UDPSocket) socket.close(); else { @@ -1088,12 +1108,23 @@ function resolve(name, type, flags, cb) { if (rep === false) { // Parse or similar critical error + // For nsswitch actions + lastStatus = NSS_REQ_ERR_UNAVAIL; + // Reset some state searchIdx = 0; triedAsIs = false; } else { // We got an actual reply + // For nsswitch actions + if (rep.rcode === 'NOERROR') + lastStatus = NSS_REQ_ERR_SUCCESS; + else if (rep.rcode === 'SERVFAIL') + lastStatus = NSS_REQ_ERR_UNAVAIL; + else + lastStatus = NSS_REQ_ERR_NOTFOUND; + if (rep.rcode === 'NOTIMP' || rep.rcode === 'SERVFAIL' || rep.rcode === 'REFUSED' || (!rep.answers.length && !rep.truncated)) { if (!sawEmptyAnswers && rep.rcode !== 'NOTIMP' && @@ -1260,6 +1291,98 @@ function resolve(name, type, flags, cb) { nextLookup(false); } break; + default: + var m; + if (m = REGEX_NSSWITCH_ACTION.exec(lookupMethod)) { + // nsswitch action + var status = m[2].toLowerCase(); + var action = m[3].toLowerCase(); + var not = (m[1] === '!'); + if (/*(status === 'success' && + ((!not && lastStatus === NSS_REQ_ERR_SUCCESS) || + (not && lastStatus !== NSS_REQ_ERR_SUCCESS))) ||*/ + (status === 'notfound' && + ((!not && lastStatus === NSS_REQ_ERR_NOTFOUND) || + (not && lastStatus !== NSS_REQ_ERR_NOTFOUND))) || + (status === 'unavail' && + ((!not && lastStatus === NSS_REQ_ERR_UNAVAIL) || + (not && lastStatus !== NSS_REQ_ERR_UNAVAIL))) || + (status === 'tryagain' && + ((!not && lastStatus === NSS_REQ_ERR_TRYAGAIN) || + (not && lastStatus !== NSS_REQ_ERR_TRYAGAIN)))) { + if (action === 'return') { + if (lastStatus === NSS_REQ_ERR_NOTFOUND) + doCb(errnoException('ENOTFOUND', name), undefined, false); + else if (lastStatus === NSS_REQ_ERR_UNAVAIL) + doCb(errnoException('ESERVFAIL', name), undefined, false); + else if (lastStatus === NSS_REQ_ERR_TRYAGAIN) + doCb(errnoException('ESERVFAIL', name), undefined, false); + return; + } + } + ++lookupIdx; + nextLookup(false); + return; + } + // Some custom nss module, like 'mdns4' for example + if (reqIsPtr || qtype === QTYPE_A || qtype === QTYPE_AAAA) { + var nssModule = nssModules[lookupMethod]; + if (!nssModule || + (reqIsPtr && !nssModule.hasByAddr) || + (!reqIsPtr && !nssModule.hasByName)) { + ++lookupIdx; + nextLookup(false); + return; + } + if (!nssReq) { + if (reqIsPtr) + nssReq = new NSSReqWrap(name, 0); + else + nssReq = new NSSReqWrap(name, -1); + nssReq.oncomplete = function(status, results) { + lastStatus = status; + if (lastStatus === NSS_REQ_ERR_SUCCESS) { + if (reqIsPtr) { + results = [{ + name: name, + type: type, + ttl: 0, // TODO: fill in when available + data: results + }]; + } else { + for (var i = 0; i < results.length; ++i) { + results[i] = { + name: name, + type: type, + ttl: 0, // TODO: fill in when available + data: results[i] + }; + } + } + doCb(null, { + rcode: 'NOERROR', + authoritative: true, + truncated: false, + recurseAvail: true, + authenticated: false, + answers: results, + authorities: [], + additional: [] + }, false); + } else { + ++lookupIdx; + nextLookup(false); + } + }; + } + if (reqIsPtr) + nssModule.queryAddr(nssReq); + else + nssModule.queryName(nssReq, (qtype === QTYPE_A ? 4 : 6)); + } else { + ++lookupIdx; + nextLookup(false); + } } } } @@ -2363,7 +2486,7 @@ function parseResolvConf() { if (network !== undefined) val[j] = network; else { - val.splice(j, 1); + spliceOne(val, j); --j; --valLen; } @@ -2372,7 +2495,7 @@ function parseResolvConf() { ret.sortlist = val; break; case 'options': - fillIn(ret.options, parseOptions(val.split(' '))); + fillIn(ret.options, parseResolvOptions(val.split(' '))); break; case 'lookup': val = val.split(' '); @@ -2398,7 +2521,7 @@ function parseResolvConf() { return ret; } -function parseOptions(options) { +function parseResolvOptions(options) { var ret = { ndots: null, timeout: null, @@ -2451,6 +2574,7 @@ function parseOptions(options) { function parseNsswitchConf() { // TODO: search for explicitly defined services file path too var ret = false; + var seen = []; try { var data = fs.readFileSync('/etc/nsswitch.conf', 'utf8'); @@ -2459,16 +2583,20 @@ function parseNsswitchConf() { var order = m[1].replace(REGEX_COMMENT, '').trim().split(/[ \f\t\v,]+/g); for (var i = 0, loc; i < order.length; ++i) { loc = order[i].toLowerCase(); - if ((loc === 'dns' || loc === 'files') && ret.indexOf(loc) === -1) { - if (!ret) - ret = [loc]; - else - ret.push(loc); - } + if (seen.indexOf(loc) !== -1) + continue; + if (loc !== 'dns' && loc !== 'files') + loc = order[i]; + if (!ret) + ret = [loc]; + else + ret.push(loc); } } } catch (ex) {} + if (ret) + ret = { lookup: ret }; return ret; } @@ -2483,12 +2611,12 @@ function parseHostConf() { .trim().split(REGEX_WHITESPACE); for (var i = 0, loc; i < order.length; ++i) { loc = order[i].toLowerCase(); - if (loc === 'bind' && ret.indexOf('dns') === -1) { + if (loc === 'bind' && (!ret || ret.indexOf('dns') === -1)) { if (!ret) ret = ['dns']; else ret.push('dns'); - } else if (loc === 'hosts' && ret.indexOf('files') === -1) { + } else if (loc === 'hosts' && (!ret || ret.indexOf('files') === -1)) { if (!ret) ret = ['files']; else @@ -2498,6 +2626,9 @@ function parseHostConf() { } } catch (ex) {} + if (ret) + ret = { lookup: ret }; + return ret; } @@ -2594,7 +2725,7 @@ function execSync(cmd, opts) { dnsConfig.search = r; } if (process.env.RES_OPTIONS) - fillIn(dnsConfig.options, parseOptions(process.env.RES_OPTIONS)); + fillIn(dnsConfig.options, parseResolvOptions(process.env.RES_OPTIONS)); r = parseResolvConf(); if (r !== false) @@ -2616,6 +2747,26 @@ function execSync(cmd, opts) { // Set missing parameters to some sensible defaults fillIn(dnsConfig, DNS_DEFAULTS); + if (process.platform !== 'win32') { + // Load any non-dns/files nss modules in case we need to try them during a + // request + for (var i = 0; i < dnsConfig.lookup.length; ++i) { + var loc = dnsConfig.lookup[i]; + if (loc !== 'dns' && + loc !== 'files' && + !REGEX_NSSWITCH_ACTION.test(loc)) { + try { + nssModules[loc] = new NSSModule(loc); + } catch (ex) { + // If we get here, it either means the module couldn't be loaded or + // there were no host resolution functions exported by the module + spliceOne(dnsConfig.lookup, i); + --i; + } + } + } + } + // Reformat nameservers to cache IP type for (var i = 0; i < dnsConfig.nameserver.length; ++i) { dnsConfig.nameserver[i] = [ isIP(dnsConfig.nameserver[i]), diff --git a/node.gyp b/node.gyp index 0c0977de754a68..f23f7fd5ec638c 100644 --- a/node.gyp +++ b/node.gyp @@ -139,6 +139,8 @@ 'src/node_watchdog.cc', 'src/node_zlib.cc', 'src/node_i18n.cc', + 'src/nss_module.cc', + 'src/nss_wrap.cc', 'src/pipe_wrap.cc', 'src/signal_wrap.cc', 'src/spawn_sync.cc', @@ -173,6 +175,8 @@ 'src/node_watchdog.h', 'src/node_wrap.h', 'src/node_i18n.h', + 'src/nss_module.h', + 'src/nss_wrap.h', 'src/pipe_wrap.h', 'src/tty_wrap.h', 'src/tcp_wrap.h', diff --git a/src/env.h b/src/env.h index 743bf057e8584d..917399c8987203 100644 --- a/src/env.h +++ b/src/env.h @@ -106,6 +106,8 @@ namespace node { V(fsevent_string, "FSEvent") \ V(gid_string, "gid") \ V(handle_string, "handle") \ + V(hasbyaddr, "hasByAddr") \ + V(hasbyname, "hasByName") \ V(heap_total_string, "heapTotal") \ V(heap_used_string, "heapUsed") \ V(hostmaster_string, "hostmaster") \ diff --git a/src/nss_module.cc b/src/nss_module.cc new file mode 100644 index 00000000000000..9ab36dfc0ad659 --- /dev/null +++ b/src/nss_module.cc @@ -0,0 +1,218 @@ +#include "nss_module.h" + +namespace node { +namespace nss_module { + +using nss_wrap::NSSReqWrap; +using v8::Boolean; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Handle; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +NSSModule::NSSModule(Environment* env, + Local req_wrap_obj, + uv_lib_t* lib, + nss_gethostbyname3_r ghbn_3, + nss_gethostbyname4_r ghbn_4, + nss_gethostbyaddr2_r ghba_2) + : BaseObject(env, req_wrap_obj), + lib_(lib), + ghbn3(ghbn_3), + ghbn4(ghbn_4), + ghba2(ghba_2) { + Wrap(req_wrap_obj, this); +} + + +NSSModule::~NSSModule() { + if (lib_ != nullptr) { + uv_dlclose(lib_); + delete lib_; + } +} + + +// args: moduleName +void NSSModule::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + +#ifdef _WIN32 + return env->ThrowError("NSSModule is not available on Windows."); +#else + CHECK(args.IsConstructCall()); + CHECK(args[0]->IsString()); + + node::Utf8Value js_module_name(env->isolate(), args[0]); + const char* module_string = *js_module_name; + int module_len = strlen(module_string); + + // Use allocated memory for both the library filename and the actual + // function names. The latter is always longer, so we allocate that much + // here. + int name_len = 5 + module_len + 17 + 1; + char* name = static_cast(malloc(name_len)); + + sprintf(name, "libnss_%s.so.2", module_string); + + uv_lib_t* lib = static_cast(malloc(sizeof(uv_lib_t))); + if (lib == nullptr) + return env->ThrowError("malloc failed"); + + int status = uv_dlopen(name, lib); + if (status == -1) { + free(name); + const char* err_msg = uv_dlerror(lib); + return env->ThrowError(env->isolate(), err_msg); + } + + sprintf(name, "_nss_%s_gethostbyname3_r", module_string); + + nss_gethostbyname3_r ghbn3 = nullptr; + nss_gethostbyname4_r ghbn4 = nullptr; + nss_gethostbyaddr2_r ghba2 = nullptr; + + status = uv_dlsym(lib, + static_cast(name), + reinterpret_cast(&ghbn3)); + if (ghbn3 == nullptr) { + name[name_len - 4] = '2'; + status = uv_dlsym(lib, + static_cast(name), + reinterpret_cast(&ghbn3)); + // TODO: resort to gethostbyname_r which does AF_UNSPEC? + } + + // No need to obtain the parallel gethostbyname if we do not at least have the + // non-parallel version + if (ghbn3 != nullptr) { + // gethostbyname4_r sends out parallel A and AAAA queries and + // is thus only suitable for AF_UNSPEC. + name[name_len - 4] = '4'; + status = uv_dlsym(lib, + static_cast(name), + reinterpret_cast(&ghbn4)); + } + + memcpy(name + name_len - 8, "addr2", 5); + status = uv_dlsym(lib, + static_cast(name), + reinterpret_cast(&ghba2)); + + if (ghba2 == nullptr) { + // Try to get the non-ttl version + memcpy(name + name_len - 8, "addr_r\0", 7); + status = uv_dlsym(lib, + static_cast(name), + reinterpret_cast(&ghba2)); + } + + free(name); + + if (ghbn3 == nullptr && ghba2 == nullptr) { + uv_dlclose(lib); + delete lib; + return env->ThrowError("nss module missing needed gethostby*_r functions"); + } + + Local has_ghbn_v = Boolean::New(env->isolate(), ghbn3 != nullptr); + Local has_ghba_v = Boolean::New(env->isolate(), ghba2 != nullptr); + args.This()->ForceSet(env->hasbyname(), has_ghbn_v, v8::ReadOnly); + args.This()->ForceSet(env->hasbyaddr(), has_ghba_v, v8::ReadOnly); + args.This()->ForceSet(env->name_string(), args[0], v8::ReadOnly); + + new NSSModule(env, args.This(), lib, ghbn3, ghbn4, ghba2); +#endif +} + + +// args: req, [family] +void NSSModule::QueryName(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + NSSModule* nss = Unwrap(args.Holder()); + + if (nss->ghbn3 == nullptr && nss->ghbn4 == nullptr) + return args.GetReturnValue().Set(false); + + CHECK(args[0]->IsObject()); + + Local req_wrap_obj = args[0].As(); + + int family = (args[1]->IsInt32()) ? args[1]->Int32Value() : 0; + + if (family != 0 && family != 4 && family != 6) + return env->ThrowError("bad address family"); + + NSSReqWrap* req_wrap = Unwrap(req_wrap_obj); + + if (req_wrap->host_type != -1) + return env->ThrowError("wrong nss request type"); + + req_wrap->module = nss; + req_wrap->family = family; + + req_wrap->Ref(); + uv_queue_work(env->event_loop(), + &req_wrap->req_, + NSSReqWrap::NameWork, + NSSReqWrap::NameAfter); + req_wrap->Dispatched(); + + args.GetReturnValue().Set(true); +} + + +// args: req +void NSSModule::QueryAddr(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + NSSModule* nss = Unwrap(args.Holder()); + + if (nss->ghba2 == nullptr) + return args.GetReturnValue().Set(false); + + CHECK(args[0]->IsObject()); + + Local req_wrap_obj = args[0].As(); + + NSSReqWrap* req_wrap = Unwrap(req_wrap_obj); + + if (req_wrap->host_type != 4 && req_wrap->host_type != 6) + return env->ThrowError("wrong nss request type"); + + req_wrap->module = nss; + + req_wrap->Ref(); + uv_queue_work(env->event_loop(), + &req_wrap->req_, + NSSReqWrap::AddrWork, + NSSReqWrap::AddrAfter); + req_wrap->Dispatched(); + + args.GetReturnValue().Set(true); +} + + +void NSSModule::Initialize(Handle target, + Handle unused, + Handle context) { + Environment* env = Environment::GetCurrent(context); + + Local class_name = FIXED_ONE_BYTE_STRING(env->isolate(), "NSSModule"); + Local nssmodule_tmpl = + env->NewFunctionTemplate(NSSModule::New); + nssmodule_tmpl->InstanceTemplate()->SetInternalFieldCount(1); + nssmodule_tmpl->SetClassName(class_name); + env->SetProtoMethod(nssmodule_tmpl, "queryName", NSSModule::QueryName); + env->SetProtoMethod(nssmodule_tmpl, "queryAddr", NSSModule::QueryAddr); + target->Set(class_name, nssmodule_tmpl->GetFunction()); +} + +} +} + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(nss_module, + node::nss_module::NSSModule::Initialize) diff --git a/src/nss_module.h b/src/nss_module.h new file mode 100644 index 00000000000000..80fbc6e28b7ee2 --- /dev/null +++ b/src/nss_module.h @@ -0,0 +1,66 @@ +#ifndef SRC_NSS_MODULE_H_ +#define SRC_NSS_MODULE_H_ + +#include "env.h" +#include "env-inl.h" +#include "node.h" +#include "base-object.h" +#include "base-object-inl.h" +#include "nss_wrap.h" + +#include + +namespace node { +namespace nss_module { + +typedef enum nss_status (*nss_gethostbyname4_r) + (const char* name, struct gaih_addrtuple** pat, + char* buffer, size_t buflen, int* errnop, + int* h_errnop, int32_t* ttlp); + +typedef enum nss_status (*nss_gethostbyname3_r) + (const char* name, int af, struct hostent* host, + char* buffer, size_t buflen, int* errnop, + int* h_errnop, int32_t* ttlp, char** canonname); + +typedef enum nss_status (*nss_gethostbyaddr2_r) + (const void* addr, socklen_t len, int af, + struct hostent* host, char* buffer, size_t buflen, + int* errnop, int* h_errnop, int32_t* ttlp); + +class NSSModule : public BaseObject { + private: + uv_lib_t* lib_; + + public: + nss_gethostbyname3_r ghbn3; + nss_gethostbyname4_r ghbn4; + nss_gethostbyaddr2_r ghba2; + + NSSModule(Environment* env, + v8::Local req_wrap_obj, + uv_lib_t* lib, + nss_gethostbyname3_r ghbn_3, + nss_gethostbyname4_r ghbn_4, + nss_gethostbyaddr2_r ghba_2); + + ~NSSModule() override; + + // args: moduleName + static void New(const v8::FunctionCallbackInfo& args); + + // args: req, [family] + static void QueryName(const v8::FunctionCallbackInfo& args); + + // args: req + static void QueryAddr(const v8::FunctionCallbackInfo& args); + + static void Initialize(v8::Handle target, + v8::Handle unused, + v8::Handle context); +}; + +} +} + +#endif // SRC_NSS_MODULE_H_ diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc new file mode 100644 index 00000000000000..cbce44845648d3 --- /dev/null +++ b/src/nss_wrap.cc @@ -0,0 +1,560 @@ +#include "nss_wrap.h" + +namespace node { +namespace nss_wrap { + +using nss_module::nss_gethostbyname3_r; +using nss_module::nss_gethostbyname4_r; +using nss_module::nss_gethostbyaddr2_r; +using v8::Array; +using v8::Context; +using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; +using v8::Handle; +using v8::HandleScope; +using v8::Integer; +using v8::Local; +using v8::Object; +using v8::String; +using v8::Value; + +NSSReqWrap::NSSReqWrap(Environment* env, + Local req_wrap_obj, + char* host_val, + int8_t host_type_val) + : ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_NSSREQWRAP), + refs_(0), + host(host_val), + host_type(host_type_val) { + Wrap(req_wrap_obj, this); +} + + +NSSReqWrap::~NSSReqWrap() { + free(host); +} + + +void NSSReqWrap::Ref() { + if (++refs_ == 1) { + ClearWeak(); + } +} + + +void NSSReqWrap::Unref() { + CHECK_GT(refs_, 0); + if (--refs_ == 0) { + MakeWeak(this); + } +} + + +size_t NSSReqWrap::self_size() const { + return sizeof(*this); +} + + +void NSSReqWrap::NameWork(uv_work_t* req) { + NSSReqWrap* req_wrap = static_cast(req->data); + + nss_gethostbyname3_r ghbn3 = req_wrap->module->ghbn3; + nss_gethostbyname4_r ghbn4 = req_wrap->module->ghbn4; + const char* hostname = req_wrap->host; + uint8_t orig_family = req_wrap->family; + + // TODO: allow configurable ttl? + int32_t ttl = INT32_MAX; + int err; + int rc6; + int rc4; + int status[2] = { NSS_STATUS_UNAVAIL, NSS_STATUS_UNAVAIL }; + int naddrs = 0; + size_t addrslen = 0; + size_t tmpbuf6len = 1024; + char* tmpbuf6 = nullptr; + size_t tmpbuf4len = 0; + char* tmpbuf4 = nullptr; + char* results; + + if (ghbn4 != nullptr && orig_family == 0) { + struct gaih_addrtuple atmem; + struct gaih_addrtuple* at; + + tmpbuf6 = static_cast(malloc(tmpbuf6len)); + if (tmpbuf6 == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + + while (true) { + at = &atmem; + rc6 = 0; + err = 0; + status[0] = ghbn4(hostname, &at, tmpbuf6, tmpbuf6len, &rc6, &err, &ttl); + if (rc6 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) + break; + tmpbuf6len *= 2; + tmpbuf6 = static_cast(realloc(tmpbuf6, tmpbuf6len)); + if (tmpbuf6 == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + if (rc6 != 0 && err == NETDB_INTERNAL) { + free(tmpbuf6); + if (status[0] == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status[0] == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + return; + } + + if (status[0] == NSS_STATUS_SUCCESS) { + for (const struct gaih_addrtuple *at2 = at = &atmem; + at2 != nullptr; + at2 = at2->next) { + ++naddrs; + if (at2->family == AF_INET) + addrslen += INADDRSZ; + else if (at2->family == AF_INET6) + addrslen += IN6ADDRSZ; + } + } + + if (naddrs == 0) { + free(tmpbuf6); + if (status[0] == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status[0] == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + req_wrap->nresults = 0; + req_wrap->results = nullptr; + return; + } + + // Store addresses and families together + results = static_cast(malloc(addrslen + naddrs)); + if (results == nullptr) { + free(tmpbuf6); + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + + char* addrs = results; + char* family = addrs + addrslen; + + req_wrap->error = NSS_REQ_ERR_SUCCESS; + req_wrap->nresults = naddrs; + req_wrap->results = results; + req_wrap->families = family; + + for (const struct gaih_addrtuple *at2 = at; + at2 != NULL; + at2 = at2->next) { + *family++ = at2->family; + if (at2->family == AF_INET) + addrs = static_cast(mempcpy(addrs, at2->addr, INADDRSZ)); + else if (at2->family == AF_INET6) + addrs = static_cast(mempcpy(addrs, at2->addr, IN6ADDRSZ)); + } + + free(tmpbuf6); + } else if (ghbn3 != nullptr) { + struct hostent th[2]; + + if (orig_family == 0 || orig_family == 6) { + // Get IPv6 addresses + + tmpbuf6 = static_cast(malloc(tmpbuf6len)); + if (tmpbuf6 == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + + while (true) { + rc6 = 0; + status[0] = ghbn3(hostname, AF_INET6, &th[0], tmpbuf6, tmpbuf6len, + &rc6, &err, &ttl, nullptr); + if (rc6 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) + break; + tmpbuf6len *= 2; + tmpbuf6 = static_cast(realloc(tmpbuf6, tmpbuf6len)); + if (tmpbuf6 == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + if (rc6 != 0 && err == NETDB_INTERNAL) { + free(tmpbuf6); + if (status[0] == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status[0] == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + return; + } + + if (status[0] != NSS_STATUS_SUCCESS || rc6 != 0) { + tmpbuf4len = tmpbuf6len; + tmpbuf4 = tmpbuf6; + tmpbuf6len = 0; + tmpbuf6 = nullptr; + } + } + + if (orig_family == 0 || orig_family == 4) { + // Get IPv4 addresses + + if (tmpbuf4 == nullptr) { + tmpbuf4len = 512; + tmpbuf4 = static_cast(malloc(tmpbuf4len)); + if (tmpbuf4 == nullptr) { + if (orig_family == 0 && tmpbuf6 != nullptr) + free(tmpbuf6); + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + while (true) { + rc4 = 0; + status[1] = ghbn3(hostname, AF_INET, &th[1], tmpbuf4, tmpbuf4len, + &rc4, &err, &ttl, nullptr); + if (rc4 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) + break; + tmpbuf4len *= 2; + tmpbuf4 = static_cast(realloc(tmpbuf4, tmpbuf4len)); + if (tmpbuf4 == nullptr) { + if (orig_family == 0 && tmpbuf6 != nullptr) + free(tmpbuf6); + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + if (rc4 != 0 && err == NETDB_INTERNAL) { + if (orig_family == 0 && tmpbuf6 != nullptr) + free(tmpbuf6); + free(tmpbuf4); + if (status[0] == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status[0] == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + return; + } + } + + for (int i = 0; i < 2; ++i) { + if (status[i] == NSS_STATUS_SUCCESS) { + for (int j = 0; th[i].h_addr_list[j] != nullptr; ++j) { + ++naddrs; + addrslen += th[i].h_length; + } + } + } + + if (naddrs == 0) { + if (orig_family == 6 || (orig_family == 0 && tmpbuf6 != nullptr)) + free(tmpbuf6); + if (tmpbuf4 != nullptr) + free(tmpbuf4); + if (status[0] == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status[0] == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + req_wrap->nresults = 0; + req_wrap->results = nullptr; + return; + } + + if (orig_family == 0) { + // Store addresses and families together + results = static_cast(malloc(addrslen + naddrs)); + if (results == nullptr) { + if (tmpbuf6 != nullptr) + free(tmpbuf6); + if (tmpbuf4 != nullptr) + free(tmpbuf4); + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } else { + results = static_cast(malloc(addrslen)); + if (results == nullptr) { + if (orig_family == 6 && tmpbuf6 != nullptr) + free(tmpbuf6); + else if (tmpbuf4 != nullptr) + free(tmpbuf4); + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + char* addrs = results; + char* family = addrs + addrslen; + + req_wrap->error = NSS_REQ_ERR_SUCCESS; + req_wrap->nresults = naddrs; + req_wrap->results = results; + if (orig_family == 0) + req_wrap->families = family; + + for (int i = 0; i < 2; ++i) { + if (status[i] == NSS_STATUS_SUCCESS) { + for (int j = 0; th[i].h_addr_list[j] != NULL; ++j) { + if (orig_family == 0) + *family++ = th[i].h_addrtype; + addrs = static_cast(mempcpy(addrs, th[i].h_addr_list[j], + th[i].h_length)); + } + } + } + + if (orig_family == 6 || (orig_family == 0 && tmpbuf6 != nullptr)) + free(tmpbuf6); + if (tmpbuf4 != nullptr) + free(tmpbuf4); + } +} + + +void NSSReqWrap::NameAfter(uv_work_t* req, int status) { + CHECK_EQ(status, 0); + + NSSReqWrap* req_wrap = static_cast(req->data); + Environment* env = req_wrap->env(); + + HandleScope handle_scope(env->isolate()); + + req_wrap->Unref(); + + int naddrs; + if (req_wrap->error == NSS_REQ_ERR_SUCCESS) + naddrs = req_wrap->nresults; + else + naddrs = 0; + + Local results = Array::New(env->isolate()); + //results = Array::New(env->isolate(), naddrs); + + if (naddrs > 0) { + char* cur_addr; + char* cur_family; + char ip[INET6_ADDRSTRLEN]; + + if (req_wrap->family == 0) { + cur_addr = req_wrap->results; + cur_family = req_wrap->families; + int f; + for (int i = 0, n = 0; + i < naddrs; + ++i, ++cur_family, cur_addr += (f == AF_INET ? 4 : 16)) { + f = *cur_family; + int err = uv_inet_ntop(f, + cur_addr, + ip, + INET6_ADDRSTRLEN); + if (!err) { + Local s = OneByteString(env->isolate(), ip); + results->Set(n, s); + n++; + } + } + } else { + cur_addr = req_wrap->results; + for (int i = 0, n = 0; i < naddrs; ++i) { + int err = uv_inet_ntop(req_wrap->family, + cur_addr, + ip, + INET6_ADDRSTRLEN); + if (!err) { + Local s = OneByteString(env->isolate(), ip); + results->Set(n, s); + n++; + } + if (req_wrap->family == 4) + cur_addr += 4; + else + cur_addr += 16; + } + } + + free(req_wrap->results); + } + + Local argv[2] = { + Integer::New(env->isolate(), req_wrap->error), + results + }; + + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); +} + + +void NSSReqWrap::AddrWork(uv_work_t* req) { + NSSReqWrap* req_wrap = static_cast(req->data); + + nss_gethostbyaddr2_r ghba2 = req_wrap->module->ghba2; + int family = (req_wrap->host_type == 4 ? AF_INET : AF_INET6); + const char* addr = req_wrap->host; + socklen_t addr_len = (family == AF_INET + ? sizeof(struct in_addr) + : sizeof(struct in6_addr)); + + // TODO: allow configurable ttl? + int32_t ttl = INT32_MAX; + int err; + int rc; + int status; + size_t tmpbuflen = 1024; + char* tmpbuf; + struct hostent result; + + if (ghba2 != nullptr) { + tmpbuf = static_cast(malloc(tmpbuflen)); + if (tmpbuf == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + + while (true) { + rc = 0; + status = ghba2(addr, addr_len, family, &result, tmpbuf, tmpbuflen, &rc, + &err, &ttl); + if (rc != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) + break; + tmpbuflen *= 2; + tmpbuf = static_cast(realloc(tmpbuf, tmpbuflen)); + if (tmpbuf == nullptr) { + req_wrap->error = NSS_REQ_ERR_MALLOC; + return; + } + } + + if ((rc != 0 && err == NETDB_INTERNAL) || status != NSS_STATUS_SUCCESS) { + free(tmpbuf); + if (status == NSS_STATUS_NOTFOUND) + req_wrap->error = NSS_REQ_ERR_NOTFOUND; + else if (status == NSS_STATUS_UNAVAIL) + req_wrap->error = NSS_REQ_ERR_UNAVAIL; + else + req_wrap->error = NSS_REQ_ERR_SUCCESS; + return; + } + + if (result.h_name == nullptr) + req_wrap->results = nullptr; + else { + uint32_t name_len = strlen(result.h_name); + req_wrap->error = NSS_REQ_ERR_SUCCESS; + req_wrap->results = static_cast(malloc(name_len + 1)); + strcpy(req_wrap->results, result.h_name); + } + free(tmpbuf); + } +} + + +void NSSReqWrap::AddrAfter(uv_work_t* req, int status) { + CHECK_EQ(status, 0); + + NSSReqWrap* req_wrap = static_cast(req->data); + Environment* env = req_wrap->env(); + + HandleScope handle_scope(env->isolate()); + + req_wrap->Unref(); + + Local result; + if (req_wrap->error == NSS_REQ_ERR_SUCCESS) { + result = v8::String::NewFromUtf8(env->isolate(), req_wrap->results); + free(req_wrap->results); + } else + result = v8::Null(env->isolate()); + + Local argv[2] = { + Integer::New(env->isolate(), req_wrap->error), + result + }; + + req_wrap->MakeCallback(env->oncomplete_string(), ARRAY_SIZE(argv), argv); +} + + +// args: req, hostType +void NSSReqWrap::New(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + + CHECK(args.IsConstructCall()); + CHECK(args[0]->IsString()); + CHECK(args[1]->IsInt32()); + + node::Utf8Value hostname_v(env->isolate(), args[0]); + char* tmp_host; + int8_t host_type = static_cast(args[1]->Int32Value()); + + if (host_type == -1) { + // hostname + tmp_host = strdup(*hostname_v); + if (tmp_host == nullptr) + return env->ThrowError("malloc failed"); + } else if (host_type == 0 || host_type == 4 || host_type == 6) { + char addr_buf4[sizeof(struct in_addr)]; + char addr_buf6[sizeof(struct in6_addr)]; + int detected_family = 0; + if (uv_inet_pton(AF_INET, *hostname_v, &addr_buf4) == 0) + detected_family = 4; + else if (uv_inet_pton(AF_INET6, *hostname_v, &addr_buf6) == 0) + detected_family = 6; + + if (detected_family == 0) + return env->ThrowError("malformed IP address"); + else if (host_type != 0 && host_type != detected_family) + return env->ThrowError("IP address type mismatch"); + + host_type = detected_family; + if (host_type == 4) { + tmp_host = static_cast(malloc(sizeof(struct in_addr))); + memcpy(tmp_host, addr_buf4, sizeof(struct in_addr)); + } else { + tmp_host = static_cast(malloc(sizeof(struct in6_addr))); + memcpy(tmp_host, addr_buf6, sizeof(struct in6_addr)); + } + } else { + CHECK(0 && "bad host type"); + abort(); + } + + new NSSReqWrap(env, args.This(), tmp_host, host_type); +} + +void NSSReqWrap::Initialize(Handle target, + Handle unused, + Handle context) { + Environment* env = Environment::GetCurrent(context); + + Local class_name = + FIXED_ONE_BYTE_STRING(env->isolate(), "NSSReqWrap"); + Local nw = env->NewFunctionTemplate(NSSReqWrap::New); + nw->InstanceTemplate()->SetInternalFieldCount(1); + nw->SetClassName(class_name); + target->Set(class_name, nw->GetFunction()); +} + +} +} + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(nss_wrap, + node::nss_wrap::NSSReqWrap::Initialize) diff --git a/src/nss_wrap.h b/src/nss_wrap.h new file mode 100644 index 00000000000000..d9c8749f908ae1 --- /dev/null +++ b/src/nss_wrap.h @@ -0,0 +1,84 @@ +#ifndef SRC_NSS_WRAP_H_ +#define SRC_NSS_WRAP_H_ + +#include "env.h" +#include "env-inl.h" +#include "node.h" +#include "req-wrap.h" +#include "req-wrap-inl.h" +#include "async-wrap.h" +#include "nss_module.h" + +#include + +#if defined(__ANDROID__) || \ + defined(__MINGW32__) || \ + defined(__OpenBSD__) || \ + defined(_MSC_VER) +# include +#else +# include +#endif + +namespace node { + +// Forward declaration +namespace nss_module { class NSSModule; } + +namespace nss_wrap { + +#define NSS_REQ_ERR_SUCCESS 0 +#define NSS_REQ_ERR_NOTFOUND 1 +#define NSS_REQ_ERR_UNAVAIL 2 +#define NSS_REQ_ERR_TRYAGAIN 3 +#define NSS_REQ_ERR_INTERNAL 4 +#define NSS_REQ_ERR_MALLOC 5 + +class NSSReqWrap : public ReqWrap { + private: + unsigned int refs_; + + public: + char* host; + int8_t host_type; + + nss_module::NSSModule* module; + uint8_t family; + + int nresults; + char* results; + char* families; + int error; + + NSSReqWrap(Environment* env, + v8::Local req_wrap_obj, + char* host_val, + int8_t host_type_val); + + ~NSSReqWrap(); + + void Ref(); + + void Unref(); + + size_t self_size() const override; + + static void NameWork(uv_work_t* req); + + static void NameAfter(uv_work_t* req, int status); + + static void AddrWork(uv_work_t* req); + + static void AddrAfter(uv_work_t* req, int status); + + static void New(const v8::FunctionCallbackInfo& args); + + static void Initialize(v8::Handle target, + v8::Handle unused, + v8::Handle context); +}; + +} +} + +#endif // SRC_NSS_WRAP_H_ From 8bf3a74f50004e7fb27bccdac6318e7edc82c666 Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 30 Sep 2015 20:42:52 -0400 Subject: [PATCH 03/39] dns: add inet6 option compatibility for js dns resolver --- lib/internal/dns.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index be5370fb52096e..76a84940990f04 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -80,7 +80,7 @@ const DNS_DEFAULTS = { timeout: 5000, // ms attempts: 4, // how many tries before giving up rotate: false, // round-robin nameservers instead of always trying first one - inet6: false, // query AAAA before A + inet6: false, // query AAAA before A and transform IPv4 to IPv4-mapped IPv6 use_vc: false // force TCP for all DNS queries }, lookup: [ 'files', 'dns' ] @@ -641,7 +641,8 @@ exports.lookup = function lookup(hostname, options, callback) { family = options >>> 0; } - var v4mapped = (hints & V4MAPPED) > 0; + var inet6 = dnsConfig.options.inet6; + var v4mapped = ((hints & V4MAPPED) > 0 || inet6); var addrconfig = (hints & ADDRCONFIG) > 0; var addrtypes = FLAG_HAS_IPv4_IPv6; @@ -715,13 +716,19 @@ exports.lookup = function lookup(hostname, options, callback) { var answers = rep.answers; if (answers.length === 0) { + if (inet6 && family !== 4 && !triedIPv4) { + triedIPv4 = true; + return resolve(hostname, 'A', 0, resolvecb); + } // Emulate old DNS behavior of sending an Error on "empty" responses // instead of sending an empty array like with dns.resolve*() return resolvecb(errnoException('ENOTFOUND', hostname)); } if (!isCustomType) { - var needv4Mapped = (v4mapped && family === 6 && triedIPv4); + var needv4Mapped = (v4mapped && + (family === 6 || (family === 0 && inet6)) && + triedIPv4); var addr; if (family === 0) family = (triedIPv4 ? 4 : 6); From 1319ccf9c068bdb20d6fcd3776c643fa867faccb Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 30 Sep 2015 20:43:24 -0400 Subject: [PATCH 04/39] dns: add rotate option compatibility for js dns resolver --- lib/internal/dns.js | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 76a84940990f04..982c82a620ffb1 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -525,6 +525,10 @@ var win32_dbPath; // This stores nss module instances for nss modules other than 'dns' and 'files' var nssModules = {}; +// This saves the current index into the dns server list for when the `rotate` +// config option is enabled +var srvRotateIdx = -1; + // The current DNS configuration. This is populated at startup but can be // modified by user-facing functions during runtime (e.g. setServers()) var dnsConfig = { @@ -915,7 +919,9 @@ exports.FLAG_RESOLVE_USEVC = FLAG_RESOLVE_USEVC; // The main resolver implementation function resolve(name, type, flags, cb) { - var nameservers = dnsConfig.nameserver; + var rotate = dnsConfig.options.rotate; + var nameservers = (!rotate ? dnsConfig.nameserver : null); + var nsCount = 0; var nsIdx = -1; var TCPOnly = (dnsConfig.options.use_vc || (flags & FLAG_RESOLVE_USEVC) > 0); var attempts = dnsConfig.options.attempts; @@ -936,6 +942,7 @@ function resolve(name, type, flags, cb) { var tcpBufLen = 0; var tcpLen = -0; var tcpSendLenBuf = null; + var nameserver; var nssReq; var lastStatus; var ndots; @@ -1025,7 +1032,7 @@ function resolve(name, type, flags, cb) { } function udpMsgHandler(msg, rinfo) { - if (rinfo.address !== nameservers[nsIdx][1] || rinfo.port !== 53) + if (rinfo.address !== nameserver[1] || rinfo.port !== 53) return; clearTimeout(timer); socket.removeListener('message', udpMsgHandler); @@ -1037,7 +1044,7 @@ function resolve(name, type, flags, cb) { } function udpListening() { - var ns = nameservers[nsIdx]; + var ns = nameserver; timer = setTimeout(cleanup, timeout); socket.send(reqBuf, 0, reqBuf.length, 53, ns[1]); } @@ -1237,13 +1244,22 @@ function resolve(name, type, flags, cb) { case 'dns': if (!retry) { // Advance to the next nameserver - ++nsIdx; + if (rotate) { + if (nsCount++ >= dnsConfig.nameserver.length) + nameserver = null; + else { + srvRotateIdx = (srvRotateIdx + 1) % dnsConfig.nameserver.length; + nameserver = dnsConfig.nameserver[srvRotateIdx]; + } + } else + nameserver = nameservers[++nsIdx]; + triedTCP = false; if (TCPOnly) retryingOnTCP = true; } - var ns = nameservers[nsIdx]; - if (!ns) { + + if (!nameserver) { // No more nameservers left, try next lookup method ++lookupIdx; return nextLookup(false); @@ -1259,9 +1275,9 @@ function resolve(name, type, flags, cb) { socket.on('data', tcpOnData); socket.on('error', cleanup); socket.on('close', tcpOnClose); - socket.connect(53, ns[1]); + socket.connect(53, nameserver[1]); } else { - socket = new UDPSocket(ns[0] === 4 ? 'udp4' : 'udp6'); + socket = new UDPSocket(nameserver[0] === 4 ? 'udp4' : 'udp6'); socket.on('listening', udpListening); socket.on('message', udpMsgHandler); socket.on('error', cleanup); From 424de9fc73f631b678f2fe333bdc99559d36b157 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 1 Oct 2015 11:40:57 -0400 Subject: [PATCH 05/39] src: fix string constant names --- src/env.h | 4 ++-- src/nss_module.cc | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/env.h b/src/env.h index 917399c8987203..02532374a4cb6d 100644 --- a/src/env.h +++ b/src/env.h @@ -106,8 +106,8 @@ namespace node { V(fsevent_string, "FSEvent") \ V(gid_string, "gid") \ V(handle_string, "handle") \ - V(hasbyaddr, "hasByAddr") \ - V(hasbyname, "hasByName") \ + V(hasbyaddr_string, "hasByAddr") \ + V(hasbyname_string, "hasByName") \ V(heap_total_string, "heapTotal") \ V(heap_used_string, "heapUsed") \ V(hostmaster_string, "hostmaster") \ diff --git a/src/nss_module.cc b/src/nss_module.cc index 9ab36dfc0ad659..710f73ad40632c 100644 --- a/src/nss_module.cc +++ b/src/nss_module.cc @@ -121,8 +121,8 @@ void NSSModule::New(const FunctionCallbackInfo& args) { Local has_ghbn_v = Boolean::New(env->isolate(), ghbn3 != nullptr); Local has_ghba_v = Boolean::New(env->isolate(), ghba2 != nullptr); - args.This()->ForceSet(env->hasbyname(), has_ghbn_v, v8::ReadOnly); - args.This()->ForceSet(env->hasbyaddr(), has_ghba_v, v8::ReadOnly); + args.This()->ForceSet(env->hasbyname_string(), has_ghbn_v, v8::ReadOnly); + args.This()->ForceSet(env->hasbyaddr_string(), has_ghba_v, v8::ReadOnly); args.This()->ForceSet(env->name_string(), args[0], v8::ReadOnly); new NSSModule(env, args.This(), lib, ghbn3, ghbn4, ghba2); From ece6528a595c2e11973c8a3a8a50d375dbc24979 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 1 Oct 2015 16:28:15 -0400 Subject: [PATCH 06/39] src: add missing AsyncWrap provider --- src/async-wrap.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/async-wrap.h b/src/async-wrap.h index 5db29600bcd180..faee7757b7c93c 100644 --- a/src/async-wrap.h +++ b/src/async-wrap.h @@ -18,6 +18,7 @@ namespace node { V(GETADDRINFOREQWRAP) \ V(GETNAMEINFOREQWRAP) \ V(JSSTREAM) \ + V(NSSREQWRAP) \ V(PIPEWRAP) \ V(PIPECONNECTWRAP) \ V(PROCESSWRAP) \ From 744ec42a988e012c66ab8eaaed515ad2e9819a16 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 2 Oct 2015 01:09:22 -0400 Subject: [PATCH 07/39] dns: style changes --- lib/internal/dns.js | 57 ++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 982c82a620ffb1..bb250370f0d2e4 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -76,11 +76,12 @@ const DNS_DEFAULTS = { ? [ HOSTNAME.slice(HOSTNAME.indexOf('.') + 1) ] : null), sortlist: null, options: { + attempts: 2, // how many tries before giving up + inet6: false, // query AAAA before A and transform IPv4 to IPv4-mapped IPv6 ndots: 1, - timeout: 5000, // ms - attempts: 4, // how many tries before giving up rotate: false, // round-robin nameservers instead of always trying first one - inet6: false, // query AAAA before A and transform IPv4 to IPv4-mapped IPv6 + singleRequest: false, // perform IPv4 and IPv6 lookups sequentially? + timeout: 5000, // ms use_vc: false // force TCP for all DNS queries }, lookup: [ 'files', 'dns' ] @@ -536,11 +537,12 @@ var dnsConfig = { search: null, sortlist: null, options: { - ndots: null, - timeout: null, attempts: null, - rotate: null, inet6: null, + ndots: null, + rotate: null, + singleRequest: null, + timeout: null, use_vc: null }, lookup: null @@ -923,9 +925,9 @@ function resolve(name, type, flags, cb) { var nameservers = (!rotate ? dnsConfig.nameserver : null); var nsCount = 0; var nsIdx = -1; - var TCPOnly = (dnsConfig.options.use_vc || (flags & FLAG_RESOLVE_USEVC) > 0); + var TCPOnly = (dnsConfig.options.use_vc || (flags & FLAG_RESOLVE_USEVC)); var attempts = dnsConfig.options.attempts; - var lookups = (flags & FLAG_RESOLVE_NSONLY ? LOOKUPNS : dnsConfig.lookup); + var lookups = ((flags & FLAG_RESOLVE_NSONLY) ? LOOKUPNS : dnsConfig.lookup); var lookupIdx = 0; var timeout = dnsConfig.options.timeout; var search = dnsConfig.search; @@ -2504,14 +2506,13 @@ function parseResolvConf() { val = val.split(' '); if (val.length > 10) val = val.slice(0, 10); - for (var j = 0, valLen = val.length; j < valLen; ++j) { + for (var j = 0; j < val.length; ++j) { var network = parseAddrMask(val[j], false); if (network !== undefined) val[j] = network; else { spliceOne(val, j); --j; - --valLen; } } if (val.length) @@ -2546,11 +2547,12 @@ function parseResolvConf() { function parseResolvOptions(options) { var ret = { - ndots: null, - timeout: null, attempts: null, - rotate: null, inet6: null, + ndots: null, + rotate: null, + singleRequest: null, + timeout: null, use_vc: null }; for (var j = 0; j < options.length; ++j) { @@ -2558,6 +2560,16 @@ function parseResolvOptions(options) { if (m) { val = m[2]; switch (m[1]) { + case 'attempts': + if (val) { + val = parseInt(val, 10); + if (val <= 5) + ret.attempts = val; + } + break; + case 'inet6': + ret.inet6 = true; + break; case 'ndots': if (val) { val = parseInt(val, 10); @@ -2565,6 +2577,12 @@ function parseResolvOptions(options) { ret.ndots = val; } break; + case 'rotate': + ret.rotate = true; + break; + case 'single-request': + ret.singleRequest = true; + break; case 'timeout': if (val) { val = parseInt(val, 10); @@ -2572,19 +2590,6 @@ function parseResolvOptions(options) { ret.timeout = val; } break; - case 'attempts': - if (val) { - val = parseInt(val, 10); - if (val <= 5) - ret.attempts = val; - } - break; - case 'rotate': - ret.rotate = true; - break; - case 'inet6': - ret.inet6 = true; - break; case 'use-vc': ret.use_vc = true; break; From e1b05b96de0af52cade37d41d9d0bf53799b6f70 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 2 Oct 2015 01:12:21 -0400 Subject: [PATCH 08/39] dns: fix nsswitch status action parsing There can be multiple status=action pairs between a single pair of brackets. --- lib/internal/dns.js | 78 ++++++++++++++++++++++++++------------------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index bb250370f0d2e4..546c93b1b9531d 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -48,7 +48,7 @@ const NSS_REQ_ERR_INTERNAL = 4; const NSS_REQ_ERR_MALLOC = 5; const HOSTNAME = os.hostname(); const REGEX_NSSWITCH_ACTION = - /^\[(!)?(success|notfound|unavail|tryagain)=(return|continue)\]$/i; + /^(!)?(success|notfound|unavail|tryagain)=(return|continue)$/i; const REGEX_ARRAY = /^\{?(.*)\}?$/; const REGEX_OPTS = /^(ndots|timeout|attempts|rotate|inet6|use-vc)(?::(\d{1,2}))?$/; @@ -1318,31 +1318,34 @@ function resolve(name, type, flags, cb) { break; default: var m; - if (m = REGEX_NSSWITCH_ACTION.exec(lookupMethod)) { - // nsswitch action - var status = m[2].toLowerCase(); - var action = m[3].toLowerCase(); - var not = (m[1] === '!'); - if (/*(status === 'success' && - ((!not && lastStatus === NSS_REQ_ERR_SUCCESS) || - (not && lastStatus !== NSS_REQ_ERR_SUCCESS))) ||*/ - (status === 'notfound' && - ((!not && lastStatus === NSS_REQ_ERR_NOTFOUND) || - (not && lastStatus !== NSS_REQ_ERR_NOTFOUND))) || - (status === 'unavail' && - ((!not && lastStatus === NSS_REQ_ERR_UNAVAIL) || - (not && lastStatus !== NSS_REQ_ERR_UNAVAIL))) || - (status === 'tryagain' && - ((!not && lastStatus === NSS_REQ_ERR_TRYAGAIN) || - (not && lastStatus !== NSS_REQ_ERR_TRYAGAIN)))) { - if (action === 'return') { - if (lastStatus === NSS_REQ_ERR_NOTFOUND) - doCb(errnoException('ENOTFOUND', name), undefined, false); - else if (lastStatus === NSS_REQ_ERR_UNAVAIL) - doCb(errnoException('ESERVFAIL', name), undefined, false); - else if (lastStatus === NSS_REQ_ERR_TRYAGAIN) - doCb(errnoException('ESERVFAIL', name), undefined, false); - return; + if (typeof lookupMethod !== 'string') { + // nsswitch action(s) + for (var a = 0; a < lookupMethod.length; ++a) { + var pair = lookupMethod[a]; + var not = pair[0]; + var status = pair[1]; + var action = pair[2]; + if (/*(status === 'success' && + ((!not && lastStatus === NSS_REQ_ERR_SUCCESS) || + (not && lastStatus !== NSS_REQ_ERR_SUCCESS))) ||*/ + (status === 'notfound' && + ((!not && lastStatus === NSS_REQ_ERR_NOTFOUND) || + (not && lastStatus !== NSS_REQ_ERR_NOTFOUND))) || + (status === 'unavail' && + ((!not && lastStatus === NSS_REQ_ERR_UNAVAIL) || + (not && lastStatus !== NSS_REQ_ERR_UNAVAIL))) || + (status === 'tryagain' && + ((!not && lastStatus === NSS_REQ_ERR_TRYAGAIN) || + (not && lastStatus !== NSS_REQ_ERR_TRYAGAIN)))) { + if (action === 'return') { + if (lastStatus === NSS_REQ_ERR_NOTFOUND) + doCb(errnoException('ENOTFOUND', name), undefined, false); + else if (lastStatus === NSS_REQ_ERR_UNAVAIL) + doCb(errnoException('ESERVFAIL', name), undefined, false); + else if (lastStatus === NSS_REQ_ERR_TRYAGAIN) + doCb(errnoException('ESERVFAIL', name), undefined, false); + return; + } } } ++lookupIdx; @@ -2608,12 +2611,25 @@ function parseNsswitchConf() { var data = fs.readFileSync('/etc/nsswitch.conf', 'utf8'); var m = /^hosts:[ \f\t\v]*(.+)$/m.exec(data); if (m) { - var order = m[1].replace(REGEX_COMMENT, '').trim().split(/[ \f\t\v,]+/g); + var order = m[1].replace(REGEX_COMMENT, '').trim(); + order = order.match(/(?:\[[^\]]+\])|(?:[^ \f\t\v,]+)/g); for (var i = 0, loc; i < order.length; ++i) { loc = order[i].toLowerCase(); - if (seen.indexOf(loc) !== -1) + if (loc && loc[0] === '[' && loc[loc.length - 1] === ']') { + // Parse [!]status=action pair(s) + loc = loc.split(' '); + for (var j = 0; j < loc.length; ++j) { + m = REGEX_NSSWITCH_ACTION.exec(loc[j]); + if (m) + loc[j] = [ m[1] === '!', m[2], m[3] ]; + else { + spliceOne(loc, j); + --j; + } + } + } else if (seen.indexOf(loc) !== -1) continue; - if (loc !== 'dns' && loc !== 'files') + if (typeof loc === 'string' && loc !== 'dns' && loc !== 'files') loc = order[i]; if (!ret) ret = [loc]; @@ -2780,9 +2796,7 @@ function execSync(cmd, opts) { // request for (var i = 0; i < dnsConfig.lookup.length; ++i) { var loc = dnsConfig.lookup[i]; - if (loc !== 'dns' && - loc !== 'files' && - !REGEX_NSSWITCH_ACTION.test(loc)) { + if (typeof loc === 'string' && loc !== 'dns' && loc !== 'files') { try { nssModules[loc] = new NSSModule(loc); } catch (ex) { From 2747205289dcd63b441a0ca12edce1b90f113b09 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 2 Oct 2015 01:13:05 -0400 Subject: [PATCH 09/39] dns: get rid of unnecessary array creation --- lib/internal/dns.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 546c93b1b9531d..2edfbd2a5aa3e6 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -739,12 +739,11 @@ exports.lookup = function lookup(hostname, options, callback) { if (family === 0) family = (triedIPv4 ? 4 : 6); if (all) { - var addrs = new Array(answers.length); for (var i = 0; i < answers.length; ++i) { addr = answers[i].data; if (needv4Mapped) addr = '::ffff:' + addr; - addrs[i] = { + answers[i] = { address: addr, family: family }; @@ -758,10 +757,9 @@ exports.lookup = function lookup(hostname, options, callback) { } } else { if (all) { - var vals = new Array(answers.length); for (var i = 0; i < answers.length; ++i) - vals[i] = answers[i].data; - callback(null, vals); + answers[i] = answers[i].data; + callback(null, answers); } else { callback(null, answers[0].data); } From d4b01a2f2a9ae2770b19aea7d51e4b872a145597 Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 2 Oct 2015 01:14:31 -0400 Subject: [PATCH 10/39] dns: fix retry behavior The attempts option counts the number of loops through the list of nameservers, not the *absolute count* of *all* lookup methods. --- lib/internal/dns.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 2edfbd2a5aa3e6..d0388769d859e3 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -1237,7 +1237,7 @@ function resolve(name, type, flags, cb) { clearTimeout(timer); - if (attempts-- === 0 || !lookupMethod) + if (!lookupMethod) return doCb(errnoException('ENOTFOUND', name), undefined, false); switch (lookupMethod) { @@ -1260,8 +1260,14 @@ function resolve(name, type, flags, cb) { } if (!nameserver) { - // No more nameservers left, try next lookup method - ++lookupIdx; + // No more nameservers left, try next lookup method if we have + // exhaused all retries + if (attempts-- === 0) + ++lookupIdx; + else { + nsCount = 0; + nsIdx = -1; + } return nextLookup(false); } From cb6721c4a0e9e36b981f938880951def1607b57a Mon Sep 17 00:00:00 2001 From: Brian White Date: Fri, 2 Oct 2015 01:18:53 -0400 Subject: [PATCH 11/39] dns: do parallel lookups Previously, lookups were always sequential. Now we only perform sequential lookups if the single-request option is enabled. --- lib/internal/dns.js | 54 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index d0388769d859e3..fc91595bb3da93 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -698,8 +698,58 @@ exports.lookup = function lookup(hostname, options, callback) { } } - if (!type) - type = (family === 4 ? 'A' : 'AAAA'); + if (!type) { + if (family === 4) + type = 'A'; + else if (family === 6 || dnsConfig.options.singleRequest) + type = 'AAAA'; + else { + type = 'AAAA'; + triedIPv4 = true; + var origCallback = callback; + var results = { 4: null, 6: null }; + callback = function(err, addrs) { + // AAAA records + results[6] = err || addrs; + finalcb(); + }; + var finalcb = function() { + var res4 = results[4]; + var res6 = results[6]; + if (res4 && res6) { + var error4 = (res4 instanceof Error); + var error6 = (res6 instanceof Error); + if (error4 && error6) + return origCallback(inet6 ? res6 : res4); + + var res; + if (all && !error4 && !error6) + res = (inet6 ? res6.concat(res4) : res4.concat(res6)); + else if (!error6 && (inet6 || error4)) + res = res6; + else + res = res4; + + origCallback(null, res); + } + }; + var newOpts; + if (typeof options === 'object') { + newOpts = { + family: 4, + hints: hints, + all: all, + flags: flags + }; + } else + newOpts = 4; + lookup(hostname, newOpts, function(err, addrs) { + // A records + results[4] = err || addrs; + finalcb(); + }); + } + } resolve(hostname, type, flags, function resolvecb(err, rep) { if (err) { From 464f7ff12902818f89fcc1b30ff245263c0163b9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 02:44:47 -0500 Subject: [PATCH 12/39] test: replace common.hasIPv6 implementation --- test/common.js | 77 +++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/test/common.js b/test/common.js index 9c478af716062b..bdd113123bd0c3 100644 --- a/test/common.js +++ b/test/common.js @@ -190,12 +190,41 @@ if (exports.isWindows) { 'faketime'); } -var ifaces = os.networkInterfaces(); -exports.hasIPv6 = Object.keys(ifaces).some(function(name) { - return /lo/.test(name) && ifaces[name].some(function(info) { - return info.family === 'IPv6'; - }); -}); +exports.hasIPv6 = (function() { + var interfaces = os.networkInterfaces(); + var ifkeys = Object.keys(interfaces); + var hasIPv6LL = false; + var hasIPv4 = false; + + for (var i = 0, addrs; i < ifkeys.length; ++i) { + addrs = interfaces[ifkeys[i]]; + for (var j = 0, addr; j < addrs.length; ++j) { + addr = addrs[j]; + // Do not consider loopback addresses + if (!addr.internal) { + switch (addr.family) { + case 'IPv4': + // Also do not consider link-local addresses + if (addr.address.slice(0, 8) !== '169.254.') + hasIPv4 = true; + break; + case 'IPv6': + if (addr.address.slice(0, 6) === 'fe80::') + hasIPv6LL = true; + else + return true; + break; + } + } + } + } + + // Only consider IPv6 link-local addresses if there are no IPv4 addresses + if (!hasIPv4 && hasIPv6LL) + return true; + + return false; +})(); function protoCtrChain(o) { var result = []; @@ -451,42 +480,6 @@ exports.getServiceName = function getServiceName(port, protocol) { return serviceName; }; -exports.hasIPv6 = function() { - var interfaces = require('os').networkInterfaces(); - var ifkeys = Object.keys(interfaces); - var hasIPv6LL = false; - var hasIPv4 = false; - - for (var i = 0, addrs; i < ifkeys.length; ++i) { - addrs = interfaces[ifkeys[i]]; - for (var j = 0, addr; j < addrs.length; ++j) { - addr = addrs[j]; - // Do not consider loopback addresses - if (!addr.internal) { - switch (addr.family) { - case 'IPv4': - // Also do not consider link-local addresses - if (addr.address.slice(0, 8) !== '169.254.') - hasIPv4 = true; - break; - case 'IPv6': - if (addr.address.slice(0, 6) === 'fe80::') - hasIPv6LL = true; - else - return true; - break; - } - } - } - } - - // Only consider IPv6 link-local addresses if there are no IPv4 addresses - if (!hasIPv4 && hasIPv6LL) - return true; - - return false; -}; - exports.hasMultiLocalhost = function hasMultiLocalhost() { var TCP = process.binding('tcp_wrap').TCP; var t = new TCP(); From 71e50dc4dc6a941a8257d27a817eed7da12eadce Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 02:46:45 -0500 Subject: [PATCH 13/39] test: remove AsyncWrap provider checks on new DNS --- test/parallel/test-async-wrap-check-providers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 45dd8d4f24048b..728cabda5fc1d3 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -18,6 +18,10 @@ let keyList = pkeys.slice(); // Drop NONE keyList.splice(0, 1); +if (!process.oldDNS) { + const unused = ['GETADDRINFOREQWRAP', 'GETNAMEINFOREQWRAP', 'QUERYWRAP']; + keyList = keyList.filter(function(k) { return unused.indexOf(k) === -1; }); +} function init(id) { keyList = keyList.filter(e => e != pkeys[id]); From 11b7bc557d2965be6bc9ae5863c5bd96a04784be Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 03:15:12 -0500 Subject: [PATCH 14/39] test: assert only on old DNS --- test/parallel/test-net-connect-immediate-finish.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-net-connect-immediate-finish.js b/test/parallel/test-net-connect-immediate-finish.js index a5403732ec1d0d..c3405e69763cd3 100644 --- a/test/parallel/test-net-connect-immediate-finish.js +++ b/test/parallel/test-net-connect-immediate-finish.js @@ -11,7 +11,8 @@ client.once('error', common.mustCall(function(err) { assert.strictEqual(err.code, 'ENOTFOUND'); assert.strictEqual(err.host, err.hostname); assert.strictEqual(err.host, '...'); - assert.strictEqual(err.syscall, 'getaddrinfo'); + if (process.oldDNS) + assert.strictEqual(err.syscall, 'getaddrinfo'); })); client.end(); From ab38d2ad5f80246e7cea982375f0ac3a5c497217 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 03:20:39 -0500 Subject: [PATCH 15/39] dns: use original hostname in errors --- lib/internal/dns.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index fc91595bb3da93..ba6b9690ce2c21 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -1002,6 +1002,7 @@ function resolve(name, type, flags, cb) { var socket; var reqBuf; var timer; + var origName; // For DNS nameserver requests qtype = QTYPE_S2V[type]; @@ -1011,6 +1012,7 @@ function resolve(name, type, flags, cb) { qtypeName = type; if (typeof name !== 'string') return process.nextTick(cb, errnoException(exports.BADQUERY, name)); + origName = name; if (name.charCodeAt(name.length - 1) === 46) { // '.' name = name.slice(0, -1); flags |= FLAG_RESOLVE_NOSEARCH; @@ -1055,7 +1057,7 @@ function resolve(name, type, flags, cb) { // to switch to passing actual DNS error names to the user in the future. if (!err && ret && ret.rcode !== 'NOERROR') { ret.rcode = RCODE_S2CARES[ret.rcode] || ret.rcode; - err = errnoException(ret.rcode, name); + err = errnoException(ret.rcode, origName); ret = undefined; } @@ -1288,7 +1290,7 @@ function resolve(name, type, flags, cb) { clearTimeout(timer); if (!lookupMethod) - return doCb(errnoException('ENOTFOUND', name), undefined, false); + return doCb(errnoException('ENOTFOUND', origName), undefined, false); switch (lookupMethod) { case 'dns': @@ -1393,11 +1395,11 @@ function resolve(name, type, flags, cb) { (not && lastStatus !== NSS_REQ_ERR_TRYAGAIN)))) { if (action === 'return') { if (lastStatus === NSS_REQ_ERR_NOTFOUND) - doCb(errnoException('ENOTFOUND', name), undefined, false); + doCb(errnoException('ENOTFOUND', origName), undefined, false); else if (lastStatus === NSS_REQ_ERR_UNAVAIL) - doCb(errnoException('ESERVFAIL', name), undefined, false); + doCb(errnoException('ESERVFAIL', origName), undefined, false); else if (lastStatus === NSS_REQ_ERR_TRYAGAIN) - doCb(errnoException('ESERVFAIL', name), undefined, false); + doCb(errnoException('ESERVFAIL', origName), undefined, false); return; } } From 1e4f701c590ece99587ed27dd74c2a4d05880ed0 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 03:21:17 -0500 Subject: [PATCH 16/39] dns: fix error message style This is keeping in line with the changes made in 20285ad177. --- lib/internal/dns.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index ba6b9690ce2c21..0d8166b5599e6e 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -550,7 +550,7 @@ var dnsConfig = { exports.setServers = function(servers) { if (!Array.isArray(servers)) - throw new Error('servers argument must be an array of addresses'); + throw new Error('"servers" argument must be an array of addresses'); var newSet = []; @@ -608,7 +608,7 @@ exports.lookup = function lookup(hostname, options, callback) { // Parse arguments if (hostname && typeof hostname !== 'string') { - throw new TypeError('invalid arguments: ' + + throw new TypeError('Invalid arguments: ' + 'hostname must be a string or falsey'); } else if (typeof options === 'function') { callback = options; @@ -616,7 +616,7 @@ exports.lookup = function lookup(hostname, options, callback) { // This should match glibc behavior hints = (ADDRCONFIG | V4MAPPED); } else if (typeof callback !== 'function') { - throw new TypeError('invalid arguments: callback must be passed'); + throw new TypeError('Invalid arguments: callback must be passed'); } else if (options !== null && typeof options === 'object') { if (typeof options.hints === 'number') hints = (options.hints >>> 0); @@ -629,7 +629,7 @@ exports.lookup = function lookup(hostname, options, callback) { if (typeof options.type === 'string') { if (QTYPE_S2V[options.type] === undefined) { throw new Error( - 'invalid argument: type must be a supported record type' + 'Invalid argument: type must be a supported record type' ); } if (options.type !== 'A' && options.type !== 'AAAA') @@ -641,7 +641,7 @@ exports.lookup = function lookup(hostname, options, callback) { hints !== ADDRCONFIG && hints !== V4MAPPED && hints !== (ADDRCONFIG | V4MAPPED)) { - throw new TypeError('invalid argument: hints must use valid flags'); + throw new TypeError('Invalid argument: hints must use valid flags'); } } else { family = options >>> 0; @@ -654,7 +654,7 @@ exports.lookup = function lookup(hostname, options, callback) { if (!isCustomType) { if (family !== 0 && family !== 4 && family !== 6) - throw new TypeError('invalid argument: family must be 4 or 6'); + throw new TypeError('Invalid argument: family must be 4 or 6'); if (hostname) { var matchedFamily = isIP(hostname); @@ -821,9 +821,9 @@ function resolver(type) { var returnFirst = (type === 'SOA'); return function query(name, callback) { if (typeof name !== 'string') - throw new Error('Name must be a string'); + throw new Error('"name" argument must be a string'); else if (typeof callback !== 'function') - throw new Error('Callback must be a function'); + throw new Error('"callback" argument must be a function'); resolve(name, type, FLAG_RESOLVE_NSONLY, function resolvecb(err, rep) { if (err) @@ -873,7 +873,7 @@ exports.resolve = function(hostname, type, callback_) { resolver = exports.resolve4; callback = type; } else { - throw new Error('Type must be a string'); + throw new Error('"type" argument must be a string'); } if (typeof resolver === 'function') @@ -884,17 +884,17 @@ exports.resolve = function(hostname, type, callback_) { exports.lookupService = function(addr, port, callback) { if (arguments.length < 3) - throw new Error('missing arguments'); + throw new Error('Invalid arguments'); if (isIP(addr) === 0) - throw new TypeError('addr needs to be a valid IP address'); + throw new TypeError('"addr" argument needs to be a valid IP address'); if (typeof port !== 'number') { port = parseInt(port, 10); if (port !== port) - throw new TypeError('invalid port'); + throw new TypeError('Invalid port'); } if (!isFinite(port)) - throw new TypeError('invalid port'); + throw new TypeError('Invalid port'); exports.lookup(addr, { type: 'PTR' }, function(err, hostname) { var host = (err || !hostname ? addr : hostname); From 5446e985f912c149abe0b6aad92cd4abe4aea795 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 03:46:05 -0500 Subject: [PATCH 17/39] lib,src: fix lint errors --- lib/internal/dns.js | 38 ++++++++++++++++++-------------------- src/nss_module.cc | 7 +++---- src/nss_module.h | 15 +++++++-------- src/nss_wrap.cc | 18 +++++++++--------- src/nss_wrap.h | 3 +-- 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 0d8166b5599e6e..7643ae94b7df29 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -28,18 +28,17 @@ */ 'use strict'; +const Buffer = require('buffer').Buffer; +const cp = require('child_process'); +const os = require('os'); +const fs = require('fs'); +const pathJoin = require('path').join; const TCPSocket = require('net').Socket; const UDPSocket = require('dgram').Socket; - const isIP = require('net').isIP; // TODO: move implementation from cares_wrap const NSSReqWrap = process.binding('nss_wrap').NSSReqWrap; const NSSModule = process.binding('nss_module').NSSModule; -const cp = require('child_process'); -const os = require('os'); -const fs = require('fs'); -const pathJoin = require('path').join; - const NSS_REQ_ERR_SUCCESS = 0; const NSS_REQ_ERR_NOTFOUND = 1; const NSS_REQ_ERR_UNAVAIL = 2; @@ -741,8 +740,9 @@ exports.lookup = function lookup(hostname, options, callback) { all: all, flags: flags }; - } else + } else { newOpts = 4; + } lookup(hostname, newOpts, function(err, addrs) { // A records results[4] = err || addrs; @@ -798,7 +798,7 @@ exports.lookup = function lookup(hostname, options, callback) { family: family }; } - callback(null, addrs); + callback(null, answers); } else { addr = answers[0].data; if (needv4Mapped) @@ -965,8 +965,6 @@ exports.FLAG_RESOLVE_NOALIASES = FLAG_RESOLVE_NOALIASES; exports.FLAG_RESOLVE_IGNTC = FLAG_RESOLVE_IGNTC; exports.FLAG_RESOLVE_USEVC = FLAG_RESOLVE_USEVC; - - // The main resolver implementation function resolve(name, type, flags, cb) { var rotate = dnsConfig.options.rotate; @@ -1045,8 +1043,6 @@ function resolve(name, type, flags, cb) { nextLookup(false); - - function doCb(err, ret, async) { if (cbCalled) return; @@ -1303,8 +1299,9 @@ function resolve(name, type, flags, cb) { srvRotateIdx = (srvRotateIdx + 1) % dnsConfig.nameserver.length; nameserver = dnsConfig.nameserver[srvRotateIdx]; } - } else + } else { nameserver = nameservers[++nsIdx]; + } triedTCP = false; if (TCPOnly) @@ -1472,7 +1469,7 @@ function resolve(name, type, flags, cb) { } // Create a properly formatted Buffer from DNS request parameters -function reqToBuf(qtype, name) { +function reqToBuf(qtype, name, origName) { var origName = name; var namelen = 1; // Account for terminating length byte var buf; @@ -1485,12 +1482,12 @@ function reqToBuf(qtype, name) { for (var i = 0; i < name.length; ++i) { if (name[i].length > 63) - return errnoException(exports.NOTFOUND, origName); + return errnoException(exports.NOTFOUND, name); // Label length byte + label content namelen += 1 + Buffer.byteLength(name[i], 'ascii'); } if (namelen > 255) - return errnoException(exports.NOTFOUND, origName); + return errnoException(exports.NOTFOUND, name); buf = new Buffer(12 + namelen + 2 + 2); @@ -2614,10 +2611,10 @@ function parseResolvOptions(options) { timeout: null, use_vc: null }; - for (var j = 0; j < options.length; ++j) { - m = REGEX_OPTS.exec(options[i]); + for (var i = 0; i < options.length; ++i) { + var m = REGEX_OPTS.exec(options[i]); if (m) { - val = m[2]; + var val = m[2]; switch (m[1]) { case 'attempts': if (val) { @@ -2683,8 +2680,9 @@ function parseNsswitchConf() { --j; } } - } else if (seen.indexOf(loc) !== -1) + } else if (seen.indexOf(loc) !== -1) { continue; + } if (typeof loc === 'string' && loc !== 'dns' && loc !== 'files') loc = order[i]; if (!ret) diff --git a/src/nss_module.cc b/src/nss_module.cc index 710f73ad40632c..be82c82937e204 100644 --- a/src/nss_module.cc +++ b/src/nss_module.cc @@ -57,7 +57,7 @@ void NSSModule::New(const FunctionCallbackInfo& args) { int name_len = 5 + module_len + 17 + 1; char* name = static_cast(malloc(name_len)); - sprintf(name, "libnss_%s.so.2", module_string); + snprintf(name, name_len, "libnss_%s.so.2", module_string); uv_lib_t* lib = static_cast(malloc(sizeof(uv_lib_t))); if (lib == nullptr) @@ -70,7 +70,7 @@ void NSSModule::New(const FunctionCallbackInfo& args) { return env->ThrowError(env->isolate(), err_msg); } - sprintf(name, "_nss_%s_gethostbyname3_r", module_string); + snprintf(name, name_len, "_nss_%s_gethostbyname3_r", module_string); nss_gethostbyname3_r ghbn3 = nullptr; nss_gethostbyname4_r ghbn4 = nullptr; @@ -84,7 +84,7 @@ void NSSModule::New(const FunctionCallbackInfo& args) { status = uv_dlsym(lib, static_cast(name), reinterpret_cast(&ghbn3)); - // TODO: resort to gethostbyname_r which does AF_UNSPEC? + // TODO(mscdex): resort to gethostbyname_r which does AF_UNSPEC? } // No need to obtain the parallel gethostbyname if we do not at least have the @@ -210,7 +210,6 @@ void NSSModule::Initialize(Handle target, env->SetProtoMethod(nssmodule_tmpl, "queryAddr", NSSModule::QueryAddr); target->Set(class_name, nssmodule_tmpl->GetFunction()); } - } } diff --git a/src/nss_module.h b/src/nss_module.h index 80fbc6e28b7ee2..0bf6c927ed07c3 100644 --- a/src/nss_module.h +++ b/src/nss_module.h @@ -6,7 +6,7 @@ #include "node.h" #include "base-object.h" #include "base-object-inl.h" -#include "nss_wrap.h" +#include "nss_wrap.h" // NOLINT(build/include_order) #include @@ -15,18 +15,18 @@ namespace nss_module { typedef enum nss_status (*nss_gethostbyname4_r) (const char* name, struct gaih_addrtuple** pat, - char* buffer, size_t buflen, int* errnop, - int* h_errnop, int32_t* ttlp); + char* buffer, size_t buflen, int* errnop, + int* h_errnop, int32_t* ttlp); typedef enum nss_status (*nss_gethostbyname3_r) (const char* name, int af, struct hostent* host, - char* buffer, size_t buflen, int* errnop, - int* h_errnop, int32_t* ttlp, char** canonname); + char* buffer, size_t buflen, int* errnop, + int* h_errnop, int32_t* ttlp, char** canonname); typedef enum nss_status (*nss_gethostbyaddr2_r) (const void* addr, socklen_t len, int af, - struct hostent* host, char* buffer, size_t buflen, - int* errnop, int* h_errnop, int32_t* ttlp); + struct hostent* host, char* buffer, size_t buflen, + int* errnop, int* h_errnop, int32_t* ttlp); class NSSModule : public BaseObject { private: @@ -59,7 +59,6 @@ class NSSModule : public BaseObject { v8::Handle unused, v8::Handle context); }; - } } diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index cbce44845648d3..6641e4e3c75db0 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -63,7 +63,7 @@ void NSSReqWrap::NameWork(uv_work_t* req) { const char* hostname = req_wrap->host; uint8_t orig_family = req_wrap->family; - // TODO: allow configurable ttl? + // TODO(mscdex): allow configurable ttl? int32_t ttl = INT32_MAX; int err; int rc6; @@ -347,7 +347,6 @@ void NSSReqWrap::NameAfter(uv_work_t* req, int status) { naddrs = 0; Local results = Array::New(env->isolate()); - //results = Array::New(env->isolate(), naddrs); if (naddrs > 0) { char* cur_addr; @@ -413,7 +412,7 @@ void NSSReqWrap::AddrWork(uv_work_t* req) { ? sizeof(struct in_addr) : sizeof(struct in6_addr)); - // TODO: allow configurable ttl? + // TODO(mscdex): allow configurable ttl? int32_t ttl = INT32_MAX; int err; int rc; @@ -454,13 +453,14 @@ void NSSReqWrap::AddrWork(uv_work_t* req) { return; } - if (result.h_name == nullptr) + if (result.h_name == nullptr) { req_wrap->results = nullptr; - else { + } else { uint32_t name_len = strlen(result.h_name); req_wrap->error = NSS_REQ_ERR_SUCCESS; - req_wrap->results = static_cast(malloc(name_len + 1)); - strcpy(req_wrap->results, result.h_name); + int results_len = name_len + 1; + req_wrap->results = static_cast(malloc(results_len)); + snprintf(req_wrap->results, results_len, "%s", result.h_name); } free(tmpbuf); } @@ -481,8 +481,9 @@ void NSSReqWrap::AddrAfter(uv_work_t* req, int status) { if (req_wrap->error == NSS_REQ_ERR_SUCCESS) { result = v8::String::NewFromUtf8(env->isolate(), req_wrap->results); free(req_wrap->results); - } else + } else { result = v8::Null(env->isolate()); + } Local argv[2] = { Integer::New(env->isolate(), req_wrap->error), @@ -552,7 +553,6 @@ void NSSReqWrap::Initialize(Handle target, nw->SetClassName(class_name); target->Set(class_name, nw->GetFunction()); } - } } diff --git a/src/nss_wrap.h b/src/nss_wrap.h index d9c8749f908ae1..d625fa77767180 100644 --- a/src/nss_wrap.h +++ b/src/nss_wrap.h @@ -7,7 +7,7 @@ #include "req-wrap.h" #include "req-wrap-inl.h" #include "async-wrap.h" -#include "nss_module.h" +#include "nss_module.h" // NOLINT(build/include_order) #include @@ -77,7 +77,6 @@ class NSSReqWrap : public ReqWrap { v8::Handle unused, v8::Handle context); }; - } } From 0c5febb7f13f9ee73a25c843f5d6e2e0b507b5f3 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 03:57:32 -0500 Subject: [PATCH 18/39] src: fix bad merge --- src/node.cc | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/node.cc b/src/node.cc index b37cc7330a6e0f..0d091ac7823335 100644 --- a/src/node.cc +++ b/src/node.cc @@ -3279,11 +3279,7 @@ static void PrintHelp() { #if HAVE_OPENSSL " --tls-cipher-list=val use an alternative default TLS cipher list\n" #endif - " --trace-deprecation show stack traces on deprecations\n" - " --trace-sync-io show stack trace when use of sync IO\n" - " is detected after the first tick\n" " --use-old-dns use c-ares for DNS resolution\n" - " --v8-options print v8 command line options\n" #if defined(NODE_HAVE_I18N_SUPPORT) " --icu-data-dir=dir set ICU data load path to dir\n" " (overrides NODE_ICU_DATA)\n" From b558ccf5e68dcc159e9f0b85e6146a07c0fc48b9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 04:12:10 -0500 Subject: [PATCH 19/39] test: fix more lint errors --- test/internet/test-dns-domain-search.js | 2 ++ test/internet/test-dns-durability.js | 2 ++ test/internet/test-dns-truncated.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/test/internet/test-dns-domain-search.js b/test/internet/test-dns-domain-search.js index 350aff2a20e1a6..455ba2770d6992 100644 --- a/test/internet/test-dns-domain-search.js +++ b/test/internet/test-dns-domain-search.js @@ -1,5 +1,7 @@ 'use strict'; +var common = require('../common'); + if (process.oldDNS || process.platform === 'win32') process.exit(0); diff --git a/test/internet/test-dns-durability.js b/test/internet/test-dns-durability.js index b49c6bd74e9e1a..f84381fd32c94a 100644 --- a/test/internet/test-dns-durability.js +++ b/test/internet/test-dns-durability.js @@ -1,5 +1,7 @@ 'use strict'; +var common = require('../common'); + if (process.oldDNS) process.exit(0); diff --git a/test/internet/test-dns-truncated.js b/test/internet/test-dns-truncated.js index 41571305e85893..01faa986c97052 100644 --- a/test/internet/test-dns-truncated.js +++ b/test/internet/test-dns-truncated.js @@ -1,5 +1,7 @@ 'use strict'; +var common = require('../common'); + if (process.oldDNS) process.exit(0); From 24b4e1121146bf7c9525f79201aff55224039b38 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 04:12:49 -0500 Subject: [PATCH 20/39] test: fix internet tests for new dns --- test/internet/test-dns-ipv4.js | 3 ++- test/internet/test-dns-ipv6.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/test/internet/test-dns-ipv4.js b/test/internet/test-dns-ipv4.js index 04befca6370e1e..e6714222100e6a 100644 --- a/test/internet/test-dns-ipv4.js +++ b/test/internet/test-dns-ipv4.js @@ -37,7 +37,8 @@ function TEST(f) { } function checkWrap(req) { - assert.ok(typeof req === 'object'); + if (process.oldDNS) + assert.ok(typeof req === 'object'); } TEST(function test_resolve4(done) { diff --git a/test/internet/test-dns-ipv6.js b/test/internet/test-dns-ipv6.js index 27547edcd84b46..3fe6723cf6dfa6 100644 --- a/test/internet/test-dns-ipv6.js +++ b/test/internet/test-dns-ipv6.js @@ -42,7 +42,8 @@ function TEST(f) { } function checkWrap(req) { - assert.ok(typeof req === 'object'); + if (process.oldDNS) + assert.ok(typeof req === 'object'); } TEST(function test_resolve6(done) { From d0ffe5a05fe2513fe60a7165e02e94a9984905e9 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 15:27:55 -0500 Subject: [PATCH 21/39] dns: fix parsing nsswitch actions --- lib/internal/dns.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 7643ae94b7df29..8e697c89c87af0 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -2670,7 +2670,7 @@ function parseNsswitchConf() { loc = order[i].toLowerCase(); if (loc && loc[0] === '[' && loc[loc.length - 1] === ']') { // Parse [!]status=action pair(s) - loc = loc.split(' '); + loc = loc.slice(1, -1).split(' '); for (var j = 0; j < loc.length; ++j) { m = REGEX_NSSWITCH_ACTION.exec(loc[j]); if (m) From e5c37fd14bb798d874a5b2c5039bf311fe22d3d8 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 15:28:53 -0500 Subject: [PATCH 22/39] test: listen for potential dns request retries --- test/internet/test-dns-durability.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/internet/test-dns-durability.js b/test/internet/test-dns-durability.js index f84381fd32c94a..bb6bbe8a72ca7c 100644 --- a/test/internet/test-dns-durability.js +++ b/test/internet/test-dns-durability.js @@ -58,7 +58,9 @@ function UDPServerReply(replybuf, expbuf) { var timer = setTimeout(function() { throw new Error(format('Timeout on UDP test (%s)', testName)); }, 100); - UDPServer.once('message', function(buf, rinfo) { + UDPServer.removeAllListeners('message'); + // Listen for multiple connections in case client retries request + UDPServer.on('message', function(buf, rinfo) { clearTimeout(timer); if (expbuf) assert.deepEqual(buf, expbuf); @@ -77,7 +79,9 @@ function TCPServerReply(replybuf, expbuf) { return s.once('readable', readN.bind(null, s, n, cb)); cb(r); } - TCPServer.once('connection', function(s) { + TCPServer.removeAllListeners('connection'); + // Listen for multiple connections in case client retries request + TCPServer.on('connection', function(s) { readN(s, 2, function readLength(buf) { readN(s, buf.readUInt16BE(0), function readBytes(buf) { clearTimeout(timer); From 2b4eeda2fe498e3faa4f437b03580d5f4039c322 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 16:07:33 -0500 Subject: [PATCH 23/39] src: use correct nss module filename on BSD --- src/nss_module.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nss_module.cc b/src/nss_module.cc index be82c82937e204..2b353f9598a547 100644 --- a/src/nss_module.cc +++ b/src/nss_module.cc @@ -57,7 +57,11 @@ void NSSModule::New(const FunctionCallbackInfo& args) { int name_len = 5 + module_len + 17 + 1; char* name = static_cast(malloc(name_len)); +#ifdef BSD + snprintf(name, name_len, "nss_%s.so.1", module_string); +#else snprintf(name, name_len, "libnss_%s.so.2", module_string); +#endif uv_lib_t* lib = static_cast(malloc(sizeof(uv_lib_t))); if (lib == nullptr) From 79682743c0072186554a25fa559be85ba9ef33cf Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 17:36:21 -0500 Subject: [PATCH 24/39] test: add temporary bypass for AsyncWrap check --- test/parallel/test-async-wrap-check-providers.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 728cabda5fc1d3..a9116059e65a8f 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -18,6 +18,10 @@ let keyList = pkeys.slice(); // Drop NONE keyList.splice(0, 1); +// TODO(mscdex): remove this when we can easily test with a dummy nss module +if (~keyList.indexOf('NSSREQWRAP')) + keyList.splice(keyList.indexOf('NSSREQWRAP'), 1); + if (!process.oldDNS) { const unused = ['GETADDRINFOREQWRAP', 'GETNAMEINFOREQWRAP', 'QUERYWRAP']; keyList = keyList.filter(function(k) { return unused.indexOf(k) === -1; }); From ef02f3682e149e3d793a074a301160ae3c245ae6 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 17:38:18 -0500 Subject: [PATCH 25/39] src: fix compilation for old GLIBC CentOS 5 in particular has an older version of GLIBC that does not define gaih_addrtuple in nss.h. Declaring them explicitly is a simple and easy solution. --- src/nss_module.h | 20 +++++++++++++++++++- src/nss_wrap.h | 2 -- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/nss_module.h b/src/nss_module.h index 0bf6c927ed07c3..9686c226d5bbbd 100644 --- a/src/nss_module.h +++ b/src/nss_module.h @@ -8,7 +8,25 @@ #include "base-object-inl.h" #include "nss_wrap.h" // NOLINT(build/include_order) -#include +// nss_status and gaih_addrtuple are copied here from nss.h because at least +// GLIBC<2.9 does not define gaih_addrtuple there and those two definitions are +// all that nss.h contains right now +// Possible results of lookup using a nss_* function. +enum nss_status { + NSS_STATUS_TRYAGAIN = -2, + NSS_STATUS_UNAVAIL, + NSS_STATUS_NOTFOUND, + NSS_STATUS_SUCCESS, + NSS_STATUS_RETURN +}; +// Data structure used for the 'gethostbyname4_r' function. +struct gaih_addrtuple { + struct gaih_addrtuple *next; + char *name; + int family; + uint32_t addr[4]; + uint32_t scopeid; +}; namespace node { namespace nss_module { diff --git a/src/nss_wrap.h b/src/nss_wrap.h index d625fa77767180..f436372ab56966 100644 --- a/src/nss_wrap.h +++ b/src/nss_wrap.h @@ -9,8 +9,6 @@ #include "async-wrap.h" #include "nss_module.h" // NOLINT(build/include_order) -#include - #if defined(__ANDROID__) || \ defined(__MINGW32__) || \ defined(__OpenBSD__) || \ From 7ef5ac2764ec860acf8757526460eb319e7e3f48 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 17:39:00 -0500 Subject: [PATCH 26/39] dns: fix TODO comment formatting --- lib/internal/dns.js | 50 +++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 8e697c89c87af0..78963ae1ecaa1f 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -1,5 +1,5 @@ /* - TODO: + TODO(mscdex): - Reuse sockets (this means maintaining a global state object)? This could especially help with minimizing the overhead of queries over TCP connections @@ -35,7 +35,8 @@ const fs = require('fs'); const pathJoin = require('path').join; const TCPSocket = require('net').Socket; const UDPSocket = require('dgram').Socket; -const isIP = require('net').isIP; // TODO: move implementation from cares_wrap +// TODO(mscdex): move isIP C++ implementation from cares_wrap +const isIP = require('net').isIP; const NSSReqWrap = process.binding('nss_wrap').NSSReqWrap; const NSSModule = process.binding('nss_module').NSSModule; @@ -69,7 +70,7 @@ const FLAG_RESOLVE_NOALIASES = 0x04; // No HOSTALIASES checking const FLAG_RESOLVE_IGNTC = 0x08; // Ignore truncation errors (UDP) const FLAG_RESOLVE_USEVC = 0x10; // Always use TCP a.k.a "Virtual Circuit" const DNS_DEFAULTS = { - // TODO: improve default nameserver for IPv6-only systems + // TODO(mscdex): improve default nameserver for IPv6-only systems nameserver: [ '127.0.0.1' ], search: (~HOSTNAME.indexOf('.') ? [ HOSTNAME.slice(HOSTNAME.indexOf('.') + 1) ] : null), @@ -658,8 +659,8 @@ exports.lookup = function lookup(hostname, options, callback) { if (hostname) { var matchedFamily = isIP(hostname); if (matchedFamily) { - // TODO: Return IPv4-mapped IPv6 address if `v4mapped === true` and - // `matchedFamily === 4` ? + // TODO(mscdex): Return IPv4-mapped IPv6 address if `v4mapped === true` + // and `matchedFamily === 4` ? if (all) { process.nextTick(callback, null, @@ -904,8 +905,8 @@ exports.lookupService = function(addr, port, callback) { return callback(err, host, port); svcPath = pathJoin(win32_dbPath, 'services'); } else { - // TODO: check /etc/nsswitch.conf for additional/alternate services file - // path + // TODO(mscdex): check /etc/nsswitch.conf for additional/alternate + // services file path svcPath = '/etc/services'; } searchServicesFile(svcPath, port, function(result) { @@ -1203,8 +1204,9 @@ function resolve(name, type, flags, cb) { triedAsIs = false; } else { // Make sure we retry on TCP if we are already on TCP - // TODO: revert back to UDP if we are on TCP because of a truncated - // response? Either way we must use TCP if `TCPOnly === true` + // TODO(mscdex): revert back to UDP if we are on TCP because of a + // truncated response? Either way we must use TCP if + // `TCPOnly === true` triedTCP = false; // Try next name with current server @@ -1427,7 +1429,7 @@ function resolve(name, type, flags, cb) { results = [{ name: name, type: type, - ttl: 0, // TODO: fill in when available + ttl: 0, // TODO(mscdex): fill in when available data: results }]; } else { @@ -1435,7 +1437,7 @@ function resolve(name, type, flags, cb) { results[i] = { name: name, type: type, - ttl: 0, // TODO: fill in when available + ttl: 0, // TODO(mscdex): fill in when available data: results[i] }; } @@ -2332,10 +2334,11 @@ function spliceOne(list, index) { // Platform-specific helper methods: function win32_wmiDNSInfo() { - // TODO: check AppendToMultiLabelName registry value before setting - // `DNSDomainSuffixSearchOrder` - // http://blogs.technet.com/b/networking/archive/2009/04/16/dns-client-name- - // resolution-behavior-in-windows-vista-vs-windows-xp.aspx + // TODO(mscdex): check AppendToMultiLabelName registry value before setting + // `DNSDomainSuffixSearchOrder` + // http://blogs.technet.com/b/networking/archive/2009/04/16/dns- + // client-name-resolution-behavior-in-windows-vista-vs-windows- + // xp.aspx var ret = false; var cmd = [ 'wmic', @@ -2403,8 +2406,9 @@ function win32_wmiDNSInfo() { options: { // Windows always tries a multi-label name as-is before using suffixes ndots: 1, - // TODO: support `timeout` via DNSQueryTimeouts registry value? - // https://technet.microsoft.com/library/Cc977482 + // TODO(mscdex): support `timeout` via DNSQueryTimeouts registry + // value? + // https://technet.microsoft.com/library/Cc977482 timeout: null, attempts: null, rotate: null, @@ -2447,7 +2451,8 @@ function win32_getRegNameServers() { return r.split(/[ \f\t\v,]+/g); // Next try to find the first interface DNS address - // TODO: make this smarter/better rather than just using first interface? + // TODO(mscdex): make this smarter/better rather than just using first + // interface? path += '\\Interfaces'; r = win32_regQuery(path, 'NameServer', true); if (r !== false) @@ -2656,7 +2661,7 @@ function parseResolvOptions(options) { } function parseNsswitchConf() { - // TODO: search for explicitly defined services file path too + // TODO(mscdex): search for explicitly defined services file path too var ret = false; var seen = []; @@ -2792,9 +2797,10 @@ function execSync(cmd, opts) { win32_dbPath = win32_regQuery(path, 'DataBasePath', false); if (win32_dbPath) win32_dbPath = win32_expandEnvironmentStrings(win32_dbPath); - // TODO: create fallbacks for win32_dbPath if it's not set in registry for - // some reason (e.g. try "%SystemRoot%\system32\drivers\etc" then - // "C:\Windows\system32\drivers\etc")? + // TODO(mscdex): create fallbacks for win32_dbPath if it's not set in + // registry for some reason (e.g. try + // "%SystemRoot%\system32\drivers\etc" then + // "C:\Windows\system32\drivers\etc")? // Try WMI first r = win32_wmiDNSInfo(); From fe7af62085816a34bb518fda927990d537d5c6c1 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 18:07:15 -0500 Subject: [PATCH 27/39] src: fix compilation on Windows --- src/nss_wrap.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index 6641e4e3c75db0..58b7aa47d28eb5 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -56,6 +56,7 @@ size_t NSSReqWrap::self_size() const { void NSSReqWrap::NameWork(uv_work_t* req) { +#ifndef _WIN32 NSSReqWrap* req_wrap = static_cast(req->data); nss_gethostbyname3_r ghbn3 = req_wrap->module->ghbn3; @@ -327,6 +328,7 @@ void NSSReqWrap::NameWork(uv_work_t* req) { if (tmpbuf4 != nullptr) free(tmpbuf4); } +#endif } @@ -403,6 +405,7 @@ void NSSReqWrap::NameAfter(uv_work_t* req, int status) { void NSSReqWrap::AddrWork(uv_work_t* req) { +#ifndef _WIN32 NSSReqWrap* req_wrap = static_cast(req->data); nss_gethostbyaddr2_r ghba2 = req_wrap->module->ghba2; @@ -464,6 +467,7 @@ void NSSReqWrap::AddrWork(uv_work_t* req) { } free(tmpbuf); } +#endif } From 014b06c55c03f8c92218b012ff1dfcf836567a93 Mon Sep 17 00:00:00 2001 From: Brian White Date: Sun, 20 Dec 2015 19:30:49 -0500 Subject: [PATCH 28/39] src: add fallback for GLIBC-specific function --- src/nss_wrap.cc | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index 58b7aa47d28eb5..74bb02a92faec6 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -18,6 +18,14 @@ using v8::Object; using v8::String; using v8::Value; +#ifdef __GLIBC__ +# define _mempcpy mempcpy +#else +void* _mempcpy(void* dest, const void* src, size_t len) { + return static_cast(memcpy(dest, src, len)) + len; +} +#endif + NSSReqWrap::NSSReqWrap(Environment* env, Local req_wrap_obj, char* host_val, @@ -160,9 +168,9 @@ void NSSReqWrap::NameWork(uv_work_t* req) { at2 = at2->next) { *family++ = at2->family; if (at2->family == AF_INET) - addrs = static_cast(mempcpy(addrs, at2->addr, INADDRSZ)); + addrs = static_cast(_mempcpy(addrs, at2->addr, INADDRSZ)); else if (at2->family == AF_INET6) - addrs = static_cast(mempcpy(addrs, at2->addr, IN6ADDRSZ)); + addrs = static_cast(_mempcpy(addrs, at2->addr, IN6ADDRSZ)); } free(tmpbuf6); @@ -317,7 +325,7 @@ void NSSReqWrap::NameWork(uv_work_t* req) { for (int j = 0; th[i].h_addr_list[j] != NULL; ++j) { if (orig_family == 0) *family++ = th[i].h_addrtype; - addrs = static_cast(mempcpy(addrs, th[i].h_addr_list[j], + addrs = static_cast(_mempcpy(addrs, th[i].h_addr_list[j], th[i].h_length)); } } From e127e21d1cd139fb22b36f59861347bf568a5bad Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 21 Dec 2015 03:52:50 -0500 Subject: [PATCH 29/39] dns: don't restrict to nameserver for PTR queries This matches behavior with the old DNS resolution mechanism. --- lib/internal/dns.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 78963ae1ecaa1f..1a46751ecad9e2 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -820,13 +820,14 @@ exports.lookup = function lookup(hostname, options, callback) { function resolver(type) { var returnFirst = (type === 'SOA'); + var flags = (type !== 'PTR' ? FLAG_RESOLVE_NSONLY : 0); return function query(name, callback) { 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'); - resolve(name, type, FLAG_RESOLVE_NSONLY, function resolvecb(err, rep) { + resolve(name, type, flags, function resolvecb(err, rep) { if (err) return callback(err); From 7f45565df560286633bd0b9c433d807993f44ba2 Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 21 Dec 2015 04:28:41 -0500 Subject: [PATCH 30/39] src: fix compilation on OS X --- src/nss_wrap.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index 74bb02a92faec6..3b2de6a1b6f8b8 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -26,6 +26,13 @@ void* _mempcpy(void* dest, const void* src, size_t len) { } #endif +#ifndef INADDRSZ +# define INADDRSZ 4 +#endif +#ifndef IN6ADDRSZ +# define IN6ADDRSZ 16 +#endif + NSSReqWrap::NSSReqWrap(Environment* env, Local req_wrap_obj, char* host_val, From 48c3ed6dd822c8c82bea66489c7d0fd4b4ef53de Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 21 Dec 2015 15:40:52 -0500 Subject: [PATCH 31/39] test: only check syscall property with old DNS --- test/parallel/test-net-connect-options-ipv6.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-net-connect-options-ipv6.js b/test/parallel/test-net-connect-options-ipv6.js index 623b2eff1ddded..cc49904291668e 100644 --- a/test/parallel/test-net-connect-options-ipv6.js +++ b/test/parallel/test-net-connect-options-ipv6.js @@ -47,7 +47,8 @@ function tryConnect() { server.close(); }); }).on('error', function(err) { - if (err.syscall === 'getaddrinfo' && err.code === 'ENOTFOUND') { + if (err.code === 'ENOTFOUND' && + (!process.oldDNS || err.syscall === 'getaddrinfo')) { if (host !== 'localhost' || --localhostTries === 0) host = hosts[++hostIdx]; if (host) From 9251fda907fefa4b26b7729abf425a7212eac594 Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 21 Dec 2015 16:02:39 -0500 Subject: [PATCH 32/39] src: use consistent self_size override --- src/nss_wrap.cc | 5 ----- src/nss_wrap.h | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index 3b2de6a1b6f8b8..a8766b92820d3d 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -65,11 +65,6 @@ void NSSReqWrap::Unref() { } -size_t NSSReqWrap::self_size() const { - return sizeof(*this); -} - - void NSSReqWrap::NameWork(uv_work_t* req) { #ifndef _WIN32 NSSReqWrap* req_wrap = static_cast(req->data); diff --git a/src/nss_wrap.h b/src/nss_wrap.h index f436372ab56966..d9611403075493 100644 --- a/src/nss_wrap.h +++ b/src/nss_wrap.h @@ -55,12 +55,12 @@ class NSSReqWrap : public ReqWrap { ~NSSReqWrap(); + size_t self_size() const override { return sizeof(*this); } + void Ref(); void Unref(); - size_t self_size() const override; - static void NameWork(uv_work_t* req); static void NameAfter(uv_work_t* req, int status); From abc390a73db3567d13e6141b496aaeb6412e5bee Mon Sep 17 00:00:00 2001 From: Brian White Date: Mon, 21 Dec 2015 23:40:00 -0500 Subject: [PATCH 33/39] src: fix segfault and issues from static analysis --- src/nss_module.cc | 13 ++++++++++--- src/nss_wrap.cc | 26 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/nss_module.cc b/src/nss_module.cc index 2b353f9598a547..0ea529e10c2be6 100644 --- a/src/nss_module.cc +++ b/src/nss_module.cc @@ -32,7 +32,7 @@ NSSModule::NSSModule(Environment* env, NSSModule::~NSSModule() { if (lib_ != nullptr) { uv_dlclose(lib_); - delete lib_; + free(lib_); } } @@ -56,6 +56,9 @@ void NSSModule::New(const FunctionCallbackInfo& args) { // here. int name_len = 5 + module_len + 17 + 1; char* name = static_cast(malloc(name_len)); + if (name == nullptr) { + return env->ThrowError("malloc failed"); + } #ifdef BSD snprintf(name, name_len, "nss_%s.so.1", module_string); @@ -64,8 +67,10 @@ void NSSModule::New(const FunctionCallbackInfo& args) { #endif uv_lib_t* lib = static_cast(malloc(sizeof(uv_lib_t))); - if (lib == nullptr) + if (lib == nullptr) { + free(name); return env->ThrowError("malloc failed"); + } int status = uv_dlopen(name, lib); if (status == -1) { @@ -119,7 +124,7 @@ void NSSModule::New(const FunctionCallbackInfo& args) { if (ghbn3 == nullptr && ghba2 == nullptr) { uv_dlclose(lib); - delete lib; + free(lib); return env->ThrowError("nss module missing needed gethostby*_r functions"); } @@ -158,6 +163,7 @@ void NSSModule::QueryName(const FunctionCallbackInfo& args) { req_wrap->module = nss; req_wrap->family = family; + req_wrap->req_.data = req_wrap; req_wrap->Ref(); uv_queue_work(env->event_loop(), @@ -188,6 +194,7 @@ void NSSModule::QueryAddr(const FunctionCallbackInfo& args) { return env->ThrowError("wrong nss request type"); req_wrap->module = nss; + req_wrap->req_.data = req_wrap; req_wrap->Ref(); uv_queue_work(env->event_loop(), diff --git a/src/nss_wrap.cc b/src/nss_wrap.cc index a8766b92820d3d..e98a30cfd94a79 100644 --- a/src/nss_wrap.cc +++ b/src/nss_wrap.cc @@ -86,6 +86,7 @@ void NSSReqWrap::NameWork(uv_work_t* req) { char* tmpbuf6 = nullptr; size_t tmpbuf4len = 0; char* tmpbuf4 = nullptr; + char* tmpnewbuf; char* results; if (ghbn4 != nullptr && orig_family == 0) { @@ -106,11 +107,13 @@ void NSSReqWrap::NameWork(uv_work_t* req) { if (rc6 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) break; tmpbuf6len *= 2; - tmpbuf6 = static_cast(realloc(tmpbuf6, tmpbuf6len)); - if (tmpbuf6 == nullptr) { + tmpnewbuf = static_cast(realloc(tmpbuf6, tmpbuf6len)); + if (tmpnewbuf == nullptr) { + free(tmpbuf6); req_wrap->error = NSS_REQ_ERR_MALLOC; return; } + tmpbuf6 = tmpnewbuf; } if (rc6 != 0 && err == NETDB_INTERNAL) { @@ -195,11 +198,13 @@ void NSSReqWrap::NameWork(uv_work_t* req) { if (rc6 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) break; tmpbuf6len *= 2; - tmpbuf6 = static_cast(realloc(tmpbuf6, tmpbuf6len)); - if (tmpbuf6 == nullptr) { + tmpnewbuf = static_cast(realloc(tmpbuf6, tmpbuf6len)); + if (tmpnewbuf == nullptr) { + free(tmpbuf6); req_wrap->error = NSS_REQ_ERR_MALLOC; return; } + tmpbuf6 = tmpnewbuf; } if (rc6 != 0 && err == NETDB_INTERNAL) { @@ -242,13 +247,15 @@ void NSSReqWrap::NameWork(uv_work_t* req) { if (rc4 != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) break; tmpbuf4len *= 2; - tmpbuf4 = static_cast(realloc(tmpbuf4, tmpbuf4len)); - if (tmpbuf4 == nullptr) { + tmpnewbuf = static_cast(realloc(tmpbuf4, tmpbuf4len)); + if (tmpnewbuf == nullptr) { + free(tmpbuf4); if (orig_family == 0 && tmpbuf6 != nullptr) free(tmpbuf6); req_wrap->error = NSS_REQ_ERR_MALLOC; return; } + tmpbuf4 = tmpnewbuf; } if (rc4 != 0 && err == NETDB_INTERNAL) { @@ -432,6 +439,7 @@ void NSSReqWrap::AddrWork(uv_work_t* req) { int status; size_t tmpbuflen = 1024; char* tmpbuf; + char* tmpnewbuf; struct hostent result; if (ghba2 != nullptr) { @@ -448,11 +456,13 @@ void NSSReqWrap::AddrWork(uv_work_t* req) { if (rc != ERANGE || (err != NETDB_INTERNAL && err != TRY_AGAIN)) break; tmpbuflen *= 2; - tmpbuf = static_cast(realloc(tmpbuf, tmpbuflen)); - if (tmpbuf == nullptr) { + tmpnewbuf = static_cast(realloc(tmpbuf, tmpbuflen)); + if (tmpnewbuf == nullptr) { + free(tmpbuf); req_wrap->error = NSS_REQ_ERR_MALLOC; return; } + tmpbuf = tmpnewbuf; } if ((rc != 0 && err == NETDB_INTERNAL) || status != NSS_STATUS_SUCCESS) { From 5faa635d4341b0877f105515e88941e514047415 Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 22 Dec 2015 00:04:59 -0500 Subject: [PATCH 34/39] dns: fix UDP bind() usage --- lib/internal/dns.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/internal/dns.js b/lib/internal/dns.js index 1a46751ecad9e2..b831dec834254c 100644 --- a/lib/internal/dns.js +++ b/lib/internal/dns.js @@ -86,6 +86,9 @@ const DNS_DEFAULTS = { }, lookup: [ 'files', 'dns' ] }; +// `exclusive` is needed in case we are querying from a cluster worker (and +// especially from Windows where UDP socket sharing is not supported) +const UDP_OPTIONS = { port: 0, exclusive: true }; // Map DNS RCODE values to sensible string values const RCODE_V2S = { @@ -658,6 +661,7 @@ exports.lookup = function lookup(hostname, options, callback) { if (hostname) { var matchedFamily = isIP(hostname); + if (matchedFamily) { // TODO(mscdex): Return IPv4-mapped IPv6 address if `v4mapped === true` // and `matchedFamily === 4` ? @@ -1340,7 +1344,7 @@ function resolve(name, type, flags, cb) { socket.on('message', udpMsgHandler); socket.on('error', cleanup); socket.on('close', nextLookup); - socket.bind(0); + socket.bind(UDP_OPTIONS); } break; case 'files': From 35a63a88b22abbb442d91de27313d393bb41de40 Mon Sep 17 00:00:00 2001 From: Brian White Date: Tue, 22 Dec 2015 13:35:34 -0500 Subject: [PATCH 35/39] test: fix AsyncWrap provider check for new dns --- src/req-wrap-inl.h | 4 ++-- .../test-async-wrap-check-providers.js | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/req-wrap-inl.h b/src/req-wrap-inl.h index 63229260ec7517..e3222bad23ddad 100644 --- a/src/req-wrap-inl.h +++ b/src/req-wrap-inl.h @@ -27,8 +27,8 @@ ReqWrap::ReqWrap(Environment* env, template ReqWrap::~ReqWrap() { CHECK_EQ(req_.data, this); // Assert that someone has called Dispatched(). - CHECK_EQ(false, persistent().IsEmpty()); - persistent().Reset(); + if (!persistent().IsEmpty()) + persistent().Reset(); } template diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index a9116059e65a8f..97324fd6d658c2 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -12,21 +12,13 @@ const zlib = require('zlib'); const ChildProcess = require('child_process').ChildProcess; const StreamWrap = require('_stream_wrap').StreamWrap; const async_wrap = process.binding('async_wrap'); +const NSSReqWrap = process.binding('nss_wrap').NSSReqWrap; const pkeys = Object.keys(async_wrap.Providers); let keyList = pkeys.slice(); // Drop NONE keyList.splice(0, 1); -// TODO(mscdex): remove this when we can easily test with a dummy nss module -if (~keyList.indexOf('NSSREQWRAP')) - keyList.splice(keyList.indexOf('NSSREQWRAP'), 1); - -if (!process.oldDNS) { - const unused = ['GETADDRINFOREQWRAP', 'GETNAMEINFOREQWRAP', 'QUERYWRAP']; - keyList = keyList.filter(function(k) { return unused.indexOf(k) === -1; }); -} - function init(id) { keyList = keyList.filter(e => e != pkeys[id]); } @@ -98,6 +90,17 @@ zlib.createGzip(); new ChildProcess(); +if (common.isWindows || process.oldDNS) + keyList.splice(keyList.indexOf('NSSREQWRAP'), 1); + +if (!process.oldDNS) { + const unused = ['GETADDRINFOREQWRAP', 'GETNAMEINFOREQWRAP', 'QUERYWRAP']; + keyList = keyList.filter(function(k) { return unused.indexOf(k) === -1; }); + + if (!common.isWindows) + new NSSReqWrap('foobarbazhostname', -1); +} + process.on('exit', function() { if (keyList.length !== 0) { process._rawDebug('Not all keys have been used:'); From 1c76f546b712fa97731877300815d19bfd74e4de Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 23 Dec 2015 05:25:07 -0500 Subject: [PATCH 36/39] test: listen for more dns request retries This is like e5c37fd14b but for test-dns-domain-search. --- test/internet/test-dns-domain-search.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/internet/test-dns-domain-search.js b/test/internet/test-dns-domain-search.js index 455ba2770d6992..15a87de6e709e2 100644 --- a/test/internet/test-dns-domain-search.js +++ b/test/internet/test-dns-domain-search.js @@ -66,7 +66,9 @@ function UDPServerReply(replybuf, expbuf, nreqs) { var timer = setTimeout(function() { throw new Error(format('Timeout on UDP test (%s)', testName)); }, 100); - UDPServer.once('message', function(buf, rinfo) { + UDPServer.removeAllListeners('message'); + // Listen for multiple connections in case client retries request + UDPServer.on('message', function(buf, rinfo) { clearTimeout(timer); if (expbuf) { if (Array.isArray(expbuf)) { @@ -81,8 +83,8 @@ function UDPServerReply(replybuf, expbuf, nreqs) { } } UDPServer.send(replybuf, 0, replybuf.length, rinfo.port, rinfo.address); - if (--nreqs > 0) - UDPServerReply(replybuf, expbuf, nreqs); + if (--nreqs === 0) + UDPServer.removeAllListeners('message'); }); } @@ -103,7 +105,9 @@ function TCPServerReply(replybuf, expbuf, nreqs) { return s.once('readable', readN.bind(null, s, n, cb)); cb(r); } - TCPServer.once('connection', function(s) { + TCPServer.removeAllListeners('connection'); + // Listen for multiple connections in case client retries request + TCPServer.on('connection', function(s) { readN(s, 2, function readLength(buf) { readN(s, buf.readUInt16BE(0), function readBytes(buf) { clearTimeout(timer); @@ -123,8 +127,8 @@ function TCPServerReply(replybuf, expbuf, nreqs) { lenbuf.writeUInt16BE(replybuf.length, 0); s.write(lenbuf); s.write(replybuf); - if (--nreqs > 0) - TCPServerReply(replybuf, expbuf, nreqs); + if (--nreqs === 0) + TCPServer.removeAllListeners('connection'); }); }); }); From e5bb65b2faa0900b96cac63798e58ea2fa87c6ce Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 23 Dec 2015 17:00:37 -0500 Subject: [PATCH 37/39] test: improve internet DNS tests This fixes code consistency, formatting, and linting issues. It also arranges the tests alphabetically. --- test/internet/test-dns.js | 423 +++++++++++++++++++------------------- 1 file changed, 209 insertions(+), 214 deletions(-) diff --git a/test/internet/test-dns.js b/test/internet/test-dns.js index 4b3ec6bedff8d3..843d47c5a8cc6b 100644 --- a/test/internet/test-dns.js +++ b/test/internet/test-dns.js @@ -1,17 +1,16 @@ 'use strict'; -var common = require('../common'); -var assert = require('assert'), - dns = require('dns'), - net = require('net'), - isIP = net.isIP, - isIPv4 = net.isIPv4, - isIPv6 = net.isIPv6; -var util = require('util'); +const common = require('../common'); +const assert = require('assert'); +const dns = require('dns'); +const net = require('net'); +const isIP = net.isIP; +const isIPv4 = net.isIPv4; +const isIPv6 = net.isIPv6; -var expected = 0, - completed = 0, - running = false, - queue = []; +var expected = 0; +var completed = 0; +var running = false; +var queue = []; function TEST(f) { @@ -45,40 +44,52 @@ function checkWrap(req) { } -TEST(function test_reverse_bogus(done) { - var error; - - try { - var req = dns.reverse('bogus ip', function() { - assert.ok(false); +if (process.oldDNS) { + TEST(function test_cares_getaddrinfo(done) { + const cares = process.binding('cares_wrap'); + const req = new cares.GetAddrInfoReqWrap(); + const err = cares.getaddrinfo(req, 'nodejs.org', 4); + req.oncomplete = common.mustCall(function(err, domains) { + assert.strictEqual(err, 0); + assert.ok(Array.isArray(domains)); + assert.ok(domains.length >= 1); + assert.ok(typeof domains[0] === 'string'); }); - } catch (e) { - error = e; - } + }); +} - assert.ok(error instanceof Error); - assert.strictEqual(error.errno, 'EINVAL'); - done(); -}); +TEST(function test_lookup_all_mixed(done) { + const req = dns.lookup('www.google.com', {all: true}, function(err, ips) { + if (err) throw err; + assert.ok(Array.isArray(ips)); + assert.ok(ips.length > 0); -TEST(function test_resolveMx(done) { - var req = dns.resolveMx('gmail.com', function(err, result) { - if (err) throw err; + ips.forEach(function(ip) { + if (isIPv4(ip.address)) + assert.equal(ip.family, 4); + else if (isIPv6(ip.address)) + assert.equal(ip.family, 6); + else + assert(false); + }); - assert.ok(result.length > 0); + done(); + }); - for (var i = 0; i < result.length; i++) { - var item = result[i]; - assert.ok(item); - assert.ok(typeof item === 'object'); + checkWrap(req); +}); - assert.ok(item.exchange); - assert.ok(typeof item.exchange === 'string'); - assert.ok(typeof item.priority === 'number'); - } +TEST(function test_lookup_failure(done) { + const req = dns.lookup('does.not.exist', 4, function(err, ip, family) { + assert.ok(err instanceof Error); + + assert.strictEqual(err.errno, 'ENOTFOUND'); + assert.ok(!/ENOENT/.test(err.message)); + assert.strictEqual(err.hostname, 'does.not.exist'); + assert.ok(/does\.not\.exist/.test(err.message)); done(); }); @@ -87,17 +98,13 @@ TEST(function test_resolveMx(done) { }); -TEST(function test_resolveNs(done) { - var req = dns.resolveNs('rackspace.com', function(err, names) { - if (err) throw err; - - assert.ok(names.length > 0); +TEST(function test_lookup_failure2(done) { + const req = dns.lookup('nosuchhostimsure', function(err) { + assert(err instanceof Error); - for (var i = 0; i < names.length; i++) { - var name = names[i]; - assert.ok(name); - assert.ok(typeof name === 'string'); - } + assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... + assert.strictEqual(err.hostname, 'nosuchhostimsure'); + assert.ok(/nosuchhostimsure/.test(err.message)); done(); }); @@ -106,24 +113,14 @@ TEST(function test_resolveNs(done) { }); -TEST(function test_resolveSrv(done) { - var req = dns.resolveSrv('_jabber._tcp.google.com', function(err, result) { +TEST(function test_lookup_ip_all(done) { + const req = dns.lookup('127.0.0.1', {all: true}, function(err, ips, family) { if (err) throw err; - assert.ok(result.length > 0); - - for (var i = 0; i < result.length; i++) { - var item = result[i]; - assert.ok(item); - assert.ok(typeof item === 'object'); - - assert.ok(item.name); - assert.ok(typeof item.name === 'string'); - - assert.ok(typeof item.port === 'number'); - assert.ok(typeof item.priority === 'number'); - assert.ok(typeof item.weight === 'number'); - } + assert.ok(Array.isArray(ips)); + assert.ok(ips.length > 0); + assert.strictEqual(ips[0].address, '127.0.0.1'); + assert.strictEqual(ips[0].family, 4); done(); }); @@ -132,24 +129,12 @@ TEST(function test_resolveSrv(done) { }); -TEST(function test_resolveNaptr(done) { - var req = dns.resolveNaptr('sip2sip.info', function(err, result) { +TEST(function test_lookup_null(done) { + const req = dns.lookup(null, function(err, ip, family) { if (err) throw err; - assert.ok(result.length > 0); - - for (var i = 0; i < result.length; i++) { - var item = result[i]; - assert.ok(item); - assert.ok(typeof item === 'object'); - - assert.ok(typeof item.flags === 'string'); - assert.ok(typeof item.service === 'string'); - assert.ok(typeof item.regexp === 'string'); - assert.ok(typeof item.replacement === 'string'); - assert.ok(typeof item.order === 'number'); - assert.ok(typeof item.preference === 'number'); - } + assert.strictEqual(ip, null); + assert.strictEqual(family, 4); done(); }); @@ -158,33 +143,26 @@ TEST(function test_resolveNaptr(done) { }); -TEST(function test_resolveSoa(done) { - var req = dns.resolveSoa('nodejs.org', function(err, result) { +TEST(function test_lookup_null_all(done) { + const req = dns.lookup(null, {all: true}, function(err, ips, family) { if (err) throw err; - assert.ok(result); - assert.ok(typeof result === 'object'); - - assert.ok(typeof result.nsname === 'string'); - assert.ok(result.nsname.length > 0); - - assert.ok(typeof result.hostmaster === 'string'); - assert.ok(result.hostmaster.length > 0); + assert.ok(Array.isArray(ips)); + assert.strictEqual(ips.length, 0); - assert.ok(typeof result.serial === 'number'); - assert.ok((result.serial > 0) && (result.serial < 4294967295)); + done(); + }); - assert.ok(typeof result.refresh === 'number'); - assert.ok((result.refresh > 0) && (result.refresh < 2147483647)); + checkWrap(req); +}); - assert.ok(typeof result.retry === 'number'); - assert.ok((result.retry > 0) && (result.retry < 2147483647)); - assert.ok(typeof result.expire === 'number'); - assert.ok((result.expire > 0) && (result.expire < 2147483647)); +TEST(function test_lookupservice_invalid(done) { + const req = dns.lookupService('1.2.3.4', 80, function(err, host, service) { + assert(err instanceof Error); - assert.ok(typeof result.minttl === 'number'); - assert.ok((result.minttl >= 0) && (result.minttl < 2147483647)); + assert.strictEqual(err.code, 'ENOTFOUND'); + assert.ok(/1\.2\.3\.4/.test(err.message)); done(); }); @@ -193,18 +171,22 @@ TEST(function test_resolveSoa(done) { }); -TEST(function test_resolveCname(done) { - var req = dns.resolveCname('www.microsoft.com', function(err, names) { - if (err) throw err; - - assert.ok(names.length > 0); +TEST(function test_resolve_failure(done) { + const req = dns.resolve4('nosuchhostimsure', function(err) { + assert(err instanceof Error); - for (var i = 0; i < names.length; i++) { - var name = names[i]; - assert.ok(name); - assert.ok(typeof name === 'string'); + switch (err.code) { + case 'ENOTFOUND': + case 'ESERVFAIL': + break; + default: + assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... + break; } + assert.strictEqual(err.hostname, 'nosuchhostimsure'); + assert.ok(/nosuchhostimsure/.test(err.message)); + done(); }); @@ -212,12 +194,13 @@ TEST(function test_resolveCname(done) { }); -TEST(function test_resolveTxt(done) { - var req = dns.resolveTxt('google.com', function(err, records) { +TEST(function test_resolveCname(done) { + const req = dns.resolveCname('cname.statdns.net', function(err, names) { if (err) throw err; - assert.equal(records.length, 1); - assert.ok(util.isArray(records[0])); - assert.equal(records[0][0].indexOf('v=spf1'), 0); + + assert.equal(names.length, 1); + assert.strictEqual(names[0], 'www.statdns.net'); + done(); }); @@ -231,6 +214,7 @@ TEST(function test_resolveLoc(done) { dns.resolveLoc('statdns.net', function(err, records) { if (err) throw err; + assert.equal(records.length, 1); assert.deepEqual(records[0], { size: 0, @@ -240,47 +224,28 @@ TEST(function test_resolveLoc(done) { longitude: 2165095648, altitude: 9999800 }); - done(); - }); -}); - -TEST(function test_resolveSshp(done) { - if (!dns.resolveSshp) - return done(); - dns.resolveSshp('statdns.net', function(err, records) { - if (err) throw err; - assert.equal(records.length, 1); - assert.deepEqual(records[0], { - algorithm: 'DSA', - fpType: 'SHA1', - fp: '123456789abcdef67890123456789abcdef67890' - }); done(); }); }); -TEST(function test_lookup_failure(done) { - var req = dns.lookup('does.not.exist', 4, function(err, ip, family) { - assert.ok(err instanceof Error); - assert.strictEqual(err.errno, dns.NOTFOUND); - assert.strictEqual(err.errno, 'ENOTFOUND'); - assert.ok(!/ENOENT/.test(err.message)); - assert.ok(/does\.not\.exist/.test(err.message)); +TEST(function test_resolveMx(done) { + const req = dns.resolveMx('gmail.com', function(err, result) { + if (err) throw err; - done(); - }); + assert.ok(result.length > 0); - checkWrap(req); -}); + for (let i = 0; i < result.length; i++) { + const item = result[i]; + assert.ok(item); + assert.ok(typeof item === 'object'); + assert.ok(item.exchange); + assert.ok(typeof item.exchange === 'string'); -TEST(function test_lookup_null(done) { - var req = dns.lookup(null, function(err, ip, family) { - if (err) throw err; - assert.strictEqual(ip, null); - assert.strictEqual(family, 4); + assert.ok(typeof item.priority === 'number'); + } done(); }); @@ -289,13 +254,19 @@ TEST(function test_lookup_null(done) { }); -TEST(function test_lookup_ip_all(done) { - var req = dns.lookup('127.0.0.1', {all: true}, function(err, ips, family) { +TEST(function test_resolveNaptr(done) { + const req = dns.resolveNaptr('statdns.net', function(err, result) { if (err) throw err; - assert.ok(Array.isArray(ips)); - assert.ok(ips.length > 0); - assert.strictEqual(ips[0].address, '127.0.0.1'); - assert.strictEqual(ips[0].family, 4); + + assert.equal(result.length, 1); + assert.deepEqual(result[0], { + flags: 'u', + service: 'E2U+web:http', + regexp: '!^.*$!https://www.statdns.net!', + replacement: '', + order: 100, + preference: 100 + }); done(); }); @@ -304,11 +275,17 @@ TEST(function test_lookup_ip_all(done) { }); -TEST(function test_lookup_null_all(done) { - var req = dns.lookup(null, {all: true}, function(err, ips, family) { +TEST(function test_resolveNs(done) { + const req = dns.resolveNs('rackspace.com', function(err, names) { if (err) throw err; - assert.ok(Array.isArray(ips)); - assert.strictEqual(ips.length, 0); + + assert.ok(names.length > 0); + + for (let i = 0; i < names.length; i++) { + const name = names[i]; + assert.ok(name); + assert.ok(typeof name === 'string'); + } done(); }); @@ -317,33 +294,31 @@ TEST(function test_lookup_null_all(done) { }); -TEST(function test_lookup_all_mixed(done) { - var req = dns.lookup('www.google.com', {all: true}, function(err, ips) { - if (err) throw err; - assert.ok(Array.isArray(ips)); - assert.ok(ips.length > 0); +TEST(function test_reverse_bogus(done) { + var error; - ips.forEach(function(ip) { - if (isIPv4(ip.address)) - assert.equal(ip.family, 4); - else if (isIPv6(ip.address)) - assert.equal(ip.family, 6); - else - assert(false); + try { + const req = dns.reverse('bogus ip', function() { + assert.ok(false); }); + } catch (e) { + error = e; + } - done(); - }); + assert.ok(error instanceof Error); + assert.strictEqual(error.errno, 'EINVAL'); - checkWrap(req); + done(); }); -TEST(function test_lookupservice_invalid(done) { - var req = dns.lookupService('1.2.3.4', 80, function(err, host, service) { +TEST(function test_reverse_failure(done) { + const req = dns.reverse('0.0.0.0', function(err) { assert(err instanceof Error); - assert.strictEqual(err.code, 'ENOTFOUND'); - assert.ok(/1\.2\.3\.4/.test(err.message)); + + assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... + assert.strictEqual(err.hostname, '0.0.0.0'); + assert.ok(/0\.0\.0\.0/.test(err.message)); done(); }); @@ -352,12 +327,33 @@ TEST(function test_lookupservice_invalid(done) { }); -TEST(function test_reverse_failure(done) { - var req = dns.reverse('0.0.0.0', function(err) { - assert(err instanceof Error); - assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... - assert.strictEqual(err.hostname, '0.0.0.0'); - assert.ok(/0\.0\.0\.0/.test(err.message)); +TEST(function test_resolveSoa(done) { + const req = dns.resolveSoa('nodejs.org', function(err, result) { + if (err) throw err; + + assert.ok(result); + assert.ok(typeof result === 'object'); + + assert.ok(typeof result.nsname === 'string'); + assert.ok(result.nsname.length > 0); + + assert.ok(typeof result.hostmaster === 'string'); + assert.ok(result.hostmaster.length > 0); + + assert.ok(typeof result.serial === 'number'); + assert.ok((result.serial > 0) && (result.serial < 4294967295)); + + assert.ok(typeof result.refresh === 'number'); + assert.ok((result.refresh > 0) && (result.refresh < 2147483647)); + + assert.ok(typeof result.retry === 'number'); + assert.ok((result.retry > 0) && (result.retry < 2147483647)); + + assert.ok(typeof result.expire === 'number'); + assert.ok((result.expire > 0) && (result.expire < 2147483647)); + + assert.ok(typeof result.minttl === 'number'); + assert.ok((result.minttl >= 0) && (result.minttl < 2147483647)); done(); }); @@ -366,12 +362,17 @@ TEST(function test_reverse_failure(done) { }); -TEST(function test_lookup_failure(done) { - var req = dns.lookup('nosuchhostimsure', function(err) { - assert(err instanceof Error); - assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... - assert.strictEqual(err.hostname, 'nosuchhostimsure'); - assert.ok(/nosuchhostimsure/.test(err.message)); +TEST(function test_resolveSrv(done) { + const req = dns.resolveSrv('_sip._tcp.statdns.net', function(err, result) { + if (err) throw err; + + assert.equal(result.length, 1); + assert.deepEqual(result[0], { + priority: 0, + weight: 5, + port: 5060, + name: 'sipserver.statdns.net' + }); done(); }); @@ -380,49 +381,43 @@ TEST(function test_lookup_failure(done) { }); -TEST(function test_resolve_failure(done) { - var req = dns.resolve4('nosuchhostimsure', function(err) { - assert(err instanceof Error); +TEST(function test_resolveSshp(done) { + if (!dns.resolveSshp) + return done(); - switch (err.code) { - case 'ENOTFOUND': - case 'ESERVFAIL': - break; - default: - assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... - break; - } + dns.resolveSshp('statdns.net', function(err, records) { + if (err) throw err; - assert.strictEqual(err.hostname, 'nosuchhostimsure'); - assert.ok(/nosuchhostimsure/.test(err.message)); + assert.equal(records.length, 1); + assert.deepEqual(records[0], { + algorithm: 'DSA', + fpType: 'SHA1', + fp: '123456789abcdef67890123456789abcdef67890' + }); done(); }); - - checkWrap(req); }); -var getaddrinfoCallbackCalled = false; +TEST(function test_resolveTxt(done) { + const req = dns.resolveTxt('statdns.net', function(err, records) { + if (err) throw err; -console.log('looking up nodejs.org...'); + assert.equal(records.length, 1); + assert.deepEqual(records[0], [ + 'Get more information on : http://www.statdns.net' + ]); -var cares = process.binding('cares_wrap'); -var req = new cares.GetAddrInfoReqWrap(); -var err = cares.getaddrinfo(req, 'nodejs.org', 4); + done(); + }); + + checkWrap(req); +}); -req.oncomplete = function(err, domains) { - assert.strictEqual(err, 0); - console.log('nodejs.org = ', domains); - assert.ok(Array.isArray(domains)); - assert.ok(domains.length >= 1); - assert.ok(typeof domains[0] == 'string'); - getaddrinfoCallbackCalled = true; -}; process.on('exit', function() { console.log(completed + ' tests completed'); assert.equal(running, false); assert.strictEqual(expected, completed); - assert.ok(getaddrinfoCallbackCalled); }); From 1bc86f946703df046d3eb15ec1f981729dadbeec Mon Sep 17 00:00:00 2001 From: Brian White Date: Wed, 23 Dec 2015 17:01:29 -0500 Subject: [PATCH 38/39] doc: first go at improving, updating dns docs --- doc/api/dns.markdown | 309 ++++++++++++++++++++++++++----------------- 1 file changed, 189 insertions(+), 120 deletions(-) diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index 1b01f0ee1cead7..48e5280fc7b1ad 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -6,11 +6,12 @@ Use `require('dns')` to access this module. This module contains functions that belong to two different categories: -1) Functions that use the underlying operating system facilities to perform -name resolution, and that do not necessarily do any network communication. -This category contains only one function: [`dns.lookup()`][]. __Developers -looking to perform name resolution in the same way that other applications on -the same operating system behave should use [`dns.lookup()`][].__ +1) Functions that utilize resources similar to the operating system's +name/address resolving facilities to perform name resolution, and that do not +necessarily perform any network communication. This category contains only one +function: [`dns.lookup()`][]. __Developers looking to perform name resolution in +a way similar to that of other applications on the same operating system should +use [`dns.lookup()`][].__ Here is an example that does a lookup of `www.google.com`. @@ -21,13 +22,11 @@ Here is an example that does a lookup of `www.google.com`. }); 2) Functions that connect to an actual DNS server to perform name resolution, -and that _always_ use the network to perform DNS queries. This category -contains all functions in the `dns` module but [`dns.lookup()`][]. These -functions do not use the same set of configuration files than what -[`dns.lookup()`][] uses. For instance, _they do not use the configuration from -`/etc/hosts`_. These functions should be used by developers who do not want to -use the underlying operating system's facilities for name resolution, and -instead want to _always_ perform DNS queries. +and that _always_ and _only_ use the network to perform DNS queries. This means +for instance, _the contents of "hosts" files (e.g. `/etc/hosts`) are not +considered_. This category contains all functions in the `dns` module but +[`dns.lookup()`][]. These functions should be used by developers who do not want +to _always_ perform queries to a nameserver over the network. Here is an example which resolves `'www.google.com'` then reverse resolves the IP addresses which are returned. @@ -50,34 +49,34 @@ resolves the IP addresses which are returned. }); There are subtle consequences in choosing one or another, please consult the -[Implementation considerations section][] for more information. +[implementation considerations section][] for more information. ## dns.getServers() Returns an array of IP addresses as strings that are currently being used for -resolution +resolution. ## dns.lookup(hostname[, options], callback) Resolves a hostname (e.g. `'google.com'`) into the first found A (IPv4) or -AAAA (IPv6) record. `options` can be an object or integer. If `options` is -not provided, then IP v4 and v6 addresses are both valid. If `options` is -an integer, then it must be `4` or `6`. +AAAA (IPv6) record. -Alternatively, `options` can be an object containing these properties: +`options` can be an object or integer. If `options` is not provided, then IPv4 +and IPv6 addresses are both valid. If `options` is an integer, then it must be +`4` or `6` to return addresses of that particular type. + +Alternatively, `options` can be an object containing any of these properties: * `family` {Number} - The record family. If present, must be the integer - `4` or `6`. If not provided, both IP v4 and v6 addresses are accepted. + `4` or `6`. If not provided, both IPv4 and IPv6 addresses are accepted. * `hints`: {Number} - If present, it should be one or more of the supported - `getaddrinfo` flags. If `hints` is not provided, then no flags are passed to - `getaddrinfo`. Multiple flags can be passed through `hints` by logically - `OR`ing their values. - See [supported `getaddrinfo` flags][] below for more information on supported - flags. -* `all`: {Boolean} - When `true`, the callback returns all resolved addresses - in an array, otherwise returns a single address. Defaults to `false`. + flags. If `hints` is not provided, then no flags are passed to the resolver. + Multiple flags can be passed by logically OR-ing flag values. + See [supported flags][] for more information on supported flags. +* `all`: {Boolean} - When `true`, the callback receives all resolved addresses + in an array, instead of a single address. Defaults to `false`. -All properties are optional. An example usage of options is shown below. +All properties are optional. An example usage of `options`: ``` { @@ -87,98 +86,148 @@ All properties are optional. An example usage of options is shown below. } ``` -The callback has arguments `(err, address, family)`. `address` is a string -representation of an IP v4 or v6 address. `family` is either the integer 4 or 6 -and denotes the family of `address` (not necessarily the value initially passed -to `lookup`). +The `callback` has arguments `(err, address, family)`. `address` is a string +representation of an IPv4 or IPv6 address. `family` is either the integer `4` or +`6` and denotes the family of `address`. -With the `all` option set, the arguments change to `(err, addresses)`, with -`addresses` being an array of objects with the properties `address` and -`family`. +With the `all` option set, the `callback` arguments change to +`(err, addresses)`, with `addresses` being an array of objects with `address` +and `family` properties. -On error, `err` is an [`Error`][] object, where `err.code` is the error code. -Keep in mind that `err.code` will be set to `'ENOENT'` not only when -the hostname does not exist but also when the lookup fails in other ways -such as no available file descriptors. +When an error occurs, `err` is set to an [`Error`][] object, where `err.code` is +one of the [error codes][]. Keep in mind that `err.code` will be set to +`'ENOENT'` not only when the `hostname` does not exist but also when the lookup +fails in other ways such as when no file descriptors are available. `dns.lookup()` doesn't necessarily have anything to do with the DNS protocol. It's only an operating system facility that can associate name with addresses, -and vice versa. - -Its implementation can have subtle but important consequences on the behavior -of any Node.js program. Please take some time to consult the [Implementation -considerations section][] before using it. +and vice versa. Its implementation can have subtle but important consequences on +the behavior of any Node.js program. Please take some time to consult the +[Implementation considerations section][] before using it. ## dns.lookupService(address, port, callback) -Resolves the given address and port into a hostname and service using -`getnameinfo`. +Resolves the given `address` and `port` into a `hostname` and `service`. The callback has arguments `(err, hostname, service)`. The `hostname` and `service` arguments are strings (e.g. `'localhost'` and `'http'` respectively). -On error, `err` is an [`Error`][] object, where `err.code` is the error code. +When an error occurs, `err` is set to an [`Error`][] object, where `err.code` is +one of the [error codes][]. ## dns.resolve(hostname[, rrtype], callback) -Resolves a hostname (e.g. `'google.com'`) into an array of the record types -specified by rrtype. +Resolves a `hostname` (e.g. `'google.com'`) into an array of the record types +specified by `rrtype`. -Valid rrtypes are: +Valid `rrtype`s are: - * `'A'` (IPV4 addresses, default) - * `'AAAA'` (IPV6 addresses) + * `'A'` (IPv4 address records) **(default)** + * `'AAAA'` (IPv6 address records) + * `'CNAME'` (canonical name records) + * `'LOC'` (geographical location records) * `'MX'` (mail exchange records) - * `'TXT'` (text records) - * `'SRV'` (SRV records) - * `'PTR'` (used for reverse IP lookups) + * `'NAPTR'` (name authority pointer records) * `'NS'` (name server records) - * `'CNAME'` (canonical name records) + * `'PTR'` (reverse IP lookup) * `'SOA'` (start of authority record) + * `'SRV'` (service records) + * `'SSHP'` (SSH host fingerprint records) + * `'TXT'` (text records) -The callback has arguments `(err, addresses)`. The type of each item -in `addresses` is determined by the record type, and described in the -documentation for the corresponding lookup methods below. +The `callback` has arguments `(err, addresses)`. The type of each item in +`addresses` is determined by the record type and is described in the +documentation for the corresponding `dns.resolve*()` methods. -On error, `err` is an [`Error`][] object, where `err.code` is -one of the error codes listed below. +When an error occurs, `err` is set to an [`Error`][] object, where `err.code` is +one of the [error codes][]. ## dns.resolve4(hostname, callback) -The same as [`dns.resolve()`][], but only for IPv4 queries (`A` records). -`addresses` is an array of IPv4 addresses (e.g. -`['74.125.79.104', '74.125.79.105', '74.125.79.106']`). +The same as [`dns.resolve()`][], but only for IPv4 (`A`) records. + +`addresses` is an array of IPv4 addresses +(e.g. `['74.125.79.104', '74.125.79.105', '74.125.79.106']`). ## dns.resolve6(hostname, callback) -The same as [`dns.resolve4()`][] except for IPv6 queries (an `AAAA` query). +The same as [`dns.resolve4()`][] except for IPv6 (`AAAA`) records. ## dns.resolveCname(hostname, callback) -The same as [`dns.resolve()`][], but only for canonical name records (`CNAME` -records). `addresses` is an array of the canonical name records available for -`hostname` (e.g., `['bar.example.com']`). +The same as [`dns.resolve()`][], but only for canonical name (`CNAME`) records. + +`addresses` is an array of the `CNAME` records available for `hostname` +(e.g., `['bar.example.com']`). + +## dns.resolveLoc(hostname, callback) + +The same as [`dns.resolve()`][], but only for geographical location (`LOC`) +records. + +`addresses` is an array of the `LOC` records available for `hostname`. + +`LOC` records have the following structure: + +``` +{ + size: 0, + horizPrec: 22, + vertPrec: 19, + latitude: 2336026648, + longitude: 2165095648, + altitude: 9999800 +} +``` ## dns.resolveMx(hostname, callback) -The same as [`dns.resolve()`][], but only for mail exchange queries -(`MX` records). +The same as [`dns.resolve()`][], but only for mail exchange (`MX`) records. + +`addresses` is an array of `MX` records available for `hostname`. + +`MX` records have the following structure: + +``` +{ + priority: 10, + exchange: 'mx.example.com' +} +``` + +## dns.resolveNaptr(hostname, callback) + +The same as [`dns.resolve()`][], but only for name authority pointer (`NAPTR`) +records. + +`addresses` is an array of the `NAPTR` records available for `hostname`. -`addresses` is an array of MX records, each with a priority and an exchange -attribute (e.g. `[{'priority': 10, 'exchange': 'mx.example.com'},...]`). +`NAPTR` records have the following structure: + +``` +{ + flags: 'u', + service: 'foo', + regexp: '!^.*$!https://www.example.net!', + replacement: '', + order: 100, + preference: 100 +} +``` ## dns.resolveNs(hostname, callback) -The same as [`dns.resolve()`][], but only for name server records -(`NS` records). `addresses` is an array of the name server records available -for `hostname` (e.g., `['ns1.example.com', 'ns2.example.com']`). +The same as [`dns.resolve()`][], but only for name server (`NS`) records. + +`addresses` is an array of the `NS` records available for `hostname` +(e.g., `['ns1.example.com', 'ns2.example.com']`). ## dns.resolveSoa(hostname, callback) -The same as [`dns.resolve()`][], but only for start of authority record queries -(`SOA` record). +The same as [`dns.resolve()`][], but only for start of authority (`SOA`) +records. `addresses` is an object with the following structure: @@ -196,18 +245,55 @@ The same as [`dns.resolve()`][], but only for start of authority record queries ## dns.resolveSrv(hostname, callback) -The same as [`dns.resolve()`][], but only for service records (`SRV` records). -`addresses` is an array of the SRV records available for `hostname`. Properties -of SRV records are priority, weight, port, and name (e.g., -`[{'priority': 10, 'weight': 5, 'port': 21223, 'name': 'service.example.com'}, ...]`). +The same as [`dns.resolve()`][], but only for service (`SRV`) records. + +`addresses` is an array of the `SRV` records available for `hostname`. + +`SRV` records have the following structure: + +``` +{ + priority: 10, + weight: 5, + port: 21223, + name: 'service.example.com' +} +``` + +## dns.resolveSshp(hostname, callback) + +The same as [`dns.resolve()`][], but only for SSH host fingerprint (`SSHP`) +records. + +`addresses` is an array of the `SSHP` records available for `hostname`. + +`SSHFP` records have the following structure: + +``` +{ + nsname: 'ns.example.com', + hostmaster: 'root.example.com', + serial: 2013101809, + refresh: 10000, + retry: 2400, + expire: 604800, + minttl: 3600 +} +``` ## dns.resolveTxt(hostname, callback) -The same as [`dns.resolve()`][], but only for text queries (`TXT` records). -`addresses` is a 2-d array of the text records available for `hostname` (e.g., -`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of -one record. Depending on the use case, the could be either joined together or -treated separately. +The same as [`dns.resolve()`][], but only for text (`TXT`) records. + +`addresses` is a 2-D array of the `TXT` records available for `hostname`. Each +sub-array contains one or more chunks of one `TXT` record. Depending on the use +case, they could be either joined together or treated separately. + +`TXT` records may look like: + +``` +[ 'v=spf1 ip4:0.0.0.0 ', '~all' ] +``` ## dns.reverse(ip, callback) @@ -257,17 +343,16 @@ Each DNS query can return one of the following error codes: - `dns.ADDRGETNETWORKPARAMS`: Could not find GetNetworkParams function. - `dns.CANCELLED`: DNS query cancelled. -## Supported getaddrinfo flags +## Supported flags -The following flags can be passed as hints to [`dns.lookup()`][]. +The following flags can be passed as `hints` to [`dns.lookup()`][]. - `dns.ADDRCONFIG`: Returned address types are determined by the types of addresses supported by the current system. For example, IPv4 addresses are only returned if the current system has at least one IPv4 address configured. Loopback addresses are not considered. - `dns.V4MAPPED`: If the IPv6 family was specified, but no IPv6 addresses were -found, then return IPv4 mapped IPv6 addresses. Note that it is not supported -on some operating systems (e.g FreeBSD 10.1). +found, then return IPv4-mapped IPv6 addresses. ## Implementation considerations @@ -278,42 +363,26 @@ significant consequences on the behavior of Node.js programs. ### dns.lookup -Under the hood, [`dns.lookup()`][] uses the same operating system facilities -as most other programs. For instance, [`dns.lookup()`][] will almost always -resolve a given name the same way as the `ping` command. On most POSIX-like -operating systems, the behavior of the [`dns.lookup()`][] function can be -tweaked by changing settings in `nsswitch.conf(5)` and/or `resolv.conf(5)`, but -be careful that changing these files will change the behavior of all other -programs running on the same operating system. - -Though the call will be asynchronous from JavaScript's perspective, it is -implemented as a synchronous call to `getaddrinfo(3)` that runs on libuv's -threadpool. Because libuv's threadpool has a fixed size, it means that if for -whatever reason the call to `getaddrinfo(3)` takes a long time, other -operations that could run on libuv's threadpool (such as filesystem -operations) will experience degraded performance. In order to mitigate this -issue, one potential solution is to increase the size of libuv's threadpool by -setting the 'UV_THREADPOOL_SIZE' environment variable to a value greater than -4 (its current default value). For more information on libuv's threadpool, see -[the official libuv documentation][]. +Under the hood, [`dns.lookup()`][] utilizes resources similar to the operating +system's facilities used by many other programs. For instance, +[`dns.lookup()`][] will almost always resolve a given name the same way as the +`ping` command. On most POSIX-like operating systems, the behavior of the +[`dns.lookup()`][] function can be tweaked by changing settings in +`nsswitch.conf(5)` and/or `resolv.conf(5)`, but be careful that changing these +files will change the behavior of all other programs running on the same +operating system. ### dns.resolve, functions starting with dns.resolve and dns.reverse -These functions are implemented quite differently than [`dns.lookup()`][]. They -do not use `getaddrinfo(3)` and they _always_ perform a DNS query on the -network. This network communication is always done asynchronously, and does not -use libuv's threadpool. - -As a result, these functions cannot have the same negative impact on other -processing that happens on libuv's threadpool that [`dns.lookup()`][] can have. - -They do not use the same set of configuration files than what [`dns.lookup()`][] -uses. For instance, _they do not use the configuration from `/etc/hosts`_. +These functions are implemented a little differently than [`dns.lookup()`][], in +that they _always_ and _only_ perform DNS queries over the network. This means +for instance, _the contents of "hosts" files (e.g. `/etc/hosts`) are not +considered_. [`dns.lookup()`]: #dns_dns_lookup_hostname_options_callback [`dns.resolve()`]: #dns_dns_resolve_hostname_rrtype_callback [`dns.resolve4()`]: #dns_dns_resolve4_hostname_callback [`Error`]: errors.html#errors_class_error -[Implementation considerations section]: #dns_implementation_considerations -[supported `getaddrinfo` flags]: #dns_supported_getaddrinfo_flags -[the official libuv documentation]: http://docs.libuv.org/en/latest/threadpool.html +[implementation considerations section]: #dns_implementation_considerations +[supported flags]: #dns_supported_flags +[error codes]: #dns_error_codes From fa1aef4eb77f2d889395bf6870446d84f25013a3 Mon Sep 17 00:00:00 2001 From: Brian White Date: Thu, 24 Dec 2015 16:44:33 -0500 Subject: [PATCH 39/39] doc: document wildcard DNS request type --- doc/api/dns.markdown | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index 48e5280fc7b1ad..402e35a69d0e86 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -135,6 +135,7 @@ Valid `rrtype`s are: * `'SRV'` (service records) * `'SSHP'` (SSH host fingerprint records) * `'TXT'` (text records) + * `'*'` (any records) The `callback` has arguments `(err, addresses)`. The type of each item in `addresses` is determined by the record type and is described in the @@ -295,6 +296,22 @@ case, they could be either joined together or treated separately. [ 'v=spf1 ip4:0.0.0.0 ', '~all' ] ``` +## dns.resolveAny(hostname, callback) + +The same as [`dns.resolve()`][], but for *any* records for `hostname`. + +`addresses` is an array of objects describing the records available for +`hostname`. + +Each object may look like: + +``` +{ + type: 'A', + data: '192.168.100.10' +} +``` + ## dns.reverse(ip, callback) Reverse resolves an ip address to an array of hostnames.