diff --git a/doc/api/dns.markdown b/doc/api/dns.markdown index 94e19a3f72eb03..5b8477fcb48dcf 100644 --- a/doc/api/dns.markdown +++ b/doc/api/dns.markdown @@ -31,12 +31,31 @@ resolves the IP addresses which are returned. }); }); -## dns.lookup(hostname, [family], callback) +## dns.lookup(hostname, [options], callback) Resolves a hostname (e.g. `'google.com'`) into the first found A (IPv4) or -AAAA (IPv6) record. -The `family` can be the integer `4` or `6`. Defaults to `null` that indicates -both Ip v4 and v6 address family. +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`. + +Alternatively, `options` can be an object containing two properties, +`family` and `hints`. Both properties are optional. If `family` is provided, +it must be the integer `4` or `6`. If `family` is not provided then IP v4 +and v6 addresses are accepted. The `hints` field, if present, 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. An example usage of +`options` is shown below. + +``` +{ + family: 4, + hints: dns.ADDRCONFIG | dns.V4MAPPED +} +``` + +See [supported `getaddrinfo` flags](#dns_supported_getaddrinfo_flags) below for +more information on supported flags. The callback has arguments `(err, address, family)`. The `address` argument is a string representation of a IP v4 or v6 address. The `family` argument @@ -120,7 +139,7 @@ of SRV records are priority, weight, port, and name (e.g., ## dns.resolveSoa(hostname, callback) -The same as `dns.resolve()`, but only for start of authority record queries +The same as `dns.resolve()`, but only for start of authority record queries (`SOA` record). `addresses` is an object with the following structure: @@ -201,3 +220,14 @@ Each DNS query can return one of the following error codes: - `dns.LOADIPHLPAPI`: Error loading iphlpapi.dll. - `dns.ADDRGETNETWORKPARAMS`: Could not find GetNetworkParams function. - `dns.CANCELLED`: DNS query cancelled. + +## Supported getaddrinfo flags + +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. diff --git a/lib/dns.js b/lib/dns.js index f1b63a1f65f2f1..dfe37dffd2522b 100644 --- a/lib/dns.js +++ b/lib/dns.js @@ -99,20 +99,37 @@ function onlookup(err, addresses) { // Easy DNS A/AAAA look up -// lookup(hostname, [family,] callback) -exports.lookup = function(hostname, family, callback) { - // parse arguments - if (arguments.length === 2) { - callback = family; +// lookup(hostname, [options,] callback) +exports.lookup = function lookup(hostname, options, callback) { + var hints = 0; + var family; + + // Parse arguments + if (typeof options === 'function') { + callback = options; family = 0; - } else if (!family) { + // Allow user to pass falsy values to options, and still pass callback. + } else if (typeof callback !== 'function') { + throw TypeError('invalid arguments: callback must be passed'); + } else if (!options) { family = 0; - } else { - family = +family; - if (family !== 4 && family !== 6) { - throw new Error('invalid argument: `family` must be 4 or 6'); + } else if (util.isObject(options)) { + hints = options.hints >>> 0; + family = options.family >>> 0; + + if (hints !== 0 && + hints !== exports.ADDRCONFIG && + hints !== exports.V4MAPPED && + hints !== (exports.ADDRCONFIG | exports.V4MAPPED)) { + throw new TypeError('invalid argument: hints must use valid flags'); } + } else { + family = options >>> 0; } + + if (family !== 0 && family !== 4 && family !== 6) + throw new TypeError('invalid argument: family must be 4 or 6'); + callback = makeAsync(callback); if (!hostname) { @@ -133,7 +150,7 @@ exports.lookup = function(hostname, family, callback) { oncomplete: onlookup }; - var err = cares.getaddrinfo(req, hostname, family); + var err = cares.getaddrinfo(req, hostname, family, hints); if (err) { callback(errnoException(err, 'getaddrinfo', hostname)); return {}; @@ -290,6 +307,9 @@ exports.setServers = function(servers) { } }; +// uv_getaddrinfo flags +exports.ADDRCONFIG = cares.AI_ADDRCONFIG; +exports.V4MAPPED = cares.AI_V4MAPPED; // ERROR CODES exports.NODATA = 'ENODATA'; diff --git a/lib/net.js b/lib/net.js index 1877e42153464c..aa815fb28f1fe0 100644 --- a/lib/net.js +++ b/lib/net.js @@ -881,11 +881,20 @@ Socket.prototype.connect = function(options, cb) { connect(self, self._host, options.port, 4); } else { + var dns = require('dns'); var host = options.host; - var family = options.family || 4; + var dnsopts = { + family: options.family, + hints: 0 + }; + + if (dnsopts.family !== 4 && dnsopts.family !== 6) + dnsopts.hints = dns.ADDRCONFIG | dns.V4MAPPED; + debug('connect: find host ' + host); + debug('connect: dns options ' + dnsopts); self._host = host; - require('dns').lookup(host, family, function(err, ip, addressType) { + dns.lookup(host, dnsopts, function(err, ip, addressType) { self.emit('lookup', err, ip, addressType); // It's possible we were destroyed while looking this up. diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 7523e9df655640..af72c49595f3ed 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -1014,10 +1014,13 @@ static void GetAddrInfo(const FunctionCallbackInfo& args) { assert(args[0]->IsObject()); assert(args[1]->IsString()); assert(args[2]->IsInt32()); + assert(args[3]->IsInt32()); Local req_wrap_obj = args[0].As(); node::Utf8Value hostname(args[1]); int family; + int32_t flags = args[3]->Int32Value(); + switch (args[2]->Int32Value()) { case 0: family = AF_UNSPEC; @@ -1042,6 +1045,7 @@ static void GetAddrInfo(const FunctionCallbackInfo& args) { memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = flags; int err = uv_getaddrinfo(env->event_loop(), &req_wrap->req_, @@ -1246,6 +1250,10 @@ static void Initialize(Handle target, Integer::New(env->isolate(), AF_INET6)); target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_UNSPEC"), Integer::New(env->isolate(), AF_UNSPEC)); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_ADDRCONFIG"), + Integer::New(env->isolate(), AI_ADDRCONFIG)); + target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_V4MAPPED"), + Integer::New(env->isolate(), AI_V4MAPPED)); } } // namespace cares_wrap diff --git a/test/internet/test-dns.js b/test/internet/test-dns.js index 6165b30a3ab2a0..902545c1752b8b 100644 --- a/test/internet/test-dns.js +++ b/test/internet/test-dns.js @@ -337,6 +337,36 @@ TEST(function test_lookup_ipv4_implicit(done) { }); +TEST(function test_lookup_ipv4_explicit_object(done) { + var req = dns.lookup('www.google.com', { + family: 4 + }, function(err, ip, family) { + if (err) throw err; + assert.ok(net.isIPv4(ip)); + assert.strictEqual(family, 4); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_lookup_ipv4_hint_addrconfig(done) { + var req = dns.lookup('www.google.com', { + hint: dns.ADDRCONFIG + }, function(err, ip, family) { + if (err) throw err; + assert.ok(net.isIPv4(ip)); + assert.strictEqual(family, 4); + + done(); + }); + + checkWrap(req); +}); + + TEST(function test_lookup_ipv6_explicit(done) { var req = dns.lookup('ipv6.google.com', 6, function(err, ip, family) { if (err) throw err; @@ -365,6 +395,36 @@ TEST(function test_lookup_ipv6_implicit(done) { */ +TEST(function test_lookup_ipv6_explicit_object(done) { + var req = dns.lookup('ipv6.google.com', { + family: 6 + }, function(err, ip, family) { + if (err) throw err; + assert.ok(net.isIPv6(ip)); + assert.strictEqual(family, 6); + + done(); + }); + + checkWrap(req); +}); + + +TEST(function test_lookup_ipv6_hint(done) { + var req = dns.lookup('ipv6.google.com', { + hint: dns.V4MAPPED + }, function(err, ip, family) { + if (err) throw err; + assert.ok(net.isIPv6(ip)); + assert.strictEqual(family, 6); + + done(); + }); + + checkWrap(req); +}); + + TEST(function test_lookup_failure(done) { var req = dns.lookup('does.not.exist', 4, function(err, ip, family) { assert.ok(err instanceof Error); diff --git a/test/simple/test-dns.js b/test/simple/test-dns.js index e7dce4b2eed3a9..48f7d5f40a28ff 100644 --- a/test/simple/test-dns.js +++ b/test/simple/test-dns.js @@ -27,6 +27,8 @@ var dns = require('dns'); var existing = dns.getServers(); assert(existing.length); +function noop() {} + var goog = [ '8.8.8.8', '8.8.4.4', @@ -61,12 +63,54 @@ assert.deepEqual(dns.getServers(), portsExpected); assert.doesNotThrow(function () { dns.setServers([]); }); assert.deepEqual(dns.getServers(), []); -assert.throws( - function() { - dns.resolve('test.com', [], new Function); - }, - function(err) { - return !(err instanceof TypeError); - }, - "Unexpected error" -); +assert.throws(function() { + dns.resolve('test.com', [], noop); +}, function(err) { + return !(err instanceof TypeError); +}, 'Unexpected error'); + +assert.throws(function() { + dns.lookup('www.google.com', { hints: 1 }, noop); +}); + +assert.throws(function() { + dns.lookup('www.google.com'); +}, 'invalid arguments: callback must be passed'); + +assert.throws(function() { + dns.lookup('www.google.com', 4); +}, 'invalid arguments: callback must be passed'); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', 6, noop); +}); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', {}, noop); +}); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', { + family: 4, + hints: 0 + }, noop); +}); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', { + family: 6, + hints: dns.ADDRCONFIG + }, noop); +}); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', { + hints: dns.V4MAPPED + }, noop); +}); + +assert.doesNotThrow(function() { + dns.lookup('www.google.com', { + hints: dns.ADDRCONFIG | dns.V4MAPPED + }, noop); +});