Skip to content

Commit 0336bc7

Browse files
ShogunPandajuanarbol
authored andcommitted
net: fix family autoselection timeout handling
PR-URL: nodejs#47860 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 97ff063 commit 0336bc7

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 = '::';
@@ -1092,9 +1091,11 @@ function internalConnectMultiple(context) {
10921091
return;
10931092
}
10941093

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

@@ -1119,7 +1120,7 @@ function internalConnectMultiple(context) {
11191120
}
11201121

11211122
const req = new TCPConnectWrap();
1122-
req.oncomplete = FunctionPrototypeBind(afterConnectMultiple, undefined, context);
1123+
req.oncomplete = FunctionPrototypeBind(afterConnectMultiple, undefined, context, current);
11231124
req.address = address;
11241125
req.port = port;
11251126
req.localAddress = localAddress;
@@ -1146,8 +1147,12 @@ function internalConnectMultiple(context) {
11461147
return;
11471148
}
11481149

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

11531158
Socket.prototype.connect = function(...args) {
@@ -1418,7 +1423,6 @@ function lookupAndConnectMultiple(self, async_id_symbol, lookup, host, options,
14181423
localPort,
14191424
timeout,
14201425
[kTimeout]: null,
1421-
[kTimeoutTriggered]: false,
14221426
errors: [],
14231427
};
14241428

@@ -1521,12 +1525,20 @@ function afterConnect(status, handle, req, readable, writable) {
15211525
}
15221526
}
15231527

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

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

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

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

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)