Browse Source

net: don't prefer IPv4 addresses during resolution

Currently the address resolution family defaults to IPv4. Instead remove
the preference and instead resolve to a family suitable for the host.

Expose the getaddrinfo flags and allow them to be passed.

Add documentation about new flags.

Reviewed-by: Trevor Norris <trev.norris@gmail.com>
archived-io.js-v0.10
cjihrig 11 years ago
committed by Trevor Norris
parent
commit
430678640c
  1. 40
      doc/api/dns.markdown
  2. 42
      lib/dns.js
  3. 13
      lib/net.js
  4. 8
      src/cares_wrap.cc
  5. 60
      test/internet/test-dns.js
  6. 62
      test/simple/test-dns.js

40
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 Resolves a hostname (e.g. `'google.com'`) into the first found A (IPv4) or
AAAA (IPv6) record. AAAA (IPv6) record. `options` can be an object or integer. If `options` is
The `family` can be the integer `4` or `6`. Defaults to `null` that indicates not provided, then IP v4 and v6 addresses are both valid. If `options` is
both Ip v4 and v6 address family. 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 The callback has arguments `(err, address, family)`. The `address` argument
is a string representation of a IP v4 or v6 address. The `family` 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) ## 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). (`SOA` record).
`addresses` is an object with the following structure: `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.LOADIPHLPAPI`: Error loading iphlpapi.dll.
- `dns.ADDRGETNETWORKPARAMS`: Could not find GetNetworkParams function. - `dns.ADDRGETNETWORKPARAMS`: Could not find GetNetworkParams function.
- `dns.CANCELLED`: DNS query cancelled. - `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.

42
lib/dns.js

@ -99,20 +99,37 @@ function onlookup(err, addresses) {
// Easy DNS A/AAAA look up // Easy DNS A/AAAA look up
// lookup(hostname, [family,] callback) // lookup(hostname, [options,] callback)
exports.lookup = function(hostname, family, callback) { exports.lookup = function lookup(hostname, options, callback) {
// parse arguments var hints = 0;
if (arguments.length === 2) { var family;
callback = family;
// Parse arguments
if (typeof options === 'function') {
callback = options;
family = 0; 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; family = 0;
} else { } else if (util.isObject(options)) {
family = +family; hints = options.hints >>> 0;
if (family !== 4 && family !== 6) { family = options.family >>> 0;
throw new Error('invalid argument: `family` must be 4 or 6');
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); callback = makeAsync(callback);
if (!hostname) { if (!hostname) {
@ -133,7 +150,7 @@ exports.lookup = function(hostname, family, callback) {
oncomplete: onlookup oncomplete: onlookup
}; };
var err = cares.getaddrinfo(req, hostname, family); var err = cares.getaddrinfo(req, hostname, family, hints);
if (err) { if (err) {
callback(errnoException(err, 'getaddrinfo', hostname)); callback(errnoException(err, 'getaddrinfo', hostname));
return {}; return {};
@ -290,6 +307,9 @@ exports.setServers = function(servers) {
} }
}; };
// uv_getaddrinfo flags
exports.ADDRCONFIG = cares.AI_ADDRCONFIG;
exports.V4MAPPED = cares.AI_V4MAPPED;
// ERROR CODES // ERROR CODES
exports.NODATA = 'ENODATA'; exports.NODATA = 'ENODATA';

13
lib/net.js

@ -881,11 +881,20 @@ Socket.prototype.connect = function(options, cb) {
connect(self, self._host, options.port, 4); connect(self, self._host, options.port, 4);
} else { } else {
var dns = require('dns');
var host = options.host; 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: find host ' + host);
debug('connect: dns options ' + dnsopts);
self._host = host; 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); self.emit('lookup', err, ip, addressType);
// It's possible we were destroyed while looking this up. // It's possible we were destroyed while looking this up.

8
src/cares_wrap.cc

@ -1014,10 +1014,13 @@ static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
assert(args[0]->IsObject()); assert(args[0]->IsObject());
assert(args[1]->IsString()); assert(args[1]->IsString());
assert(args[2]->IsInt32()); assert(args[2]->IsInt32());
assert(args[3]->IsInt32());
Local<Object> req_wrap_obj = args[0].As<Object>(); Local<Object> req_wrap_obj = args[0].As<Object>();
node::Utf8Value hostname(args[1]); node::Utf8Value hostname(args[1]);
int family; int family;
int32_t flags = args[3]->Int32Value();
switch (args[2]->Int32Value()) { switch (args[2]->Int32Value()) {
case 0: case 0:
family = AF_UNSPEC; family = AF_UNSPEC;
@ -1042,6 +1045,7 @@ static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
memset(&hints, 0, sizeof(struct addrinfo)); memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = family; hints.ai_family = family;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = flags;
int err = uv_getaddrinfo(env->event_loop(), int err = uv_getaddrinfo(env->event_loop(),
&req_wrap->req_, &req_wrap->req_,
@ -1246,6 +1250,10 @@ static void Initialize(Handle<Object> target,
Integer::New(env->isolate(), AF_INET6)); Integer::New(env->isolate(), AF_INET6));
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_UNSPEC"), target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_UNSPEC"),
Integer::New(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 } // namespace cares_wrap

60
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) { TEST(function test_lookup_ipv6_explicit(done) {
var req = dns.lookup('ipv6.google.com', 6, function(err, ip, family) { var req = dns.lookup('ipv6.google.com', 6, function(err, ip, family) {
if (err) throw err; 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) { TEST(function test_lookup_failure(done) {
var req = dns.lookup('does.not.exist', 4, function(err, ip, family) { var req = dns.lookup('does.not.exist', 4, function(err, ip, family) {
assert.ok(err instanceof Error); assert.ok(err instanceof Error);

62
test/simple/test-dns.js

@ -27,6 +27,8 @@ var dns = require('dns');
var existing = dns.getServers(); var existing = dns.getServers();
assert(existing.length); assert(existing.length);
function noop() {}
var goog = [ var goog = [
'8.8.8.8', '8.8.8.8',
'8.8.4.4', '8.8.4.4',
@ -61,12 +63,54 @@ assert.deepEqual(dns.getServers(), portsExpected);
assert.doesNotThrow(function () { dns.setServers([]); }); assert.doesNotThrow(function () { dns.setServers([]); });
assert.deepEqual(dns.getServers(), []); assert.deepEqual(dns.getServers(), []);
assert.throws( assert.throws(function() {
function() { dns.resolve('test.com', [], noop);
dns.resolve('test.com', [], new Function); }, function(err) {
}, return !(err instanceof TypeError);
function(err) { }, 'Unexpected error');
return !(err instanceof TypeError);
}, assert.throws(function() {
"Unexpected error" 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);
});

Loading…
Cancel
Save