Skip to content

Commit 2efd797

Browse files
committed
http: add --enable-network-family-autoselection command line option
1 parent bb0c56f commit 2efd797

File tree

6 files changed

+129
-3
lines changed

6 files changed

+129
-3
lines changed

doc/api/cli.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,15 @@ added: v6.0.0
314314
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built
315315
against FIPS-compatible OpenSSL.)
316316

317+
### `--enable-network-family-autoselection`
318+
319+
<!-- YAML
320+
added: REPLACEME
321+
-->
322+
323+
Enables the family autoselection algorithm unless connection options explicitly
324+
disables it.
325+
317326
### `--enable-source-maps`
318327

319328
<!-- YAML
@@ -1840,6 +1849,7 @@ Node.js options that are allowed are:
18401849
* `--disable-proto`
18411850
* `--dns-result-order`
18421851
* `--enable-fips`
1852+
* `--enable-network-family-autoselection`
18431853
* `--enable-source-maps`
18441854
* `--experimental-abortcontroller`
18451855
* `--experimental-import-meta-resolve`

doc/api/net.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,8 @@ changes:
873873
- version: REPLACEME
874874
pr-url: https://github.com/nodejs/node/pull/45777
875875
description: The default value for autoSelectFamily option can be changed
876-
at runtime using `setDefaultAutoSelectFamily`.
876+
at runtime using `setDefaultAutoSelectFamily` or via the
877+
command line option `--enable-network-family-autoselection`.
877878
- version: REPLACEME
878879
pr-url: https://github.com/nodejs/node/pull/44731
879880
description: Added the `autoSelectFamily` option.
@@ -933,7 +934,8 @@ For TCP connections, available `options` are:
933934
option before timing out and trying the next address.
934935
Ignored if the `family` option is not `0` or if `localAddress` is set.
935936
Connection errors are not emitted if at least one connection succeeds.
936-
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][].
937+
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][]
938+
or via the command line option `--enable-network-family-autoselection`.
937939
* `autoSelectFamilyAttemptTimeout` {number}: The amount of time in milliseconds to wait
938940
for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option.
939941
If set to a positive integer less than `10`, then the value `10` will be used instead.

lib/net.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,13 +118,14 @@ const {
118118
validateString
119119
} = require('internal/validators');
120120
const kLastWriteQueueSize = Symbol('lastWriteQueueSize');
121+
const { getOptionValue } = require('internal/options');
121122

122123
// Lazy loaded to improve startup performance.
123124
let cluster;
124125
let dns;
125126
let BlockList;
126127
let SocketAddress;
127-
let autoSelectFamilyDefault = false;
128+
let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselection');
128129

129130
const { clearTimeout, setTimeout } = require('timers');
130131
const { kTimeout } = require('internal/timers');

src/node_options.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
346346
"returned)",
347347
&EnvironmentOptions::dns_result_order,
348348
kAllowedInEnvvar);
349+
AddOption("--enable-network-family-autoselection",
350+
"Enable network address family autodetection algorithm",
351+
&EnvironmentOptions::enable_network_family_autoselection,
352+
kAllowedInEnvvar);
349353
AddOption("--enable-source-maps",
350354
"Source Map V3 support for stack traces",
351355
&EnvironmentOptions::enable_source_maps,

src/node_options.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class EnvironmentOptions : public Options {
127127
bool frozen_intrinsics = false;
128128
int64_t heap_snapshot_near_heap_limit = 0;
129129
std::string heap_snapshot_signal;
130+
bool enable_network_family_autoselection = false;
130131
uint64_t max_http_header_size = 16 * 1024;
131132
bool deprecation = true;
132133
bool force_async_hooks_checks = true;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
'use strict';
2+
3+
// Flags: --enable-network-family-autoselection
4+
5+
const common = require('../common');
6+
const { parseDNSPacket, writeDNSPacket } = require('../common/dns');
7+
8+
const assert = require('assert');
9+
const dgram = require('dgram');
10+
const { Resolver } = require('dns');
11+
const { createConnection, createServer } = require('net');
12+
13+
// Test that happy eyeballs algorithm can be enable from command line.
14+
15+
let autoSelectFamilyAttemptTimeout = common.platformTimeout(250);
16+
if (common.isWindows) {
17+
// Some of the windows machines in the CI need more time to establish connection
18+
autoSelectFamilyAttemptTimeout = common.platformTimeout(1500);
19+
}
20+
21+
function _lookup(resolver, hostname, options, cb) {
22+
resolver.resolve(hostname, 'ANY', (err, replies) => {
23+
assert.notStrictEqual(options.family, 4);
24+
25+
if (err) {
26+
return cb(err);
27+
}
28+
29+
const hosts = replies
30+
.map((r) => ({ address: r.address, family: r.type === 'AAAA' ? 6 : 4 }))
31+
.sort((a, b) => b.family - a.family);
32+
33+
if (options.all === true) {
34+
return cb(null, hosts);
35+
}
36+
37+
return cb(null, hosts[0].address, hosts[0].family);
38+
});
39+
}
40+
41+
function createDnsServer(ipv6Addr, ipv4Addr, cb) {
42+
// Create a DNS server which replies with a AAAA and a A record for the same host
43+
const socket = dgram.createSocket('udp4');
44+
45+
socket.on('message', common.mustCall((msg, { address, port }) => {
46+
const parsed = parseDNSPacket(msg);
47+
const domain = parsed.questions[0].domain;
48+
assert.strictEqual(domain, 'example.org');
49+
50+
socket.send(writeDNSPacket({
51+
id: parsed.id,
52+
questions: parsed.questions,
53+
answers: [
54+
{ type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
55+
{ type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
56+
]
57+
}), port, address);
58+
}));
59+
60+
socket.bind(0, () => {
61+
const resolver = new Resolver();
62+
resolver.setServers([`127.0.0.1:${socket.address().port}`]);
63+
64+
cb({ dnsServer: socket, lookup: _lookup.bind(null, resolver) });
65+
});
66+
}
67+
68+
// Test that IPV4 is reached if IPV6 is not reachable
69+
{
70+
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {
71+
const ipv4Server = createServer((socket) => {
72+
socket.on('data', common.mustCall(() => {
73+
socket.write('response-ipv4');
74+
socket.end();
75+
}));
76+
});
77+
78+
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
79+
const port = ipv4Server.address().port;
80+
81+
const connection = createConnection({
82+
host: 'example.org',
83+
port: port,
84+
lookup,
85+
autoSelectFamilyAttemptTimeout,
86+
});
87+
88+
let response = '';
89+
connection.setEncoding('utf-8');
90+
91+
connection.on('ready', common.mustCall(() => {
92+
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
93+
}));
94+
95+
connection.on('data', (chunk) => {
96+
response += chunk;
97+
});
98+
99+
connection.on('end', common.mustCall(() => {
100+
assert.strictEqual(response, 'response-ipv4');
101+
ipv4Server.close();
102+
dnsServer.close();
103+
}));
104+
105+
connection.write('request');
106+
}));
107+
}));
108+
}

0 commit comments

Comments
 (0)