Skip to content

Commit 5388aa7

Browse files
committed
net: improve network family autoselection handle handling
1 parent ee1b6ab commit 5388aa7

File tree

4 files changed

+125
-36
lines changed

4 files changed

+125
-36
lines changed

lib/net.js

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const {
5656
UV_EINVAL,
5757
UV_ENOTCONN,
5858
UV_ECANCELED,
59+
UV_ETIMEDOUT,
5960
} = internalBinding('uv');
6061

6162
const { Buffer } = require('buffer');
@@ -483,6 +484,10 @@ function Socket(options) {
483484
}
484485
}
485486

487+
if (options.signal) {
488+
addClientAbortSignalOption(this, options);
489+
}
490+
486491
// Reserve properties
487492
this.server = null;
488493
this._server = null;
@@ -1092,6 +1097,11 @@ function internalConnectMultiple(context, canceled) {
10921097
clearTimeout(context[kTimeout]);
10931098
const self = context.socket;
10941099

1100+
// We were requested to abort. Stop all operations
1101+
if (self._aborted) {
1102+
return;
1103+
}
1104+
10951105
// All connections have been tried without success, destroy with error
10961106
if (canceled || context.current === context.addresses.length) {
10971107
if (context.errors.length === 0) {
@@ -1106,7 +1116,11 @@ function internalConnectMultiple(context, canceled) {
11061116
assert(self.connecting);
11071117

11081118
const current = context.current++;
1109-
const handle = current === 0 ? self._handle : new TCP(TCPConstants.SOCKET);
1119+
1120+
if (current > 0) {
1121+
self[kReinitializeHandle](new TCP(TCPConstants.SOCKET));
1122+
}
1123+
11101124
const { localPort, port, flags } = context;
11111125
const { address, family: addressType } = context.addresses[current];
11121126
let localAddress;
@@ -1115,16 +1129,16 @@ function internalConnectMultiple(context, canceled) {
11151129
if (localPort) {
11161130
if (addressType === 4) {
11171131
localAddress = DEFAULT_IPV4_ADDR;
1118-
err = handle.bind(localAddress, localPort);
1132+
err = self._handle.bind(localAddress, localPort);
11191133
} else { // addressType === 6
11201134
localAddress = DEFAULT_IPV6_ADDR;
1121-
err = handle.bind6(localAddress, localPort, flags);
1135+
err = self._handle.bind6(localAddress, localPort, flags);
11221136
}
11231137

11241138
debug('connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)',
11251139
localAddress, localPort, addressType);
11261140

1127-
err = checkBindError(err, localPort, handle);
1141+
err = checkBindError(err, localPort, self._handle);
11281142
if (err) {
11291143
ArrayPrototypePush(context.errors, exceptionWithHostPort(err, 'bind', localAddress, localPort));
11301144
internalConnectMultiple(context);
@@ -1144,9 +1158,9 @@ function internalConnectMultiple(context, canceled) {
11441158
ArrayPrototypePush(self.autoSelectFamilyAttemptedAddresses, `${address}:${port}`);
11451159

11461160
if (addressType === 4) {
1147-
err = handle.connect(req, address, port);
1161+
err = self._handle.connect(req, address, port);
11481162
} else {
1149-
err = handle.connect6(req, address, port);
1163+
err = self._handle.connect6(req, address, port);
11501164
}
11511165

11521166
if (err) {
@@ -1166,7 +1180,7 @@ function internalConnectMultiple(context, canceled) {
11661180
debug('connect/multiple: setting the attempt timeout to %d ms', context.timeout);
11671181

11681182
// If the attempt has not returned an error, start the connection timer
1169-
context[kTimeout] = setTimeout(internalConnectMultipleTimeout, context.timeout, context, req, handle);
1183+
context[kTimeout] = setTimeout(internalConnectMultipleTimeout, context.timeout, context, req, self._handle);
11701184
}
11711185
}
11721186

@@ -1184,6 +1198,15 @@ Socket.prototype.connect = function(...args) {
11841198
const options = normalized[0];
11851199
const cb = normalized[1];
11861200

1201+
if (cb !== null) {
1202+
this.once('connect', cb);
1203+
}
1204+
1205+
// If the parent is already connecting, do not attempt to connect again
1206+
if (this._parent && this._parent.connecting) {
1207+
return this;
1208+
}
1209+
11871210
// options.port === null will be checked later.
11881211
if (options.port === undefined && options.path == null)
11891212
throw new ERR_MISSING_ARGS(['options', 'port', 'path']);
@@ -1208,10 +1231,6 @@ Socket.prototype.connect = function(...args) {
12081231
initSocketHandle(this);
12091232
}
12101233

1211-
if (cb !== null) {
1212-
this.once('connect', cb);
1213-
}
1214-
12151234
this._unrefTimer();
12161235

12171236
this.connecting = true;
@@ -1584,7 +1603,47 @@ function afterConnect(status, handle, req, readable, writable) {
15841603
}
15851604
}
15861605

1606+
function addClientAbortSignalOption(self, options) {
1607+
validateAbortSignal(options.signal, 'options.signal');
1608+
const { signal } = options;
1609+
1610+
function onAbort() {
1611+
signal.removeEventListener('abort', onAbort);
1612+
self._aborted = true;
1613+
}
1614+
1615+
if (signal.aborted) {
1616+
process.nextTick(onAbort);
1617+
} else {
1618+
process.nextTick(() => {
1619+
signal.addEventListener('abort', onAbort);
1620+
});
1621+
}
1622+
}
1623+
1624+
function createConnectionError(req, status) {
1625+
let details;
1626+
1627+
if (req.localAddress && req.localPort) {
1628+
details = req.localAddress + ':' + req.localPort;
1629+
}
1630+
1631+
const ex = exceptionWithHostPort(status,
1632+
'connect',
1633+
req.address,
1634+
req.port,
1635+
details);
1636+
if (details) {
1637+
ex.localAddress = req.localAddress;
1638+
ex.localPort = req.localPort;
1639+
}
1640+
1641+
return ex;
1642+
}
1643+
15871644
function afterConnectMultiple(context, current, status, handle, req, readable, writable) {
1645+
debug('connect/multiple: connection attempt to %s:%s completed with status %s', req.address, req.port, status);
1646+
15881647
// Make sure another connection is not spawned
15891648
clearTimeout(context[kTimeout]);
15901649

@@ -1597,35 +1656,15 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
15971656

15981657
const self = context.socket;
15991658

1600-
16011659
// Some error occurred, add to the list of exceptions
16021660
if (status !== 0) {
1603-
let details;
1604-
if (req.localAddress && req.localPort) {
1605-
details = req.localAddress + ':' + req.localPort;
1606-
}
1607-
const ex = exceptionWithHostPort(status,
1608-
'connect',
1609-
req.address,
1610-
req.port,
1611-
details);
1612-
if (details) {
1613-
ex.localAddress = req.localAddress;
1614-
ex.localPort = req.localPort;
1615-
}
1616-
1617-
ArrayPrototypePush(context.errors, ex);
1661+
ArrayPrototypePush(context.errors, createConnectionError(req, status));
16181662

16191663
// Try the next address
16201664
internalConnectMultiple(context, status === UV_ECANCELED);
16211665
return;
16221666
}
16231667

1624-
if (context.current > 1 && self[kReinitializeHandle]) {
1625-
self[kReinitializeHandle](handle);
1626-
handle = self._handle;
1627-
}
1628-
16291668
if (hasObserver('net')) {
16301669
startPerf(
16311670
self,
@@ -1634,17 +1673,18 @@ function afterConnectMultiple(context, current, status, handle, req, readable, w
16341673
);
16351674
}
16361675

1637-
afterConnect(status, handle, req, readable, writable);
1676+
afterConnect(status, self._handle, req, readable, writable);
16381677
}
16391678

16401679
function internalConnectMultipleTimeout(context, req, handle) {
16411680
debug('connect/multiple: connection to %s:%s timed out', req.address, req.port);
16421681
req.oncomplete = undefined;
1682+
ArrayPrototypePush(context.errors, createConnectionError(req, UV_ETIMEDOUT));
16431683
handle.close();
16441684
internalConnectMultiple(context);
16451685
}
16461686

1647-
function addAbortSignalOption(self, options) {
1687+
function addServerAbortSignalOption(self, options) {
16481688
if (options?.signal === undefined) {
16491689
return;
16501690
}
@@ -1933,7 +1973,7 @@ Server.prototype.listen = function(...args) {
19331973
listenInCluster(this, null, -1, -1, backlogFromArgs);
19341974
return this;
19351975
}
1936-
addAbortSignalOption(this, options);
1976+
addServerAbortSignalOption(this, options);
19371977
// (handle[, backlog][, cb]) where handle is an object with a fd
19381978
if (typeof options.fd === 'number' && options.fd >= 0) {
19391979
listenInCluster(this, null, null, null, backlogFromArgs, options.fd);

test/internet/test-https-autoselectfamily-slow-timeout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const { request } = require('https');
1111

1212
request(
1313
`https://${addresses.INET_HOST}/en`,
14-
// Purposely set this to false because we want all connection but the last to fail
14+
// Purposely set this to a low value because we want all connection but the last to fail
1515
{ autoSelectFamily: true, autoSelectFamilyAttemptTimeout: 10 },
1616
(res) => {
1717
assert.strictEqual(res.statusCode, 200);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { addresses } = require('../common/internet');
5+
6+
const assert = require('assert');
7+
const { connect } = require('net');
8+
9+
// Test that when all errors are returned when no connections succeeded and that the close event is emitted
10+
{
11+
const connection = connect({
12+
host: addresses.INET_HOST,
13+
port: 10,
14+
autoSelectFamily: true,
15+
// Purposely set this to a low value because we want all non last connection to fail due to early timeout
16+
autoSelectFamilyAttemptTimeout: 10,
17+
});
18+
19+
connection.on('close', common.mustCall());
20+
connection.on('ready', common.mustNotCall());
21+
22+
connection.on('error', common.mustCall((error) => {
23+
assert.ok(connection.autoSelectFamilyAttemptedAddresses.length > 0);
24+
assert.strictEqual(error.constructor.name, 'AggregateError');
25+
assert.strictEqual(error.errors.length, 2);
26+
}));
27+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const { addresses: { INET_HOST } } = require('../common/internet');
5+
6+
if (!common.hasCrypto) {
7+
common.skip('missing crypto');
8+
}
9+
10+
const { Socket } = require('net');
11+
const { TLSSocket } = require('tls');
12+
13+
// Test that TLS connecting works with autoSelectFamily when using a backing socket
14+
{
15+
const socket = new Socket();
16+
const secureSocket = new TLSSocket(socket);
17+
18+
secureSocket.on('connect', common.mustCall(() => secureSocket.end()));
19+
20+
socket.connect({ host: INET_HOST, port: 443, servername: INET_HOST });
21+
secureSocket.connect({ host: INET_HOST, port: 443, servername: INET_HOST });
22+
}

0 commit comments

Comments
 (0)