Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 863bdb7

Browse filesBrowse files
ShogunPandaruyadorno
authored andcommitted
net: add autoSelectFamily global getter and setter
PR-URL: #45777 Backport-PR-URL: #49016 Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent e7d2e8e commit 863bdb7
Copy full SHA for 863bdb7
Expand file treeCollapse file tree

12 files changed

+355
-9
lines changed
Open diff view settings
Collapse file

‎doc/api/cli.md‎

Copy file name to clipboardExpand all lines: doc/api/cli.md
+10Lines changed: 10 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
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
@@ -1861,6 +1870,7 @@ Node.js options that are allowed are:
18611870
* `--disable-proto`
18621871
* `--dns-result-order`
18631872
* `--enable-fips`
1873+
* `--enable-network-family-autoselection`
18641874
* `--enable-source-maps`
18651875
* `--experimental-abortcontroller`
18661876
* `--experimental-global-customevent`
Collapse file

‎doc/api/net.md‎

Copy file name to clipboardExpand all lines: doc/api/net.md
+44-2Lines changed: 44 additions & 2 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,20 @@ Returns the bound `address`, the address `family` name and `port` of the
778778
socket as reported by the operating system:
779779
`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`
780780

781+
### `socket.autoSelectFamilyAttemptedAddresses`
782+
783+
<!-- YAML
784+
added: REPLACEME
785+
-->
786+
787+
* {string\[]}
788+
789+
This property is only present if the family autoselection algorithm is enabled in
790+
[`socket.connect(options)`][] and it is an array of the addresses that have been attempted.
791+
792+
Each address is a string in the form of `$IP:$PORT`. If the connection was successful,
793+
then the last address is the one that the socket is currently connected to.
794+
781795
### `socket.bufferSize`
782796

783797
<!-- YAML
@@ -854,6 +868,11 @@ behavior.
854868
<!-- YAML
855869
added: v0.1.90
856870
changes:
871+
- version: REPLACEME
872+
pr-url: https://github.com/nodejs/node/pull/45777
873+
description: The default value for autoSelectFamily option can be changed
874+
at runtime using `setDefaultAutoSelectFamily` or via the
875+
command line option `--enable-network-family-autoselection`.
857876
- version: v18.13.0
858877
pr-url: https://github.com/nodejs/node/pull/44731
859878
description: Added the `autoSelectFamily` option.
@@ -905,12 +924,14 @@ For TCP connections, available `options` are:
905924
that loosely implements section 5 of [RFC 8305][].
906925
The `all` option passed to lookup is set to `true` and the sockets attempts to connect to all
907926
obtained IPv6 and IPv4 addresses, in sequence, until a connection is established.
908-
The first returned AAAA address is tried first, then the first returned A address and so on.
927+
The first returned AAAA address is tried first, then the first returned A address,
928+
then the second returned AAAA address and so on.
909929
Each connection attempt is given the amount of time specified by the `autoSelectFamilyAttemptTimeout`
910930
option before timing out and trying the next address.
911931
Ignored if the `family` option is not `0` or if `localAddress` is set.
912932
Connection errors are not emitted if at least one connection succeeds.
913-
**Default:** `false`.
933+
**Default:** initially `false`, but it can be changed at runtime using [`net.setDefaultAutoSelectFamily(value)`][]
934+
or via the command line option `--enable-network-family-autoselection`.
914935
* `autoSelectFamilyAttemptTimeout` {number}: The amount of time in milliseconds to wait
915936
for a connection attempt to finish before trying the next address when using the `autoSelectFamily` option.
916937
If set to a positive integer less than `10`, then the value `10` will be used instead.
@@ -1499,6 +1520,26 @@ immediately initiates connection with
14991520
[`socket.connect(port[, host][, connectListener])`][`socket.connect(port)`],
15001521
then returns the `net.Socket` that starts the connection.
15011522

1523+
## `net.setDefaultAutoSelectFamily(value)`
1524+
1525+
<!-- YAML
1526+
added: REPLACEME
1527+
-->
1528+
1529+
Sets the default value of the `autoSelectFamily` option of [`socket.connect(options)`][].
1530+
1531+
* `value` {boolean} The new default value. The initial default value is `false`.
1532+
1533+
## `net.getDefaultAutoSelectFamily()`
1534+
1535+
<!-- YAML
1536+
added: REPLACEME
1537+
-->
1538+
1539+
Gets the current default value of the `autoSelectFamily` option of [`socket.connect(options)`][].
1540+
1541+
* Returns: {boolean} The current default value of the `autoSelectFamily` option.
1542+
15021543
## `net.createServer([options][, connectionListener])`
15031544

15041545
<!-- YAML
@@ -1683,6 +1724,7 @@ net.isIPv6('fhqwhgads'); // returns false
16831724
[`net.createConnection(path)`]: #netcreateconnectionpath-connectlistener
16841725
[`net.createConnection(port, host)`]: #netcreateconnectionport-host-connectlistener
16851726
[`net.createServer()`]: #netcreateserveroptions-connectionlistener
1727+
[`net.setDefaultAutoSelectFamily(value)`]: #netsetdefaultautoselectfamilyvalue
16861728
[`new net.Socket(options)`]: #new-netsocketoptions
16871729
[`readable.setEncoding()`]: stream.md#readablesetencodingencoding
16881730
[`server.close()`]: #serverclosecallback
Collapse file

‎lib/net.js‎

Copy file name to clipboardExpand all lines: lib/net.js
+25-6Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,14 @@ const {
124124
DTRACE_NET_SERVER_CONNECTION,
125125
DTRACE_NET_STREAM_END,
126126
} = require('internal/dtrace');
127+
const { getOptionValue } = require('internal/options');
127128

128129
// Lazy loaded to improve startup performance.
129130
let cluster;
130131
let dns;
131132
let BlockList;
132133
let SocketAddress;
134+
let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselection');
133135

134136
const { clearTimeout, setTimeout } = require('timers');
135137
const { kTimeout } = require('internal/timers');
@@ -244,6 +246,14 @@ function connect(...args) {
244246
return socket.connect(normalized);
245247
}
246248

249+
function getDefaultAutoSelectFamily() {
250+
return autoSelectFamilyDefault;
251+
}
252+
253+
function setDefaultAutoSelectFamily(value) {
254+
validateBoolean(value, 'value');
255+
autoSelectFamilyDefault = value;
256+
}
247257

248258
// Returns an array [options, cb], where options is an object,
249259
// cb is either a function or null.
@@ -1116,6 +1126,8 @@ function internalConnectMultiple(context) {
11161126
req.localAddress = localAddress;
11171127
req.localPort = localPort;
11181128

1129+
ArrayPrototypePush(self.autoSelectFamilyAttemptedAddresses, `${address}:${port}`);
1130+
11191131
if (addressType === 4) {
11201132
err = handle.connect(req, address, port);
11211133
} else {
@@ -1208,9 +1220,9 @@ function socketToDnsFamily(family) {
12081220
}
12091221

12101222
function lookupAndConnect(self, options) {
1211-
const { localAddress, localPort, autoSelectFamily } = options;
1223+
const { localAddress, localPort } = options;
12121224
const host = options.host || 'localhost';
1213-
let { port, autoSelectFamilyAttemptTimeout } = options;
1225+
let { port, autoSelectFamilyAttemptTimeout, autoSelectFamily } = options;
12141226

12151227
if (localAddress && !isIP(localAddress)) {
12161228
throw new ERR_INVALID_IP_ADDRESS(localAddress);
@@ -1229,11 +1241,14 @@ function lookupAndConnect(self, options) {
12291241
}
12301242
port |= 0;
12311243

1232-
if (autoSelectFamily !== undefined) {
1233-
validateBoolean(autoSelectFamily);
1244+
1245+
if (autoSelectFamily != null) {
1246+
validateBoolean(autoSelectFamily, 'options.autoSelectFamily');
1247+
} else {
1248+
autoSelectFamily = autoSelectFamilyDefault;
12341249
}
12351250

1236-
if (autoSelectFamilyAttemptTimeout !== undefined) {
1251+
if (autoSelectFamilyAttemptTimeout != null) {
12371252
validateInt32(autoSelectFamilyAttemptTimeout, 'options.autoSelectFamilyAttemptTimeout', 1);
12381253

12391254
if (autoSelectFamilyAttemptTimeout < 10) {
@@ -1257,7 +1272,7 @@ function lookupAndConnect(self, options) {
12571272
return;
12581273
}
12591274

1260-
if (options.lookup !== undefined)
1275+
if (options.lookup != null)
12611276
validateFunction(options.lookup, 'options.lookup');
12621277

12631278
if (dns === undefined) dns = require('dns');
@@ -1394,6 +1409,8 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
13941409
}
13951410
}
13961411

1412+
self.autoSelectFamilyAttemptedAddresses = [];
1413+
13971414
const context = {
13981415
socket: self,
13991416
addresses,
@@ -2262,4 +2279,6 @@ module.exports = {
22622279
Server,
22632280
Socket,
22642281
Stream: Socket, // Legacy naming
2282+
getDefaultAutoSelectFamily,
2283+
setDefaultAutoSelectFamily,
22652284
};
Collapse file

‎src/node_options.cc‎

Copy file name to clipboardExpand all lines: src/node_options.cc
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
359359
"returned)",
360360
&EnvironmentOptions::dns_result_order,
361361
kAllowedInEnvvar);
362+
AddOption("--enable-network-family-autoselection",
363+
"Enable network address family autodetection algorithm",
364+
&EnvironmentOptions::enable_network_family_autoselection,
365+
kAllowedInEnvvar);
362366
AddOption("--enable-source-maps",
363367
"Source Map V3 support for stack traces",
364368
&EnvironmentOptions::enable_source_maps,
Collapse file

‎src/node_options.h‎

Copy file name to clipboardExpand all lines: src/node_options.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ class EnvironmentOptions : public Options {
128128
bool frozen_intrinsics = false;
129129
int64_t heap_snapshot_near_heap_limit = 0;
130130
std::string heap_snapshot_signal;
131+
bool enable_network_family_autoselection = false;
131132
uint64_t max_http_header_size = 16 * 1024;
132133
bool deprecation = true;
133134
bool force_async_hooks_checks = true;
Collapse file
File renamed without changes.
Collapse file
File renamed without changes.
Collapse file
+108Lines 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+
}
Collapse file
File renamed without changes.
Collapse file

‎test/parallel/test-net-happy-eyeballs.js‎ ‎…st/parallel/test-net-autoselectfamily.js‎test/parallel/test-net-happy-eyeballs.js renamed to test/parallel/test-net-autoselectfamily.js test/parallel/test-net-happy-eyeballs.js renamed to test/parallel/test-net-autoselectfamily.js

Copy file name to clipboardExpand all lines: test/parallel/test-net-autoselectfamily.js
+14-1Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) {
7474
});
7575

7676
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
77+
const port = ipv4Server.address().port;
78+
7779
const connection = createConnection({
7880
host: 'example.org',
79-
port: ipv4Server.address().port,
81+
port: port,
8082
lookup,
8183
autoSelectFamily: true,
8284
autoSelectFamilyAttemptTimeout,
@@ -85,6 +87,10 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) {
8587
let response = '';
8688
connection.setEncoding('utf-8');
8789

90+
connection.on('ready', common.mustCall(() => {
91+
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`, `127.0.0.1:${port}`]);
92+
}));
93+
8894
connection.on('data', (chunk) => {
8995
response += chunk;
9096
});
@@ -132,6 +138,10 @@ if (common.hasIPv6) {
132138
let response = '';
133139
connection.setEncoding('utf-8');
134140

141+
connection.on('ready', common.mustCall(() => {
142+
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, [`::1:${port}`]);
143+
}));
144+
135145
connection.on('data', (chunk) => {
136146
response += chunk;
137147
});
@@ -162,6 +172,7 @@ if (common.hasIPv6) {
162172

163173
connection.on('ready', common.mustNotCall());
164174
connection.on('error', common.mustCall((error) => {
175+
assert.deepStrictEqual(connection.autoSelectFamilyAttemptedAddresses, ['::1:10', '127.0.0.1:10']);
165176
assert.strictEqual(error.constructor.name, 'AggregateError');
166177
assert.strictEqual(error.errors.length, 2);
167178

@@ -199,6 +210,8 @@ if (common.hasIPv6) {
199210

200211
connection.on('ready', common.mustNotCall());
201212
connection.on('error', common.mustCall((error) => {
213+
assert.strictEqual(connection.autoSelectFamilyAttemptedAddresses, undefined);
214+
202215
if (common.hasIPv6) {
203216
assert.strictEqual(error.code, 'ECONNREFUSED');
204217
assert.strictEqual(error.message, `connect ECONNREFUSED ::1:${port}`);

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.