Skip to content

Commit 563da18

Browse files
committed
dns: allow --dns-order to change default dns verbatim
Allow the "--dns-order" option to change the default value of verbatim in `dns.lookup()`. This is useful when running Node.js in ipv6-only environments to avoid possible ENETUNREACH errors.
1 parent f96dffb commit 563da18

10 files changed

+246
-5
lines changed

doc/api/cli.md

+13
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ Make built-in language features like `eval` and `new Function` that generate
181181
code from strings throw an exception instead. This does not affect the Node.js
182182
`vm` module.
183183

184+
### `--dns-order=value`
185+
<!-- YAML
186+
added: REPLACEME
187+
-->
188+
189+
Set the default value of `verbatim` in [`dns.lookup()`][]. The value could be:
190+
* `ipv4`: Set default `verbatim` `false`.
191+
* `verbatim`: Set default `verbatim` `true`.
192+
193+
Otherwise, default `verbatim` will be decided by Node.js.
194+
184195
### `--enable-fips`
185196
<!-- YAML
186197
added: v6.0.0
@@ -1371,6 +1382,7 @@ Node.js options that are allowed are:
13711382
* `--conditions`
13721383
* `--diagnostic-dir`
13731384
* `--disable-proto`
1385+
* `--dns-order`
13741386
* `--enable-fips`
13751387
* `--enable-source-maps`
13761388
* `--experimental-abortcontroller`
@@ -1728,6 +1740,7 @@ $ node --max-old-space-size=1536 index.js
17281740
[`NODE_OPTIONS`]: #cli_node_options_options
17291741
[`NO_COLOR`]: https://no-color.org
17301742
[`SlowBuffer`]: buffer.md#buffer_class_slowbuffer
1743+
[`dns.lookup()`]: dns.md#dns_dns_lookup_hostname_options_callback
17311744
[`process.setUncaughtExceptionCaptureCallback()`]: process.md#process_process_setuncaughtexceptioncapturecallback_fn
17321745
[`tls.DEFAULT_MAX_VERSION`]: tls.md#tls_tls_default_max_version
17331746
[`tls.DEFAULT_MIN_VERSION`]: tls.md#tls_tls_default_min_version

doc/api/dns.md

+15
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,20 @@ That is, if attempting to resolve with the first server provided results in a
665665
subsequent servers provided. Fallback DNS servers will only be used if the
666666
earlier ones time out or result in some other error.
667667

668+
## `dns.verbatim`
669+
670+
<!-- YAML
671+
added: REPLACEME
672+
-->
673+
674+
* {boolean}
675+
676+
By default set to `false`. Determines the default value of `verbatim` when
677+
it's not passed to [`dns.lookup()`][] or [`dnsPromises.lookup()`][].
678+
679+
Configurable using the [`--dns-order`][] CLI option. Change `dns.verbatim`
680+
will override the [`--dns-order`][] option.
681+
668682
## DNS promises API
669683
<!-- YAML
670684
added: v10.6.0
@@ -1241,6 +1255,7 @@ uses. For instance, _they do not use the configuration from `/etc/hosts`_.
12411255
[Implementation considerations section]: #dns_implementation_considerations
12421256
[RFC 5952]: https://tools.ietf.org/html/rfc5952#section-6
12431257
[RFC 8482]: https://tools.ietf.org/html/rfc8482
1258+
[`--dns-order`]: cli.md#cli_dns_order_value
12441259
[`Error`]: errors.md#errors_class_error
12451260
[`UV_THREADPOOL_SIZE`]: cli.md#cli_uv_threadpool_size_size
12461261
[`dgram.createSocket()`]: dgram.md#dgram_dgram_createsocket_options_callback

lib/dns.js

+17-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ const {
4141
Resolver,
4242
validateHints,
4343
emitInvalidHostnameWarning,
44+
getDefaultVerbatim,
45+
setDefaultVerbatim,
4446
} = require('internal/dns/utils');
4547
const {
4648
ERR_INVALID_ARG_TYPE,
@@ -96,7 +98,7 @@ function lookup(hostname, options, callback) {
9698
let hints = 0;
9799
let family = -1;
98100
let all = false;
99-
let verbatim = false;
101+
let verbatim = getDefaultVerbatim();
100102

101103
// Parse arguments
102104
if (hostname) {
@@ -113,7 +115,9 @@ function lookup(hostname, options, callback) {
113115
hints = options.hints >>> 0;
114116
family = options.family >>> 0;
115117
all = options.all === true;
116-
verbatim = options.verbatim === true;
118+
if (typeof options.verbatim === 'boolean') {
119+
verbatim = options.verbatim === true;
120+
}
117121

118122
validateHints(hints);
119123
} else {
@@ -335,3 +339,14 @@ ObjectDefineProperties(module.exports, {
335339
}
336340
}
337341
});
342+
343+
ObjectDefineProperty(module.exports, 'verbatim', {
344+
configurable: true,
345+
enumerable: true,
346+
get() {
347+
return getDefaultVerbatim()
348+
},
349+
set(value) {
350+
setDefaultVerbatim(value)
351+
}
352+
});

lib/internal/dns/promises.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
'use strict';
2-
32
const {
43
ArrayPrototypeMap,
54
ObjectCreate,
@@ -14,6 +13,7 @@ const {
1413
validateHints,
1514
validateTimeout,
1615
emitInvalidHostnameWarning,
16+
getDefaultVerbatim,
1717
} = require('internal/dns/utils');
1818
const { codes, dnsException } = require('internal/errors');
1919
const { toASCII } = require('internal/idna');
@@ -103,7 +103,7 @@ function lookup(hostname, options) {
103103
var hints = 0;
104104
var family = -1;
105105
var all = false;
106-
var verbatim = false;
106+
var verbatim = getDefaultVerbatim();
107107

108108
// Parse arguments
109109
if (hostname && typeof hostname !== 'string') {
@@ -112,7 +112,9 @@ function lookup(hostname, options) {
112112
hints = options.hints >>> 0;
113113
family = options.family >>> 0;
114114
all = options.all === true;
115-
verbatim = options.verbatim === true;
115+
if (typeof options.verbatim === 'boolean') {
116+
verbatim = options.verbatim === true;
117+
}
116118

117119
validateHints(hints);
118120
} else {

lib/internal/dns/utils.js

+27
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ const {
1313

1414
const errors = require('internal/errors');
1515
const { isIP } = require('internal/net');
16+
const { getOptionValue } = require('internal/options');
1617
const {
1718
validateArray,
1819
validateInt32,
1920
validateString,
21+
validateBoolean,
2022
} = require('internal/validators');
2123
const {
2224
ChannelWrap,
@@ -184,6 +186,29 @@ function emitInvalidHostnameWarning(hostname) {
184186
);
185187
}
186188

189+
let defaultVerbatim;
190+
191+
function getDefaultVerbatim() {
192+
if (defaultVerbatim === undefined) {
193+
const option = getOptionValue('--dns-order');
194+
switch (option) {
195+
case 'verbatim':
196+
defaultVerbatim = true;
197+
break;
198+
case 'ipv4':
199+
default:
200+
defaultVerbatim = false;
201+
}
202+
}
203+
204+
return defaultVerbatim;
205+
}
206+
207+
function setDefaultVerbatim(value) {
208+
validateBoolean(value, 'dns.verbatim');
209+
defaultVerbatim = value;
210+
}
211+
187212
module.exports = {
188213
bindDefaultResolver,
189214
getDefaultResolver,
@@ -192,4 +217,6 @@ module.exports = {
192217
validateTimeout,
193218
Resolver,
194219
emitInvalidHostnameWarning,
220+
getDefaultVerbatim,
221+
setDefaultVerbatim,
195222
};

src/node_options.cc

+7
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
297297
" (default: current working directory)",
298298
&EnvironmentOptions::diagnostic_dir,
299299
kAllowedInEnvironment);
300+
AddOption("--dns-order",
301+
"set default value of verbatim in dns.lookup. Options are "
302+
"'ipv4' (IPv4 addresses are placed before IPv6 addresses) "
303+
"'verbatim' (addresses are in the order the DNS resolver "
304+
"returned)",
305+
&EnvironmentOptions::dns_verbatim,
306+
kAllowedInEnvironment);
300307
AddOption("--enable-source-maps",
301308
"Source Map V3 support for stack traces",
302309
&EnvironmentOptions::enable_source_maps,

src/node_options.h

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ class EnvironmentOptions : public Options {
101101
public:
102102
bool abort_on_uncaught_exception = false;
103103
std::vector<std::string> conditions;
104+
std::string dns_verbatim;
104105
bool enable_source_maps = false;
105106
bool experimental_json_modules = false;
106107
bool experimental_modules = false;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Flags: --expose-internals --dns-order=ipv4
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { internalBinding } = require('internal/test/binding');
6+
const cares = internalBinding('cares_wrap');
7+
const { promisify } = require('util');
8+
9+
// Test that --dns-order=ipv4 works as expected.
10+
11+
const originalGetaddrinfo = cares.getaddrinfo;
12+
const calls = [];
13+
cares.getaddrinfo = common.mustCallAtLeast((...args) => {
14+
calls.push(args);
15+
originalGetaddrinfo(...args);
16+
}, 1);
17+
18+
const dns = require('dns');
19+
const dnsPromises = dns.promises;
20+
21+
let verbatim;
22+
23+
// We want to test the parameter of verbatim only so that we
24+
// ignore possible errors here.
25+
function allowFailed(fn) {
26+
return fn.catch((_err) => {
27+
//
28+
});
29+
}
30+
31+
(async () => {
32+
let callsLength = 0;
33+
const checkParameter = (expected) => {
34+
assert.strictEqual(calls.length, callsLength + 1);
35+
verbatim = calls[callsLength][4];
36+
assert.strictEqual(verbatim, expected);
37+
callsLength += 1;
38+
};
39+
40+
await allowFailed(promisify(dns.lookup)('example.org'));
41+
checkParameter(false);
42+
43+
await allowFailed(dnsPromises.lookup('example.org'));
44+
checkParameter(false);
45+
46+
await allowFailed(promisify(dns.lookup)('example.org', {}));
47+
checkParameter(false);
48+
49+
await allowFailed(dnsPromises.lookup('example.org', {}));
50+
checkParameter(false);
51+
})().then(common.mustCall());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Flags: --expose-internals --dns-order=verbatim
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { internalBinding } = require('internal/test/binding');
6+
const cares = internalBinding('cares_wrap');
7+
const { promisify } = require('util');
8+
9+
// Test that --dns-order=verbatim works as expected.
10+
11+
const originalGetaddrinfo = cares.getaddrinfo;
12+
const calls = [];
13+
cares.getaddrinfo = common.mustCallAtLeast((...args) => {
14+
calls.push(args);
15+
originalGetaddrinfo(...args);
16+
}, 1);
17+
18+
const dns = require('dns');
19+
const dnsPromises = dns.promises;
20+
21+
let verbatim;
22+
23+
// We want to test the parameter of verbatim only so that we
24+
// ignore possible errors here.
25+
function allowFailed(fn) {
26+
return fn.catch((_err) => {
27+
//
28+
});
29+
}
30+
31+
(async () => {
32+
let callsLength = 0;
33+
const checkParameter = (expected) => {
34+
assert.strictEqual(calls.length, callsLength + 1);
35+
verbatim = calls[callsLength][4];
36+
assert.strictEqual(verbatim, expected);
37+
callsLength += 1;
38+
};
39+
40+
await allowFailed(promisify(dns.lookup)('example.org'));
41+
checkParameter(true);
42+
43+
await allowFailed(dnsPromises.lookup('example.org'));
44+
checkParameter(true);
45+
46+
await allowFailed(promisify(dns.lookup)('example.org', {}));
47+
checkParameter(true);
48+
49+
await allowFailed(dnsPromises.lookup('example.org', {}));
50+
checkParameter(true);
51+
})().then(common.mustCall());

test/parallel/test-dns-verbatim.js

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const { internalBinding } = require('internal/test/binding');
6+
const cares = internalBinding('cares_wrap');
7+
const { promisify } = require('util');
8+
9+
// Test that changing require('dns').verbatim works as expected.
10+
11+
const originalGetaddrinfo = cares.getaddrinfo;
12+
const calls = [];
13+
cares.getaddrinfo = common.mustCallAtLeast((...args) => {
14+
calls.push(args);
15+
originalGetaddrinfo(...args);
16+
}, 1);
17+
18+
const dns = require('dns');
19+
const dnsPromises = dns.promises;
20+
21+
let verbatim;
22+
23+
// We want to test the parameter of verbatim only so that we
24+
// ignore possible errors here.
25+
function allowFailed(fn) {
26+
return fn.catch((_err) => {
27+
//
28+
});
29+
}
30+
31+
(async () => {
32+
let callsLength = 0;
33+
const checkParameter = (expected) => {
34+
assert.strictEqual(calls.length, callsLength + 1);
35+
verbatim = calls[callsLength][4];
36+
assert.strictEqual(verbatim, expected);
37+
callsLength += 1;
38+
};
39+
40+
dns.verbatim = true;
41+
await allowFailed(promisify(dns.lookup)('example.org'));
42+
checkParameter(true);
43+
await allowFailed(dnsPromises.lookup('example.org'));
44+
checkParameter(true);
45+
await allowFailed(promisify(dns.lookup)('example.org', {}));
46+
checkParameter(true);
47+
await allowFailed(dnsPromises.lookup('example.org', {}));
48+
checkParameter(true);
49+
50+
dns.verbatim = false;
51+
await allowFailed(promisify(dns.lookup)('example.org'));
52+
checkParameter(false);
53+
await allowFailed(dnsPromises.lookup('example.org'));
54+
checkParameter(false);
55+
await allowFailed(promisify(dns.lookup)('example.org', {}));
56+
checkParameter(false);
57+
await allowFailed(dnsPromises.lookup('example.org', {}));
58+
checkParameter(false);
59+
})().then(common.mustCall());

0 commit comments

Comments
 (0)