Skip to content
This repository was archived by the owner on Apr 22, 2023. It is now read-only.

Commit 4306786

Browse files
cjihrigtrevnorris
authored andcommitted
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>
1 parent 7da63a1 commit 4306786

File tree

6 files changed

+198
-27
lines changed

6 files changed

+198
-27
lines changed

doc/api/dns.markdown

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,31 @@ resolves the IP addresses which are returned.
3131
});
3232
});
3333

34-
## dns.lookup(hostname, [family], callback)
34+
## dns.lookup(hostname, [options], callback)
3535

3636
Resolves a hostname (e.g. `'google.com'`) into the first found A (IPv4) or
37-
AAAA (IPv6) record.
38-
The `family` can be the integer `4` or `6`. Defaults to `null` that indicates
39-
both Ip v4 and v6 address family.
37+
AAAA (IPv6) record. `options` can be an object or integer. If `options` is
38+
not provided, then IP v4 and v6 addresses are both valid. If `options` is
39+
an integer, then it must be `4` or `6`.
40+
41+
Alternatively, `options` can be an object containing two properties,
42+
`family` and `hints`. Both properties are optional. If `family` is provided,
43+
it must be the integer `4` or `6`. If `family` is not provided then IP v4
44+
and v6 addresses are accepted. The `hints` field, if present, should be one
45+
or more of the supported `getaddrinfo` flags. If `hints` is not provided,
46+
then no flags are passed to `getaddrinfo`. Multiple flags can be passed
47+
through `hints` by logically `OR`ing their values. An example usage of
48+
`options` is shown below.
49+
50+
```
51+
{
52+
family: 4,
53+
hints: dns.ADDRCONFIG | dns.V4MAPPED
54+
}
55+
```
56+
57+
See [supported `getaddrinfo` flags](#dns_supported_getaddrinfo_flags) below for
58+
more information on supported flags.
4059

4160
The callback has arguments `(err, address, family)`. The `address` argument
4261
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.,
120139

121140
## dns.resolveSoa(hostname, callback)
122141

123-
The same as `dns.resolve()`, but only for start of authority record queries
142+
The same as `dns.resolve()`, but only for start of authority record queries
124143
(`SOA` record).
125144

126145
`addresses` is an object with the following structure:
@@ -201,3 +220,14 @@ Each DNS query can return one of the following error codes:
201220
- `dns.LOADIPHLPAPI`: Error loading iphlpapi.dll.
202221
- `dns.ADDRGETNETWORKPARAMS`: Could not find GetNetworkParams function.
203222
- `dns.CANCELLED`: DNS query cancelled.
223+
224+
## Supported getaddrinfo flags
225+
226+
The following flags can be passed as hints to `dns.lookup`.
227+
228+
- `dns.ADDRCONFIG`: Returned address types are determined by the types
229+
of addresses supported by the current system. For example, IPv4 addresses
230+
are only returned if the current system has at least one IPv4 address
231+
configured. Loopback addresses are not considered.
232+
- `dns.V4MAPPED`: If the IPv6 family was specified, but no IPv6 addresses
233+
were found, then return IPv4 mapped IPv6 addresses.

lib/dns.js

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,37 @@ function onlookup(err, addresses) {
9999

100100

101101
// Easy DNS A/AAAA look up
102-
// lookup(hostname, [family,] callback)
103-
exports.lookup = function(hostname, family, callback) {
104-
// parse arguments
105-
if (arguments.length === 2) {
106-
callback = family;
102+
// lookup(hostname, [options,] callback)
103+
exports.lookup = function lookup(hostname, options, callback) {
104+
var hints = 0;
105+
var family;
106+
107+
// Parse arguments
108+
if (typeof options === 'function') {
109+
callback = options;
107110
family = 0;
108-
} else if (!family) {
111+
// Allow user to pass falsy values to options, and still pass callback.
112+
} else if (typeof callback !== 'function') {
113+
throw TypeError('invalid arguments: callback must be passed');
114+
} else if (!options) {
109115
family = 0;
110-
} else {
111-
family = +family;
112-
if (family !== 4 && family !== 6) {
113-
throw new Error('invalid argument: `family` must be 4 or 6');
116+
} else if (util.isObject(options)) {
117+
hints = options.hints >>> 0;
118+
family = options.family >>> 0;
119+
120+
if (hints !== 0 &&
121+
hints !== exports.ADDRCONFIG &&
122+
hints !== exports.V4MAPPED &&
123+
hints !== (exports.ADDRCONFIG | exports.V4MAPPED)) {
124+
throw new TypeError('invalid argument: hints must use valid flags');
114125
}
126+
} else {
127+
family = options >>> 0;
115128
}
129+
130+
if (family !== 0 && family !== 4 && family !== 6)
131+
throw new TypeError('invalid argument: family must be 4 or 6');
132+
116133
callback = makeAsync(callback);
117134

118135
if (!hostname) {
@@ -133,7 +150,7 @@ exports.lookup = function(hostname, family, callback) {
133150
oncomplete: onlookup
134151
};
135152

136-
var err = cares.getaddrinfo(req, hostname, family);
153+
var err = cares.getaddrinfo(req, hostname, family, hints);
137154
if (err) {
138155
callback(errnoException(err, 'getaddrinfo', hostname));
139156
return {};
@@ -290,6 +307,9 @@ exports.setServers = function(servers) {
290307
}
291308
};
292309

310+
// uv_getaddrinfo flags
311+
exports.ADDRCONFIG = cares.AI_ADDRCONFIG;
312+
exports.V4MAPPED = cares.AI_V4MAPPED;
293313

294314
// ERROR CODES
295315
exports.NODATA = 'ENODATA';

lib/net.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -881,11 +881,20 @@ Socket.prototype.connect = function(options, cb) {
881881
connect(self, self._host, options.port, 4);
882882

883883
} else {
884+
var dns = require('dns');
884885
var host = options.host;
885-
var family = options.family || 4;
886+
var dnsopts = {
887+
family: options.family,
888+
hints: 0
889+
};
890+
891+
if (dnsopts.family !== 4 && dnsopts.family !== 6)
892+
dnsopts.hints = dns.ADDRCONFIG | dns.V4MAPPED;
893+
886894
debug('connect: find host ' + host);
895+
debug('connect: dns options ' + dnsopts);
887896
self._host = host;
888-
require('dns').lookup(host, family, function(err, ip, addressType) {
897+
dns.lookup(host, dnsopts, function(err, ip, addressType) {
889898
self.emit('lookup', err, ip, addressType);
890899

891900
// It's possible we were destroyed while looking this up.

src/cares_wrap.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,10 +1014,13 @@ static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
10141014
assert(args[0]->IsObject());
10151015
assert(args[1]->IsString());
10161016
assert(args[2]->IsInt32());
1017+
assert(args[3]->IsInt32());
10171018
Local<Object> req_wrap_obj = args[0].As<Object>();
10181019
node::Utf8Value hostname(args[1]);
10191020

10201021
int family;
1022+
int32_t flags = args[3]->Int32Value();
1023+
10211024
switch (args[2]->Int32Value()) {
10221025
case 0:
10231026
family = AF_UNSPEC;
@@ -1042,6 +1045,7 @@ static void GetAddrInfo(const FunctionCallbackInfo<Value>& args) {
10421045
memset(&hints, 0, sizeof(struct addrinfo));
10431046
hints.ai_family = family;
10441047
hints.ai_socktype = SOCK_STREAM;
1048+
hints.ai_flags = flags;
10451049

10461050
int err = uv_getaddrinfo(env->event_loop(),
10471051
&req_wrap->req_,
@@ -1246,6 +1250,10 @@ static void Initialize(Handle<Object> target,
12461250
Integer::New(env->isolate(), AF_INET6));
12471251
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AF_UNSPEC"),
12481252
Integer::New(env->isolate(), AF_UNSPEC));
1253+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_ADDRCONFIG"),
1254+
Integer::New(env->isolate(), AI_ADDRCONFIG));
1255+
target->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "AI_V4MAPPED"),
1256+
Integer::New(env->isolate(), AI_V4MAPPED));
12491257
}
12501258

12511259
} // namespace cares_wrap

test/internet/test-dns.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,36 @@ TEST(function test_lookup_ipv4_implicit(done) {
337337
});
338338

339339

340+
TEST(function test_lookup_ipv4_explicit_object(done) {
341+
var req = dns.lookup('www.google.com', {
342+
family: 4
343+
}, function(err, ip, family) {
344+
if (err) throw err;
345+
assert.ok(net.isIPv4(ip));
346+
assert.strictEqual(family, 4);
347+
348+
done();
349+
});
350+
351+
checkWrap(req);
352+
});
353+
354+
355+
TEST(function test_lookup_ipv4_hint_addrconfig(done) {
356+
var req = dns.lookup('www.google.com', {
357+
hint: dns.ADDRCONFIG
358+
}, function(err, ip, family) {
359+
if (err) throw err;
360+
assert.ok(net.isIPv4(ip));
361+
assert.strictEqual(family, 4);
362+
363+
done();
364+
});
365+
366+
checkWrap(req);
367+
});
368+
369+
340370
TEST(function test_lookup_ipv6_explicit(done) {
341371
var req = dns.lookup('ipv6.google.com', 6, function(err, ip, family) {
342372
if (err) throw err;
@@ -365,6 +395,36 @@ TEST(function test_lookup_ipv6_implicit(done) {
365395
*/
366396

367397

398+
TEST(function test_lookup_ipv6_explicit_object(done) {
399+
var req = dns.lookup('ipv6.google.com', {
400+
family: 6
401+
}, function(err, ip, family) {
402+
if (err) throw err;
403+
assert.ok(net.isIPv6(ip));
404+
assert.strictEqual(family, 6);
405+
406+
done();
407+
});
408+
409+
checkWrap(req);
410+
});
411+
412+
413+
TEST(function test_lookup_ipv6_hint(done) {
414+
var req = dns.lookup('ipv6.google.com', {
415+
hint: dns.V4MAPPED
416+
}, function(err, ip, family) {
417+
if (err) throw err;
418+
assert.ok(net.isIPv6(ip));
419+
assert.strictEqual(family, 6);
420+
421+
done();
422+
});
423+
424+
checkWrap(req);
425+
});
426+
427+
368428
TEST(function test_lookup_failure(done) {
369429
var req = dns.lookup('does.not.exist', 4, function(err, ip, family) {
370430
assert.ok(err instanceof Error);

test/simple/test-dns.js

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ var dns = require('dns');
2727
var existing = dns.getServers();
2828
assert(existing.length);
2929

30+
function noop() {}
31+
3032
var goog = [
3133
'8.8.8.8',
3234
'8.8.4.4',
@@ -61,12 +63,54 @@ assert.deepEqual(dns.getServers(), portsExpected);
6163
assert.doesNotThrow(function () { dns.setServers([]); });
6264
assert.deepEqual(dns.getServers(), []);
6365

64-
assert.throws(
65-
function() {
66-
dns.resolve('test.com', [], new Function);
67-
},
68-
function(err) {
69-
return !(err instanceof TypeError);
70-
},
71-
"Unexpected error"
72-
);
66+
assert.throws(function() {
67+
dns.resolve('test.com', [], noop);
68+
}, function(err) {
69+
return !(err instanceof TypeError);
70+
}, 'Unexpected error');
71+
72+
assert.throws(function() {
73+
dns.lookup('www.google.com', { hints: 1 }, noop);
74+
});
75+
76+
assert.throws(function() {
77+
dns.lookup('www.google.com');
78+
}, 'invalid arguments: callback must be passed');
79+
80+
assert.throws(function() {
81+
dns.lookup('www.google.com', 4);
82+
}, 'invalid arguments: callback must be passed');
83+
84+
assert.doesNotThrow(function() {
85+
dns.lookup('www.google.com', 6, noop);
86+
});
87+
88+
assert.doesNotThrow(function() {
89+
dns.lookup('www.google.com', {}, noop);
90+
});
91+
92+
assert.doesNotThrow(function() {
93+
dns.lookup('www.google.com', {
94+
family: 4,
95+
hints: 0
96+
}, noop);
97+
});
98+
99+
assert.doesNotThrow(function() {
100+
dns.lookup('www.google.com', {
101+
family: 6,
102+
hints: dns.ADDRCONFIG
103+
}, noop);
104+
});
105+
106+
assert.doesNotThrow(function() {
107+
dns.lookup('www.google.com', {
108+
hints: dns.V4MAPPED
109+
}, noop);
110+
});
111+
112+
assert.doesNotThrow(function() {
113+
dns.lookup('www.google.com', {
114+
hints: dns.ADDRCONFIG | dns.V4MAPPED
115+
}, noop);
116+
});

0 commit comments

Comments
 (0)