diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index 1b01f0ee1cead7..402e35a69d0e86 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,149 @@ 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) + * `'*'` (any 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, each with a priority and an exchange -attribute (e.g. `[{'priority': 10, 'exchange': 'mx.example.com'},...]`). +`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`. + +`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 +246,71 @@ 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.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) @@ -257,17 +360,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 +380,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 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..b831dec834254c --- /dev/null +++ b/lib/internal/dns.js @@ -0,0 +1,2882 @@ +/* + TODO(mscdex): + - 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 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; +// 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; + +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}))?$/; +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(mscdex): improve default nameserver for IPv6-only systems + nameserver: [ '127.0.0.1' ], + search: (~HOSTNAME.indexOf('.') + ? [ 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, + rotate: false, // round-robin nameservers instead of always trying first one + singleRequest: false, // perform IPv4 and IPv6 lookups sequentially? + timeout: 5000, // ms + use_vc: false // force TCP for all DNS queries + }, + 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 = { + // 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; + +// 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 = { + nameserver: null, + search: null, + sortlist: null, + options: { + attempts: null, + inet6: null, + ndots: null, + rotate: null, + singleRequest: null, + timeout: 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 inet6 = dnsConfig.options.inet6; + var v4mapped = ((hints & V4MAPPED) > 0 || inet6); + 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(mscdex): 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) { + 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) { + // 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) { + 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 || (family === 0 && inet6)) && + triedIPv4); + var addr; + if (family === 0) + family = (triedIPv4 ? 4 : 6); + if (all) { + for (var i = 0; i < answers.length; ++i) { + addr = answers[i].data; + if (needv4Mapped) + addr = '::ffff:' + addr; + answers[i] = { + address: addr, + family: family + }; + } + callback(null, answers); + } else { + addr = answers[0].data; + if (needv4Mapped) + addr = '::ffff:' + addr; + callback(null, addr, family); + } + } else { + if (all) { + for (var i = 0; i < answers.length; ++i) + answers[i] = answers[i].data; + callback(null, answers); + } else { + callback(null, answers[0].data); + } + } + }); +}; + +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, flags, 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" argument 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('Invalid arguments'); + + if (isIP(addr) === 0) + 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'); + } + 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(mscdex): 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 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)); + 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 nameserver; + var nssReq; + var lastStatus; + var ndots; + var qtype; + var qtypeName; + var curName; + var socket; + var reqBuf; + var timer; + var origName; + + // 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)); + origName = 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, origName); + ret = undefined; + } + + if (async) + process.nextTick(cb, err, ret); + else + cb(err, ret); + } + + // 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 { + tcpBuf = null; + tcpBufLen = 0; + tcpLen = -0; + socket.removeListener('data', tcpOnData); + socket.destroy(); + } + } + + function udpMsgHandler(msg, rinfo) { + if (rinfo.address !== nameserver[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 = nameserver; + 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 + + // 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' && + 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(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 + 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 (!lookupMethod) + return doCb(errnoException('ENOTFOUND', origName), undefined, false); + + switch (lookupMethod) { + case 'dns': + if (!retry) { + // Advance to the next nameserver + 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; + } + + if (!nameserver) { + // 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); + } + + 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, nameserver[1]); + } else { + socket = new UDPSocket(nameserver[0] === 4 ? 'udp4' : 'udp6'); + socket.on('listening', udpListening); + socket.on('message', udpMsgHandler); + socket.on('error', cleanup); + socket.on('close', nextLookup); + socket.bind(UDP_OPTIONS); + } + 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; + default: + var m; + 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', origName), undefined, false); + else if (lastStatus === NSS_REQ_ERR_UNAVAIL) + doCb(errnoException('ESERVFAIL', origName), undefined, false); + else if (lastStatus === NSS_REQ_ERR_TRYAGAIN) + doCb(errnoException('ESERVFAIL', origName), 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(mscdex): fill in when available + data: results + }]; + } else { + for (var i = 0; i < results.length; ++i) { + results[i] = { + name: name, + type: type, + ttl: 0, // TODO(mscdex): 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); + } + } + } +} + +// Create a properly formatted Buffer from DNS request parameters +function reqToBuf(qtype, name, origName) { + 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, name); + // Label length byte + label content + namelen += 1 + Buffer.byteLength(name[i], 'ascii'); + } + if (namelen > 255) + return errnoException(exports.NOTFOUND, name); + + 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(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', + '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(mscdex): 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(mscdex): 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; j < val.length; ++j) { + var network = parseAddrMask(val[j], false); + if (network !== undefined) + val[j] = network; + else { + spliceOne(val, j); + --j; + } + } + if (val.length) + ret.sortlist = val; + break; + case 'options': + fillIn(ret.options, parseResolvOptions(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 parseResolvOptions(options) { + var ret = { + attempts: null, + inet6: null, + ndots: null, + rotate: null, + singleRequest: null, + timeout: null, + use_vc: null + }; + for (var i = 0; i < options.length; ++i) { + var m = REGEX_OPTS.exec(options[i]); + if (m) { + var 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); + if (val <= 15) + 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); + if (val <= 30) + ret.timeout = val; + } + break; + case 'use-vc': + ret.use_vc = true; + break; + } + } + } + return ret; +} + +function parseNsswitchConf() { + // TODO(mscdex): search for explicitly defined services file path too + var ret = false; + var seen = []; + + 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(); + order = order.match(/(?:\[[^\]]+\])|(?:[^ \f\t\v,]+)/g); + for (var i = 0, loc; i < order.length; ++i) { + loc = order[i].toLowerCase(); + if (loc && loc[0] === '[' && loc[loc.length - 1] === ']') { + // Parse [!]status=action pair(s) + loc = loc.slice(1, -1).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 (typeof loc === 'string' && loc !== 'dns' && loc !== 'files') + loc = order[i]; + if (!ret) + ret = [loc]; + else + ret.push(loc); + } + } + } catch (ex) {} + + if (ret) + ret = { lookup: ret }; + 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 || ret.indexOf('dns') === -1)) { + if (!ret) + ret = ['dns']; + else + ret.push('dns'); + } else if (loc === 'hosts' && (!ret || ret.indexOf('files') === -1)) { + if (!ret) + ret = ['files']; + else + ret.push('files'); + } + } + } + } catch (ex) {} + + if (ret) + ret = { lookup: ret }; + + 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(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(); + 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, parseResolvOptions(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); + + 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 (typeof loc === 'string' && loc !== 'dns' && loc !== 'files') { + 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]), + dnsConfig.nameserver[i] ]; + } +})(); diff --git a/node.gyp b/node.gyp index 4f096f580ab1d7..f23f7fd5ec638c 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', @@ -138,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', @@ -172,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/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) \ diff --git a/src/env.h b/src/env.h index 743bf057e8584d..02532374a4cb6d 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_string, "hasByAddr") \ + V(hasbyname_string, "hasByName") \ V(heap_total_string, "heapTotal") \ V(heap_used_string, "heapUsed") \ V(hostmaster_string, "hostmaster") \ diff --git a/src/node.cc b/src/node.cc index 894c8f76416275..0d091ac7823335 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,7 @@ static void PrintHelp() { #if HAVE_OPENSSL " --tls-cipher-list=val use an alternative default TLS cipher list\n" #endif + " --use-old-dns use c-ares for DNS resolution\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 +3409,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/src/nss_module.cc b/src/nss_module.cc new file mode 100644 index 00000000000000..0ea529e10c2be6 --- /dev/null +++ b/src/nss_module.cc @@ -0,0 +1,228 @@ +#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_); + free(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)); + if (name == nullptr) { + return env->ThrowError("malloc failed"); + } + +#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) { + free(name); + 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); + } + + snprintf(name, name_len, "_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(mscdex): 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); + free(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_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); +#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->req_.data = req_wrap; + + 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->req_.data = req_wrap; + + 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..9686c226d5bbbd --- /dev/null +++ b/src/nss_module.h @@ -0,0 +1,83 @@ +#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" // NOLINT(build/include_order) + +// 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 { + +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..e98a30cfd94a79 --- /dev/null +++ b/src/nss_wrap.cc @@ -0,0 +1,584 @@ +#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; + +#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 + +#ifndef INADDRSZ +# define INADDRSZ 4 +#endif +#ifndef IN6ADDRSZ +# define IN6ADDRSZ 16 +#endif + +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); + } +} + + +void NSSReqWrap::NameWork(uv_work_t* req) { +#ifndef _WIN32 + 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(mscdex): 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* tmpnewbuf; + 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; + 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) { + 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; + 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) { + 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; + 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) { + 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); + } +#endif +} + + +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()); + + 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) { +#ifndef _WIN32 + 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(mscdex): allow configurable ttl? + int32_t ttl = INT32_MAX; + int err; + int rc; + int status; + size_t tmpbuflen = 1024; + char* tmpbuf; + char* tmpnewbuf; + 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; + 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) { + 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; + 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); + } +#endif +} + + +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..d9611403075493 --- /dev/null +++ b/src/nss_wrap.h @@ -0,0 +1,81 @@ +#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" // NOLINT(build/include_order) + +#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(); + + size_t self_size() const override { return sizeof(*this); } + + void Ref(); + + void Unref(); + + 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_ 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/common.js b/test/common.js index bd9d4956c901ce..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 = []; diff --git a/test/internet/test-dns-domain-search.js b/test/internet/test-dns-domain-search.js new file mode 100644 index 00000000000000..15a87de6e709e2 --- /dev/null +++ b/test/internet/test-dns-domain-search.js @@ -0,0 +1,197 @@ +'use strict'; + +var common = require('../common'); + +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.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)) { + 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) + UDPServer.removeAllListeners('message'); + }); +} + +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.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); + 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) + TCPServer.removeAllListeners('connection'); + }); + }); + }); +} + +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..bb6bbe8a72ca7c --- /dev/null +++ b/test/internet/test-dns-durability.js @@ -0,0 +1,1449 @@ +'use strict'; + +var common = require('../common'); + +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.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); + 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.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); + 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-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) { diff --git a/test/internet/test-dns-truncated.js b/test/internet/test-dns-truncated.js new file mode 100644 index 00000000000000..01faa986c97052 --- /dev/null +++ b/test/internet/test-dns-truncated.js @@ -0,0 +1,90 @@ +'use strict'; + +var common = require('../common'); + +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..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) { @@ -40,44 +39,57 @@ function TEST(f) { function checkWrap(req) { - assert.ok(typeof req === 'object'); + if (process.oldDNS) + assert.ok(typeof req === 'object'); } -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(); }); @@ -86,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(); }); @@ -105,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(); }); @@ -130,24 +128,13 @@ 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; - - 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'); +TEST(function test_lookup_null(done) { + const req = dns.lookup(null, function(err, ip, family) { + if (err) throw err; - 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(); }); @@ -155,33 +142,27 @@ 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; - assert.ok(result); - assert.ok(typeof result === 'object'); - - assert.ok(typeof result.nsname === 'string'); - assert.ok(result.nsname.length > 0); +TEST(function test_lookup_null_all(done) { + const req = dns.lookup(null, {all: true}, function(err, ips, family) { + if (err) throw err; - 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(); }); @@ -189,18 +170,23 @@ 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; - 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(); }); @@ -208,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(); }); @@ -221,41 +208,44 @@ TEST(function test_resolveTxt(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_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(); }); - - checkWrap(req); }); -TEST(function test_lookup_null(done) { - var req = dns.lookup(null, function(err, ip, family) { +TEST(function test_resolveMx(done) { + const req = dns.resolveMx('gmail.com', function(err, result) { if (err) throw err; - assert.strictEqual(ip, null); - assert.strictEqual(family, 4); - 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_ip_all(done) { - var req = dns.lookup('127.0.0.1', {all: true}, function(err, ips, family) { - 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.ok(typeof item.priority === 'number'); + } done(); }); @@ -264,11 +254,19 @@ 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_resolveNaptr(done) { + const req = dns.resolveNaptr('statdns.net', function(err, result) { if (err) throw err; - assert.ok(Array.isArray(ips)); - assert.strictEqual(ips.length, 0); + + 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(); }); @@ -277,20 +275,17 @@ 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) { +TEST(function test_resolveNs(done) { + const req = dns.resolveNs('rackspace.com', function(err, names) { if (err) throw err; - assert.ok(Array.isArray(ips)); - assert.ok(ips.length > 0); - 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(names.length > 0); + + for (let i = 0; i < names.length; i++) { + const name = names[i]; + assert.ok(name); + assert.ok(typeof name === 'string'); + } done(); }); @@ -299,22 +294,28 @@ TEST(function test_lookup_all_mixed(done) { }); -TEST(function test_lookupservice_invalid(done) { - var req = dns.lookupService('1.2.3.4', 80, function(err, host, service) { - assert(err instanceof Error); - assert.strictEqual(err.code, 'ENOTFOUND'); - assert.ok(/1\.2\.3\.4/.test(err.message)); +TEST(function test_reverse_bogus(done) { + var error; - done(); - }); + try { + const req = dns.reverse('bogus ip', function() { + assert.ok(false); + }); + } catch (e) { + error = e; + } - checkWrap(req); + assert.ok(error instanceof Error); + assert.strictEqual(error.errno, 'EINVAL'); + + done(); }); TEST(function test_reverse_failure(done) { - var req = dns.reverse('0.0.0.0', function(err) { + const 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)); @@ -326,12 +327,33 @@ 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_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(); }); @@ -340,21 +362,17 @@ TEST(function test_lookup_failure(done) { }); -TEST(function test_resolve_failure(done) { - var req = dns.resolve4('nosuchhostimsure', function(err) { - assert(err instanceof Error); - - switch (err.code) { - case 'ENOTFOUND': - case 'ESERVFAIL': - break; - default: - assert.strictEqual(err.code, 'ENOTFOUND'); // Silly error code... - break; - } +TEST(function test_resolveSrv(done) { + const req = dns.resolveSrv('_sip._tcp.statdns.net', function(err, result) { + if (err) throw err; - assert.strictEqual(err.hostname, 'nosuchhostimsure'); - assert.ok(/nosuchhostimsure/.test(err.message)); + assert.equal(result.length, 1); + assert.deepEqual(result[0], { + priority: 0, + weight: 5, + port: 5060, + name: 'sipserver.statdns.net' + }); done(); }); @@ -363,26 +381,43 @@ TEST(function test_resolve_failure(done) { }); -var getaddrinfoCallbackCalled = false; +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_resolveTxt(done) { + const req = dns.resolveTxt('statdns.net', function(err, records) { + if (err) throw err; + + assert.equal(records.length, 1); + assert.deepEqual(records[0], [ + 'Get more information on : http://www.statdns.net' + ]); -console.log('looking up nodejs.org...'); + done(); + }); -var cares = process.binding('cares_wrap'); -var req = new cares.GetAddrInfoReqWrap(); -var err = cares.getaddrinfo(req, 'nodejs.org', 4); + 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); }); diff --git a/test/parallel/test-async-wrap-check-providers.js b/test/parallel/test-async-wrap-check-providers.js index 45dd8d4f24048b..97324fd6d658c2 100644 --- a/test/parallel/test-async-wrap-check-providers.js +++ b/test/parallel/test-async-wrap-check-providers.js @@ -12,13 +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); - function init(id) { keyList = keyList.filter(e => e != pkeys[id]); } @@ -90,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:'); 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'); 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(); 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)