Skip to content

Commit 8b51c1a

Browse files
ShogunPandaaduh95
authored andcommitted
net: enable autoSelectFamily by default
Co-authored-by: Antoine du Hamel <[email protected]> PR-URL: #46790 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Robert Nagy <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Rafael Gonzaga <[email protected]>
1 parent 069365c commit 8b51c1a

22 files changed

+147
-94
lines changed

doc/api/cli.md

+10-3
Original file line numberDiff line numberDiff line change
@@ -462,14 +462,20 @@ added: v6.0.0
462462
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built
463463
against FIPS-compatible OpenSSL.)
464464

465-
### `--enable-network-family-autoselection`
465+
### `--no-network-family-autoselection`
466466

467467
<!-- YAML
468468
added: v19.4.0
469+
changes:
470+
- version: REPLACEME
471+
pr-url: https://github.com/nodejs/node/pull/46790
472+
description: The flag was renamed from `--no-enable-network-family-autoselection`
473+
to `--no-network-family-autoselection`. The old name can still work as
474+
an alias.
469475
-->
470476

471-
Enables the family autoselection algorithm unless connection options explicitly
472-
disables it.
477+
Disables the family autoselection algorithm unless connection options explicitly
478+
enables it.
473479

474480
### `--enable-source-maps`
475481

@@ -2125,6 +2131,7 @@ Node.js options that are allowed are:
21252131
* `--no-extra-info-on-fatal-exception`
21262132
* `--no-force-async-hooks-checks`
21272133
* `--no-global-search-paths`
2134+
* `--no-network-family-autoselection`
21282135
* `--no-warnings`
21292136
* `--node-memory-debug`
21302137
* `--openssl-config`

doc/api/errors.md

+7
Original file line numberDiff line numberDiff line change
@@ -2593,6 +2593,13 @@ An attempt was made to operate on an already closed socket.
25932593
When calling [`net.Socket.write()`][] on a connecting socket and the socket was
25942594
closed before the connection was established.
25952595

2596+
<a id="ERR_SOCKET_CONNECTION_TIMEOUT"></a>
2597+
2598+
### `ERR_SOCKET_CONNECTION_TIMEOUT`
2599+
2600+
The socket was unable to connect to any address returned by the DNS within the
2601+
allowed timeout when using the family autoselection algorithm.
2602+
25962603
<a id="ERR_SOCKET_DGRAM_IS_CONNECTED"></a>
25972604

25982605
### `ERR_SOCKET_DGRAM_IS_CONNECTED`

doc/api/net.md

+14-5
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,12 @@ behavior.
870870
<!-- YAML
871871
added: v0.1.90
872872
changes:
873+
- version: REPLACEME
874+
pr-url: https://github.com/nodejs/node/pull/46790
875+
description: The default value for the autoSelectFamily option is now true.
876+
The `--enable-network-family-autoselection` CLI flag has been renamed
877+
to `--network-family-autoselection`. The old name is now an
878+
alias but it is discouraged.
873879
- version: v19.4.0
874880
pr-url: https://github.com/nodejs/node/pull/45777
875881
description: The default value for autoSelectFamily option can be changed
@@ -936,12 +942,12 @@ For TCP connections, available `options` are:
936942
option before timing out and trying the next address.
937943
Ignored if the `family` option is not `0` or if `localAddress` is set.
938944
Connection errors are not emitted if at least one connection succeeds.
939-
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][]
940-
or via the command line option `--enable-network-family-autoselection`.
945+
If all connections attempts fails, a single `AggregateError` with all failed attempts is emitted.
946+
**Default:** [`net.getDefaultAutoSelectFamily()`][]
941947
* `autoSelectFamilyAttemptTimeout` {number}: The amount of time in milliseconds to wait
942948
for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option.
943949
If set to a positive integer less than `10`, then the value `10` will be used instead.
944-
**Default:** initially `250`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamilyAttemptTimeout(value)`][]
950+
**Default:** [`net.getDefaultAutoSelectFamilyAttemptTimeout()`][]
945951

946952
For [IPC][] connections, available `options` are:
947953

@@ -1629,6 +1635,8 @@ added: v19.4.0
16291635
-->
16301636

16311637
Gets the current default value of the `autoSelectFamily` option of [`socket.connect(options)`][].
1638+
The initial default value is `true`, unless the command line option
1639+
`--no-network-family-autoselection` is provided.
16321640

16331641
* Returns: {boolean} The current default value of the `autoSelectFamily` option.
16341642

@@ -1649,6 +1657,7 @@ added: v19.8.0
16491657
-->
16501658

16511659
Gets the current default value of the `autoSelectFamilyAttemptTimeout` option of [`socket.connect(options)`][].
1660+
The initial default value is `250`.
16521661

16531662
* Returns: {number} The current default value of the `autoSelectFamilyAttemptTimeout` option.
16541663

@@ -1747,8 +1756,8 @@ net.isIPv6('fhqwhgads'); // returns false
17471756
[`net.createConnection(path)`]: #netcreateconnectionpath-connectlistener
17481757
[`net.createConnection(port, host)`]: #netcreateconnectionport-host-connectlistener
17491758
[`net.createServer()`]: #netcreateserveroptions-connectionlistener
1750-
[`net.setDefaultAutoSelectFamily(value)`]: #netsetdefaultautoselectfamilyvalue
1751-
[`net.setDefaultAutoSelectFamilyAttemptTimeout(value)`]: #netsetdefaultautoselectfamilyattempttimeoutvalue
1759+
[`net.getDefaultAutoSelectFamily()`]: #netgetdefaultautoselectfamily
1760+
[`net.getDefaultAutoSelectFamilyAttemptTimeout()`]: #netgetdefaultautoselectfamilyattempttimeout
17521761
[`new net.Socket(options)`]: #new-netsocketoptions
17531762
[`readable.setEncoding()`]: stream.md#readablesetencodingencoding
17541763
[`server.close()`]: #serverclosecallback

lib/internal/errors.js

+2
Original file line numberDiff line numberDiff line change
@@ -1567,6 +1567,8 @@ E('ERR_SOCKET_CLOSED', 'Socket is closed', Error);
15671567
E('ERR_SOCKET_CLOSED_BEFORE_CONNECTION',
15681568
'Socket closed before the connection was established',
15691569
Error);
1570+
E('ERR_SOCKET_CONNECTION_TIMEOUT',
1571+
'Socket connection timeout', Error);
15701572
E('ERR_SOCKET_DGRAM_IS_CONNECTED', 'Already connected', Error);
15711573
E('ERR_SOCKET_DGRAM_NOT_CONNECTED', 'Not connected', Error);
15721574
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running', Error);

lib/internal/net.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ function makeSyncWrite(fd) {
6767
}
6868

6969
module.exports = {
70-
kReinitializeHandle: Symbol('reinitializeHandle'),
70+
kReinitializeHandle: Symbol('kReinitializeHandle'),
7171
isIP,
7272
isIPv4,
7373
isIPv6,

lib/net.js

+54-16
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
const {
2525
ArrayIsArray,
26+
ArrayPrototypeIncludes,
2627
ArrayPrototypeIndexOf,
2728
ArrayPrototypePush,
2829
Boolean,
@@ -97,6 +98,7 @@ const {
9798
ERR_INVALID_HANDLE_TYPE,
9899
ERR_SERVER_ALREADY_LISTEN,
99100
ERR_SERVER_NOT_RUNNING,
101+
ERR_SOCKET_CONNECTION_TIMEOUT,
100102
ERR_SOCKET_CLOSED,
101103
ERR_SOCKET_CLOSED_BEFORE_CONNECTION,
102104
ERR_MISSING_ARGS,
@@ -127,7 +129,7 @@ let cluster;
127129
let dns;
128130
let BlockList;
129131
let SocketAddress;
130-
let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselection');
132+
let autoSelectFamilyDefault = getOptionValue('--network-family-autoselection');
131133
let autoSelectFamilyAttemptTimeoutDefault = 250;
132134

133135
const { clearTimeout, setTimeout } = require('timers');
@@ -1092,6 +1094,11 @@ function internalConnectMultiple(context, canceled) {
10921094

10931095
// All connections have been tried without success, destroy with error
10941096
if (canceled || context.current === context.addresses.length) {
1097+
if (context.errors.length === 0) {
1098+
self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT());
1099+
return;
1100+
}
1101+
10951102
self.destroy(aggregateErrors(context.errors));
10961103
return;
10971104
}
@@ -1322,6 +1329,7 @@ function lookupAndConnect(self, options) {
13221329
options,
13231330
dnsopts,
13241331
port,
1332+
localAddress,
13251333
localPort,
13261334
autoSelectFamilyAttemptTimeout,
13271335
);
@@ -1364,7 +1372,9 @@ function lookupAndConnect(self, options) {
13641372
});
13651373
}
13661374

1367-
function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options, dnsopts, port, localPort, timeout) {
1375+
function lookupAndConnectMultiple(
1376+
self, async_id_symbol, lookup, host, options, dnsopts, port, localAddress, localPort, timeout,
1377+
) {
13681378
defaultTriggerAsyncIdScope(self[async_id_symbol], function emitLookup() {
13691379
lookup(host, dnsopts, function emitLookup(err, addresses) {
13701380
// It's possible we were destroyed while looking this up.
@@ -1385,6 +1395,7 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
13851395
// Filter addresses by only keeping the one which are either IPv4 or IPV6.
13861396
// The first valid address determines which group has preference on the
13871397
// alternate family sorting which happens later.
1398+
const validAddresses = [[], []];
13881399
const validIps = [[], []];
13891400
let destinations;
13901401
for (let i = 0, l = addresses.length; i < l; i++) {
@@ -1397,12 +1408,19 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
13971408
destinations = addressType === 6 ? { 6: 0, 4: 1 } : { 4: 0, 6: 1 };
13981409
}
13991410

1400-
ArrayPrototypePush(validIps[destinations[addressType]], address);
1411+
const destination = destinations[addressType];
1412+
1413+
// Only try an address once
1414+
if (!ArrayPrototypeIncludes(validIps[destination], ip)) {
1415+
ArrayPrototypePush(validAddresses[destination], address);
1416+
ArrayPrototypePush(validIps[destination], ip);
1417+
}
14011418
}
14021419
}
14031420

1421+
14041422
// When no AAAA or A records are available, fail on the first one
1405-
if (!validIps[0].length && !validIps[1].length) {
1423+
if (!validAddresses[0].length && !validAddresses[1].length) {
14061424
const { address: firstIp, family: firstAddressType } = addresses[0];
14071425

14081426
if (!isIP(firstIp)) {
@@ -1420,16 +1438,36 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
14201438

14211439
// Sort addresses alternating families
14221440
const toAttempt = [];
1423-
for (let i = 0, l = MathMax(validIps[0].length, validIps[1].length); i < l; i++) {
1424-
if (i in validIps[0]) {
1425-
ArrayPrototypePush(toAttempt, validIps[0][i]);
1441+
for (let i = 0, l = MathMax(validAddresses[0].length, validAddresses[1].length); i < l; i++) {
1442+
if (i in validAddresses[0]) {
1443+
ArrayPrototypePush(toAttempt, validAddresses[0][i]);
14261444
}
1427-
if (i in validIps[1]) {
1428-
ArrayPrototypePush(toAttempt, validIps[1][i]);
1445+
if (i in validAddresses[1]) {
1446+
ArrayPrototypePush(toAttempt, validAddresses[1][i]);
14291447
}
14301448
}
14311449

1450+
if (toAttempt.length === 1) {
1451+
debug('connect/multiple: only one address found, switching back to single connection');
1452+
const { address: ip, family: addressType } = toAttempt[0];
1453+
1454+
self._unrefTimer();
1455+
defaultTriggerAsyncIdScope(
1456+
self[async_id_symbol],
1457+
internalConnect,
1458+
self,
1459+
ip,
1460+
port,
1461+
addressType,
1462+
localAddress,
1463+
localPort,
1464+
);
1465+
1466+
return;
1467+
}
1468+
14321469
self.autoSelectFamilyAttemptedAddresses = [];
1470+
debug('connect/multiple: will try the following addresses', toAttempt);
14331471

14341472
const context = {
14351473
socket: self,
@@ -1543,6 +1581,13 @@ function afterConnect(status, handle, req, readable, writable) {
15431581
}
15441582

15451583
function afterConnectMultiple(context, status, handle, req, readable, writable) {
1584+
// One of the connection has completed and correctly dispatched but after timeout, ignore this one
1585+
if (context[kTimeoutTriggered]) {
1586+
debug('connect/multiple: ignoring successful but timedout connection to %s:%s', req.address, req.port);
1587+
handle.close();
1588+
return;
1589+
}
1590+
15461591
const self = context.socket;
15471592

15481593
// Make sure another connection is not spawned
@@ -1571,13 +1616,6 @@ function afterConnectMultiple(context, status, handle, req, readable, writable)
15711616
return;
15721617
}
15731618

1574-
// One of the connection has completed and correctly dispatched but after timeout, ignore this one
1575-
if (context[kTimeoutTriggered]) {
1576-
debug('connect/multiple: ignoring successful but timedout connection to %s:%s', req.address, req.port);
1577-
handle.close();
1578-
return;
1579-
}
1580-
15811619
if (context.current > 1 && self[kReinitializeHandle]) {
15821620
self[kReinitializeHandle](handle);
15831621
handle = self._handle;

src/node_options.cc

+7-4
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,13 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
358358
"returned)",
359359
&EnvironmentOptions::dns_result_order,
360360
kAllowedInEnvvar);
361-
AddOption("--enable-network-family-autoselection",
362-
"Enable network address family autodetection algorithm",
363-
&EnvironmentOptions::enable_network_family_autoselection,
364-
kAllowedInEnvvar);
361+
AddOption("--network-family-autoselection",
362+
"Disable network address family autodetection algorithm",
363+
&EnvironmentOptions::network_family_autoselection,
364+
kAllowedInEnvvar,
365+
true);
366+
AddAlias("--enable-network-family-autoselection",
367+
"--network-family-autoselection");
365368
AddOption("--enable-source-maps",
366369
"Source Map V3 support for stack traces",
367370
&EnvironmentOptions::enable_source_maps,

src/node_options.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ class EnvironmentOptions : public Options {
132132
bool frozen_intrinsics = false;
133133
int64_t heap_snapshot_near_heap_limit = 0;
134134
std::string heap_snapshot_signal;
135-
bool enable_network_family_autoselection = false;
135+
bool network_family_autoselection = true;
136136
uint64_t max_http_header_size = 16 * 1024;
137137
bool deprecation = true;
138138
bool force_async_hooks_checks = true;

test/common/index.js

+10
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const process = global.process; // Some tests tamper with the process global.
2626
const assert = require('assert');
2727
const { exec, execSync, spawn, spawnSync } = require('child_process');
2828
const fs = require('fs');
29+
const net = require('net');
2930
// Do not require 'os' until needed so that test-os-checked-function can
3031
// monkey patch it. If 'os' is required here, that test will fail.
3132
const path = require('path');
@@ -137,6 +138,14 @@ const isPi = (() => {
137138

138139
const isDumbTerminal = process.env.TERM === 'dumb';
139140

141+
// When using high concurrency or in the CI we need much more time for each connection attempt
142+
const defaultAutoSelectFamilyAttemptTimeout = platformTimeout(2500);
143+
// Since this is also used by tools outside of the test suite,
144+
// make sure setDefaultAutoSelectFamilyAttemptTimeout
145+
if (typeof net.setDefaultAutoSelectFamilyAttemptTimeout === 'function') {
146+
net.setDefaultAutoSelectFamilyAttemptTimeout(platformTimeout(defaultAutoSelectFamilyAttemptTimeout));
147+
}
148+
140149
const buildType = process.config.target_defaults ?
141150
process.config.target_defaults.default_configuration :
142151
'Release';
@@ -886,6 +895,7 @@ const common = {
886895
canCreateSymLink,
887896
childShouldThrowAndAbort,
888897
createZeroFilledFile,
898+
defaultAutoSelectFamilyAttemptTimeout,
889899
expectsError,
890900
expectWarning,
891901
gcUntil,

test/internet/test-tls-autoselectfamily-servername.js

-4
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,8 @@ if (!common.hasCrypto) {
77
common.skip('missing crypto');
88
}
99

10-
const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
1110
const { connect } = require('tls');
1211

13-
// Some of the windows machines in the CI need more time to establish connection
14-
setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250));
15-
1612
// Test that TLS connecting works without autoSelectFamily
1713
{
1814
const socket = connect({

test/parallel/test-http-autoselectfamily.js

-4
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,9 @@ const assert = require('assert');
77
const dgram = require('dgram');
88
const { Resolver } = require('dns');
99
const { request, createServer } = require('http');
10-
const { setDefaultAutoSelectFamilyAttemptTimeout } = require('net');
1110

1211
// Test that happy eyeballs algorithm is properly implemented when using HTTP.
1312

14-
// Some of the windows machines in the CI need more time to establish connection
15-
setDefaultAutoSelectFamilyAttemptTimeout(common.platformTimeout(common.isWindows ? 1500 : 250));
16-
1713
function _lookup(resolver, hostname, options, cb) {
1814
resolver.resolve(hostname, 'ANY', (err, replies) => {
1915
assert.notStrictEqual(options.family, 4);

test/parallel/test-http2-ping-settings-heapdump.js

+1-10
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,6 @@ const v8 = require('v8');
1111
// after it is destroyed, either because they are detached from it or have been
1212
// destroyed themselves.
1313

14-
// We use an higher autoSelectFamilyAttemptTimeout in this test as the v8.getHeapSnapshot().resume()
15-
// will slow the connection flow and we don't want the second connection attempt to start.
16-
let autoSelectFamilyAttemptTimeout = common.platformTimeout(1000);
17-
if (common.isWindows) {
18-
// Some of the windows machines in the CI need more time to establish connection
19-
autoSelectFamilyAttemptTimeout = common.platformTimeout(10000);
20-
}
21-
2214
for (const variant of ['ping', 'settings']) {
2315
const server = http2.createServer();
2416
server.on('session', common.mustCall((session) => {
@@ -38,8 +30,7 @@ for (const variant of ['ping', 'settings']) {
3830
}));
3931

4032
server.listen(0, common.mustCall(() => {
41-
const client = http2.connect(`http://localhost:${server.address().port}`, { autoSelectFamilyAttemptTimeout },
42-
common.mustCall());
33+
const client = http2.connect(`http://localhost:${server.address().port}`, common.mustCall());
4334
client.on('error', (err) => {
4435
// We destroy the session so it's possible to get ECONNRESET here.
4536
if (err.code !== 'ECONNRESET')

0 commit comments

Comments
 (0)