Skip to content

Commit e8289a8

Browse files
ShogunPandaruyadorno
authored andcommittedAug 14, 2023
net: fix family autoselection timeout handling
PR-URL: #47860 Backport-PR-URL: #49016 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent 863bdb7 commit e8289a8

File tree

2 files changed

+88
-16
lines changed

2 files changed

+88
-16
lines changed
 

‎lib/net.js

+27-13
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ let autoSelectFamilyDefault = getOptionValue('--enable-network-family-autoselect
135135

136136
const { clearTimeout, setTimeout } = require('timers');
137137
const { kTimeout } = require('internal/timers');
138-
const kTimeoutTriggered = Symbol('kTimeoutTriggered');
139138

140139
const DEFAULT_IPV4_ADDR = '0.0.0.0';
141140
const DEFAULT_IPV6_ADDR = '::';
@@ -1093,9 +1092,11 @@ function internalConnectMultiple(context) {
10931092
return;
10941093
}
10951094

1095+
1096+
const current = context.current++;
1097+
const handle = current === 0 ? self._handle : new TCP(TCPConstants.SOCKET);
10961098
const { localPort, port, flags } = context;
1097-
const { address, family: addressType } = context.addresses[context.current++];
1098-
const handle = new TCP(TCPConstants.SOCKET);
1099+
const { address, family: addressType } = context.addresses[current];
10991100
let localAddress;
11001101
let err;
11011102

@@ -1120,7 +1121,7 @@ function internalConnectMultiple(context) {
11201121
}
11211122

11221123
const req = new TCPConnectWrap();
1123-
req.oncomplete = FunctionPrototypeBind(afterConnectMultiple, undefined, context);
1124+
req.oncomplete = FunctionPrototypeBind(afterConnectMultiple, undefined, context, current);
11241125
req.address = address;
11251126
req.port = port;
11261127
req.localAddress = localAddress;
@@ -1147,8 +1148,12 @@ function internalConnectMultiple(context) {
11471148
return;
11481149
}
11491150

1150-
// If the attempt has not returned an error, start the connection timer
1151-
context[kTimeout] = setTimeout(internalConnectMultipleTimeout, context.timeout, context, req);
1151+
if (current < context.addresses.length - 1) {
1152+
debug('connect/multiple: setting the attempt timeout to %d ms', context.timeout);
1153+
1154+
// If the attempt has not returned an error, start the connection timer
1155+
context[kTimeout] = setTimeout(internalConnectMultipleTimeout, context.timeout, context, req, handle);
1156+
}
11521157
}
11531158

11541159
Socket.prototype.connect = function(...args) {
@@ -1419,7 +1424,6 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
14191424
localPort,
14201425
timeout,
14211426
[kTimeout]: null,
1422-
[kTimeoutTriggered]: false,
14231427
errors: [],
14241428
};
14251429

@@ -1522,12 +1526,20 @@ function afterConnect(status, handle, req, readable, writable) {
15221526
}
15231527
}
15241528

1525-
function afterConnectMultiple(context, status, handle, req, readable, writable) {
1526-
const self = context.socket;
1527-
1529+
function afterConnectMultiple(context, current, status, handle, req, readable, writable) {
15281530
// Make sure another connection is not spawned
15291531
clearTimeout(context[kTimeout]);
15301532

1533+
// One of the connection has completed and correctly dispatched but after timeout, ignore this one
1534+
if (status === 0 && current !== context.current - 1) {
1535+
debug('connect/multiple: ignoring successful but timedout connection to %s:%s', req.address, req.port);
1536+
handle.close();
1537+
return;
1538+
}
1539+
1540+
const self = context.socket;
1541+
1542+
15311543
// Some error occurred, add to the list of exceptions
15321544
if (status !== 0) {
15331545
let details;
@@ -1552,7 +1564,7 @@ function afterConnectMultiple(context, status, handle, req, readable, writable)
15521564
}
15531565

15541566
// One of the connection has completed and correctly dispatched but after timeout, ignore this one
1555-
if (context[kTimeoutTriggered]) {
1567+
if (status === 0 && current !== context.current - 1) {
15561568
debug('connect/multiple: ignoring successful but timedout connection to %s:%s', req.address, req.port);
15571569
handle.close();
15581570
return;
@@ -1578,8 +1590,10 @@ function afterConnectMultiple(context, status, handle, req, readable, writable)
15781590
afterConnect(status, handle, req, readable, writable);
15791591
}
15801592

1581-
function internalConnectMultipleTimeout(context, req) {
1582-
context[kTimeoutTriggered] = true;
1593+
function internalConnectMultipleTimeout(context, req, handle) {
1594+
debug('connect/multiple: connection to %s:%s timed out', req.address, req.port);
1595+
req.oncomplete = undefined;
1596+
handle.close();
15831597
internalConnectMultiple(context);
15841598
}
15851599

‎test/parallel/test-net-autoselectfamily.js

+61-3
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,15 @@ function _lookup(resolver, hostname, options, cb) {
3636
});
3737
}
3838

39-
function createDnsServer(ipv6Addr, ipv4Addr, cb) {
39+
function createDnsServer(ipv6Addrs, ipv4Addrs, cb) {
40+
if (!Array.isArray(ipv6Addrs)) {
41+
ipv6Addrs = [ipv6Addrs];
42+
}
43+
44+
if (!Array.isArray(ipv4Addrs)) {
45+
ipv4Addrs = [ipv4Addrs];
46+
}
47+
4048
// Create a DNS server which replies with a AAAA and a A record for the same host
4149
const socket = dgram.createSocket('udp4');
4250

@@ -49,8 +57,8 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) {
4957
id: parsed.id,
5058
questions: parsed.questions,
5159
answers: [
52-
{ type: 'AAAA', address: ipv6Addr, ttl: 123, domain: 'example.org' },
53-
{ type: 'A', address: ipv4Addr, ttl: 123, domain: 'example.org' },
60+
...ipv6Addrs.map((address) => ({ type: 'AAAA', address, ttl: 123, domain: 'example.org' })),
61+
...ipv4Addrs.map((address) => ({ type: 'A', address, ttl: 123, domain: 'example.org' })),
5462
]
5563
}), port, address);
5664
}));
@@ -106,6 +114,56 @@ function createDnsServer(ipv6Addr, ipv4Addr, cb) {
106114
}));
107115
}
108116

117+
// Test that only the last successful connection is established.
118+
{
119+
createDnsServer(
120+
'::1',
121+
['104.20.22.46', '104.20.23.46', '127.0.0.1'],
122+
common.mustCall(function({ dnsServer, lookup }) {
123+
const ipv4Server = createServer((socket) => {
124+
socket.on('data', common.mustCall(() => {
125+
socket.write('response-ipv4');
126+
socket.end();
127+
}));
128+
});
129+
130+
ipv4Server.listen(0, '127.0.0.1', common.mustCall(() => {
131+
const port = ipv4Server.address().port;
132+
133+
const connection = createConnection({
134+
host: 'example.org',
135+
port: port,
136+
lookup,
137+
autoSelectFamily: true,
138+
autoSelectFamilyAttemptTimeout,
139+
});
140+
141+
let response = '';
142+
connection.setEncoding('utf-8');
143+
144+
connection.on('ready', common.mustCall(() => {
145+
assert.deepStrictEqual(
146+
connection.autoSelectFamilyAttemptedAddresses,
147+
[`::1:${port}`, `104.20.22.46:${port}`, `104.20.23.46:${port}`, `127.0.0.1:${port}`]
148+
);
149+
}));
150+
151+
connection.on('data', (chunk) => {
152+
response += chunk;
153+
});
154+
155+
connection.on('end', common.mustCall(() => {
156+
assert.strictEqual(response, 'response-ipv4');
157+
ipv4Server.close();
158+
dnsServer.close();
159+
}));
160+
161+
connection.write('request');
162+
}));
163+
})
164+
);
165+
}
166+
109167
// Test that IPV4 is NOT reached if IPV6 is reachable
110168
if (common.hasIPv6) {
111169
createDnsServer('::1', '127.0.0.1', common.mustCall(function({ dnsServer, lookup }) {

0 commit comments

Comments
 (0)
Please sign in to comment.