24
24
const {
25
25
ArrayIsArray,
26
26
ArrayPrototypeIndexOf,
27
+ ArrayPrototypePush,
27
28
Boolean,
29
+ FunctionPrototypeBind,
30
+ MathMax,
28
31
Number,
29
32
NumberIsNaN,
30
33
NumberParseInt,
@@ -40,6 +43,7 @@ let debug = require('internal/util/debuglog').debuglog('net', (fn) => {
40
43
debug = fn ;
41
44
} ) ;
42
45
const {
46
+ kWrapConnectedHandle,
43
47
isIP,
44
48
isIPv4,
45
49
isIPv6,
@@ -96,6 +100,7 @@ const {
96
100
ERR_SOCKET_CLOSED ,
97
101
ERR_MISSING_ARGS ,
98
102
} ,
103
+ aggregateErrors,
99
104
errnoException,
100
105
exceptionWithHostPort,
101
106
genericNodeError,
@@ -105,6 +110,7 @@ const { isUint8Array } = require('internal/util/types');
105
110
const { queueMicrotask } = require ( 'internal/process/task_queues' ) ;
106
111
const {
107
112
validateAbortSignal,
113
+ validateBoolean,
108
114
validateFunction,
109
115
validateInt32,
110
116
validateNumber,
@@ -123,8 +129,9 @@ let dns;
123
129
let BlockList ;
124
130
let SocketAddress ;
125
131
126
- const { clearTimeout } = require ( 'timers' ) ;
132
+ const { clearTimeout, setTimeout } = require ( 'timers' ) ;
127
133
const { kTimeout } = require ( 'internal/timers' ) ;
134
+ const kTimeoutTriggered = Symbol ( 'kTimeoutTriggered' ) ;
128
135
129
136
const DEFAULT_IPV4_ADDR = '0.0.0.0' ;
130
137
const DEFAULT_IPV6_ADDR = '::' ;
@@ -1057,6 +1064,73 @@ function internalConnect(
1057
1064
}
1058
1065
1059
1066
1067
+ function internalConnectMultiple ( context ) {
1068
+ clearTimeout ( context [ kTimeout ] ) ;
1069
+ const self = context . socket ;
1070
+ assert ( self . connecting ) ;
1071
+
1072
+ // All connections have been tried without success, destroy with error
1073
+ if ( context . current === context . addresses . length ) {
1074
+ self . destroy ( aggregateErrors ( context . errors ) ) ;
1075
+ return ;
1076
+ }
1077
+
1078
+ const { localPort, port, flags } = context ;
1079
+ const { address, family : addressType } = context . addresses [ context . current ++ ] ;
1080
+ const handle = new TCP ( TCPConstants . SOCKET ) ;
1081
+ let localAddress ;
1082
+ let err ;
1083
+
1084
+ if ( localPort ) {
1085
+ if ( addressType === 4 ) {
1086
+ localAddress = DEFAULT_IPV4_ADDR ;
1087
+ err = handle . bind ( localAddress , localPort ) ;
1088
+ } else { // addressType === 6
1089
+ localAddress = DEFAULT_IPV6_ADDR ;
1090
+ err = handle . bind6 ( localAddress , localPort , flags ) ;
1091
+ }
1092
+
1093
+ debug ( 'connect/multiple: binding to localAddress: %s and localPort: %d (addressType: %d)' ,
1094
+ localAddress , localPort , addressType ) ;
1095
+
1096
+ err = checkBindError ( err , localPort , handle ) ;
1097
+ if ( err ) {
1098
+ ArrayPrototypePush ( context . errors , exceptionWithHostPort ( err , 'bind' , localAddress , localPort ) ) ;
1099
+ internalConnectMultiple ( context ) ;
1100
+ return ;
1101
+ }
1102
+ }
1103
+
1104
+ const req = new TCPConnectWrap ( ) ;
1105
+ req . oncomplete = FunctionPrototypeBind ( afterConnectMultiple , undefined , context ) ;
1106
+ req . address = address ;
1107
+ req . port = port ;
1108
+ req . localAddress = localAddress ;
1109
+ req . localPort = localPort ;
1110
+
1111
+ if ( addressType === 4 ) {
1112
+ err = handle . connect ( req , address , port ) ;
1113
+ } else {
1114
+ err = handle . connect6 ( req , address , port ) ;
1115
+ }
1116
+
1117
+ if ( err ) {
1118
+ const sockname = self . _getsockname ( ) ;
1119
+ let details ;
1120
+
1121
+ if ( sockname ) {
1122
+ details = sockname . address + ':' + sockname . port ;
1123
+ }
1124
+
1125
+ ArrayPrototypePush ( context . errors , exceptionWithHostPort ( err , 'connect' , address , port , details ) ) ;
1126
+ internalConnectMultiple ( context ) ;
1127
+ return ;
1128
+ }
1129
+
1130
+ // If the attempt has not returned an error, start the connection timer
1131
+ context [ kTimeout ] = setTimeout ( internalConnectMultipleTimeout , context . timeout , context , req ) ;
1132
+ }
1133
+
1060
1134
Socket . prototype . connect = function ( ...args ) {
1061
1135
let normalized ;
1062
1136
// If passed an array, it's treated as an array of arguments that have
@@ -1126,9 +1200,9 @@ function socketToDnsFamily(family) {
1126
1200
}
1127
1201
1128
1202
function lookupAndConnect ( self , options ) {
1129
- const { localAddress, localPort } = options ;
1203
+ const { localAddress, localPort, autoSelectFamily } = options ;
1130
1204
const host = options . host || 'localhost' ;
1131
- let { port } = options ;
1205
+ let { port, autoSelectFamilyAttemptTimeout } = options ;
1132
1206
1133
1207
if ( localAddress && ! isIP ( localAddress ) ) {
1134
1208
throw new ERR_INVALID_IP_ADDRESS ( localAddress ) ;
@@ -1147,6 +1221,20 @@ function lookupAndConnect(self, options) {
1147
1221
}
1148
1222
port |= 0 ;
1149
1223
1224
+ if ( autoSelectFamily !== undefined ) {
1225
+ validateBoolean ( autoSelectFamily ) ;
1226
+ }
1227
+
1228
+ if ( autoSelectFamilyAttemptTimeout !== undefined ) {
1229
+ validateInt32 ( autoSelectFamilyAttemptTimeout ) ;
1230
+
1231
+ if ( autoSelectFamilyAttemptTimeout < 10 ) {
1232
+ autoSelectFamilyAttemptTimeout = 10 ;
1233
+ }
1234
+ } else {
1235
+ autoSelectFamilyAttemptTimeout = 250 ;
1236
+ }
1237
+
1150
1238
// If host is an IP, skip performing a lookup
1151
1239
const addressType = isIP ( host ) ;
1152
1240
if ( addressType ) {
@@ -1181,6 +1269,26 @@ function lookupAndConnect(self, options) {
1181
1269
debug ( 'connect: dns options' , dnsopts ) ;
1182
1270
self . _host = host ;
1183
1271
const lookup = options . lookup || dns . lookup ;
1272
+
1273
+ if ( dnsopts . family !== 4 && dnsopts . family !== 6 && ! localAddress && autoSelectFamily ) {
1274
+ debug ( 'connect: autodetecting' ) ;
1275
+
1276
+ dnsopts . all = true ;
1277
+ lookupAndConnectMultiple (
1278
+ self ,
1279
+ async_id_symbol ,
1280
+ lookup ,
1281
+ host ,
1282
+ options ,
1283
+ dnsopts ,
1284
+ port ,
1285
+ localPort ,
1286
+ autoSelectFamilyAttemptTimeout
1287
+ ) ;
1288
+
1289
+ return ;
1290
+ }
1291
+
1184
1292
defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function ( ) {
1185
1293
lookup ( host , dnsopts , function emitLookup ( err , ip , addressType ) {
1186
1294
self . emit ( 'lookup' , err , ip , addressType , host ) ;
@@ -1215,6 +1323,86 @@ function lookupAndConnect(self, options) {
1215
1323
} ) ;
1216
1324
}
1217
1325
1326
+ function lookupAndConnectMultiple ( self , async_id_symbol , lookup , host , options , dnsopts , port , localPort , timeout ) {
1327
+ defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function emitLookup ( ) {
1328
+ lookup ( host , dnsopts , function emitLookup ( err , addresses ) {
1329
+ // It's possible we were destroyed while looking this up.
1330
+ // XXX it would be great if we could cancel the promise returned by
1331
+ // the look up.
1332
+ if ( ! self . connecting ) {
1333
+ return ;
1334
+ } else if ( err ) {
1335
+ // net.createConnection() creates a net.Socket object and immediately
1336
+ // calls net.Socket.connect() on it (that's us). There are no event
1337
+ // listeners registered yet so defer the error event to the next tick.
1338
+ process . nextTick ( connectErrorNT , self , err ) ;
1339
+ return ;
1340
+ }
1341
+
1342
+ // Filter addresses by only keeping the one which are either IPv4 or IPV6.
1343
+ // The first valid address determines which group has preference on the
1344
+ // alternate family sorting which happens later.
1345
+ const validIps = [ [ ] , [ ] ] ;
1346
+ let destinations ;
1347
+ for ( let i = 0 , l = addresses . length ; i < l ; i ++ ) {
1348
+ const address = addresses [ i ] ;
1349
+ const { address : ip , family : addressType } = address ;
1350
+ self . emit ( 'lookup' , err , ip , addressType , host ) ;
1351
+
1352
+ if ( isIP ( ip ) && ( addressType === 4 || addressType === 6 ) ) {
1353
+ if ( ! destinations ) {
1354
+ destinations = addressType === 6 ? { 6 : 0 , 4 : 1 } : { 4 : 0 , 6 : 1 } ;
1355
+ }
1356
+
1357
+ ArrayPrototypePush ( validIps [ destinations [ addressType ] ] , address ) ;
1358
+ }
1359
+ }
1360
+
1361
+ // When no AAAA or A records are available, fail on the first one
1362
+ if ( ! validIps [ 0 ] . length && ! validIps [ 1 ] . length ) {
1363
+ const { address : firstIp , family : firstAddressType } = addresses [ 0 ] ;
1364
+
1365
+ if ( ! isIP ( firstIp ) ) {
1366
+ err = new ERR_INVALID_IP_ADDRESS ( firstIp ) ;
1367
+ process . nextTick ( connectErrorNT , self , err ) ;
1368
+ } else if ( firstAddressType !== 4 && firstAddressType !== 6 ) {
1369
+ err = new ERR_INVALID_ADDRESS_FAMILY ( firstAddressType ,
1370
+ options . host ,
1371
+ options . port ) ;
1372
+ process . nextTick ( connectErrorNT , self , err ) ;
1373
+ }
1374
+
1375
+ return ;
1376
+ }
1377
+
1378
+ // Sort addresses alternating families
1379
+ const toAttempt = [ ] ;
1380
+ for ( let i = 0 , l = MathMax ( validIps [ 0 ] . length , validIps [ 1 ] . length ) ; i < l ; i ++ ) {
1381
+ if ( i in validIps [ 0 ] ) {
1382
+ ArrayPrototypePush ( toAttempt , validIps [ 0 ] [ i ] ) ;
1383
+ }
1384
+ if ( i in validIps [ 1 ] ) {
1385
+ ArrayPrototypePush ( toAttempt , validIps [ 1 ] [ i ] ) ;
1386
+ }
1387
+ }
1388
+
1389
+ const context = {
1390
+ socket : self ,
1391
+ addresses,
1392
+ current : 0 ,
1393
+ port,
1394
+ localPort,
1395
+ timeout,
1396
+ [ kTimeout ] : null ,
1397
+ [ kTimeoutTriggered ] : false ,
1398
+ errors : [ ] ,
1399
+ } ;
1400
+
1401
+ self . _unrefTimer ( ) ;
1402
+ defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , internalConnectMultiple , context ) ;
1403
+ } ) ;
1404
+ } ) ;
1405
+ }
1218
1406
1219
1407
function connectErrorNT ( self , err ) {
1220
1408
self . destroy ( err ) ;
@@ -1309,6 +1497,67 @@ function afterConnect(status, handle, req, readable, writable) {
1309
1497
}
1310
1498
}
1311
1499
1500
+ function afterConnectMultiple ( context , status , handle , req , readable , writable ) {
1501
+ const self = context . socket ;
1502
+
1503
+ // Make sure another connection is not spawned
1504
+ clearTimeout ( context [ kTimeout ] ) ;
1505
+
1506
+ // Some error occurred, add to the list of exceptions
1507
+ if ( status !== 0 ) {
1508
+ let details ;
1509
+ if ( req . localAddress && req . localPort ) {
1510
+ details = req . localAddress + ':' + req . localPort ;
1511
+ }
1512
+ const ex = exceptionWithHostPort ( status ,
1513
+ 'connect' ,
1514
+ req . address ,
1515
+ req . port ,
1516
+ details ) ;
1517
+ if ( details ) {
1518
+ ex . localAddress = req . localAddress ;
1519
+ ex . localPort = req . localPort ;
1520
+ }
1521
+
1522
+ ArrayPrototypePush ( context . errors , ex ) ;
1523
+
1524
+ // Try the next address
1525
+ internalConnectMultiple ( context ) ;
1526
+ return ;
1527
+ }
1528
+
1529
+ // One of the connection has completed and correctly dispatched but after timeout, ignore this one
1530
+ if ( context [ kTimeoutTriggered ] ) {
1531
+ debug ( 'connect/multiple: ignoring successful but timedout connection to %s:%s' , req . address , req . port ) ;
1532
+ handle . close ( ) ;
1533
+ return ;
1534
+ }
1535
+
1536
+ // Perform initialization sequence on the handle, then move on with the regular callback
1537
+ self . _handle = handle ;
1538
+ initSocketHandle ( self ) ;
1539
+
1540
+ if ( self [ kWrapConnectedHandle ] ) {
1541
+ self [ kWrapConnectedHandle ] ( handle ) ;
1542
+ initSocketHandle ( self ) ; // This is called again to initialize the TLSWrap
1543
+ }
1544
+
1545
+ if ( hasObserver ( 'net' ) ) {
1546
+ startPerf (
1547
+ self ,
1548
+ kPerfHooksNetConnectContext ,
1549
+ { type : 'net' , name : 'connect' , detail : { host : req . address , port : req . port } }
1550
+ ) ;
1551
+ }
1552
+
1553
+ afterConnect ( status , handle , req , readable , writable ) ;
1554
+ }
1555
+
1556
+ function internalConnectMultipleTimeout ( context , req ) {
1557
+ context [ kTimeoutTriggered ] = true ;
1558
+ internalConnectMultiple ( context ) ;
1559
+ }
1560
+
1312
1561
function addAbortSignalOption ( self , options ) {
1313
1562
if ( options ?. signal === undefined ) {
1314
1563
return ;
0 commit comments