@@ -96,6 +96,7 @@ const {
96
96
ERR_SOCKET_CLOSED ,
97
97
ERR_MISSING_ARGS ,
98
98
} ,
99
+ aggregateErrors,
99
100
errnoException,
100
101
exceptionWithHostPort,
101
102
genericNodeError,
@@ -1042,6 +1043,76 @@ function internalConnect(
1042
1043
}
1043
1044
1044
1045
1046
+ function internalConnectMultiple (
1047
+ self , addresses , port , localPort , flags
1048
+ ) {
1049
+ assert ( self . connecting ) ;
1050
+
1051
+ const context = {
1052
+ errors : [ ] ,
1053
+ connecting : 0 ,
1054
+ completed : false
1055
+ } ;
1056
+
1057
+ const oncomplete = afterConnectMultiple . bind ( self , context ) ;
1058
+
1059
+ for ( const { address, family : addressType } of addresses ) {
1060
+ const handle = new TCP ( TCPConstants . SOCKET ) ;
1061
+
1062
+ let localAddress ;
1063
+ let err ;
1064
+
1065
+ if ( localPort ) {
1066
+ if ( addressType === 4 ) {
1067
+ localAddress = DEFAULT_IPV4_ADDR ;
1068
+ err = handle . bind ( localAddress , localPort ) ;
1069
+ } else { // addressType === 6
1070
+ localAddress = DEFAULT_IPV6_ADDR ;
1071
+ err = handle . bind6 ( localAddress , localPort , flags ) ;
1072
+ }
1073
+
1074
+ debug ( 'connect/happy eyeballs: binding to localAddress: %s and localPort: %d (addressType: %d)' ,
1075
+ localAddress , localPort , addressType ) ;
1076
+
1077
+ err = checkBindError ( err , localPort , handle ) ;
1078
+ if ( err ) {
1079
+ context . errors . push ( exceptionWithHostPort ( err , 'bind' , localAddress , localPort ) ) ;
1080
+ continue ;
1081
+ }
1082
+ }
1083
+
1084
+ const req = new TCPConnectWrap ( ) ;
1085
+ req . oncomplete = oncomplete ;
1086
+ req . address = address ;
1087
+ req . port = port ;
1088
+ req . localAddress = localAddress ;
1089
+ req . localPort = localPort ;
1090
+
1091
+ if ( addressType === 4 ) {
1092
+ err = handle . connect ( req , address , port ) ;
1093
+ } else {
1094
+ err = handle . connect6 ( req , address , port ) ;
1095
+ }
1096
+
1097
+ if ( err ) {
1098
+ const sockname = self . _getsockname ( ) ;
1099
+ let details ;
1100
+
1101
+ if ( sockname ) {
1102
+ details = sockname . address + ':' + sockname . port ;
1103
+ }
1104
+
1105
+ context . errors . push ( exceptionWithHostPort ( err , 'connect' , address , port , details ) ) ;
1106
+ } else {
1107
+ context . connecting ++ ;
1108
+ }
1109
+ }
1110
+
1111
+ if ( context . errors . length && context . connecting === 0 ) {
1112
+ self . destroy ( aggregateErrors ( context . error ) ) ;
1113
+ }
1114
+ }
1115
+
1045
1116
Socket . prototype . connect = function ( ...args ) {
1046
1117
let normalized ;
1047
1118
// If passed an array, it's treated as an array of arguments that have
@@ -1166,6 +1237,64 @@ function lookupAndConnect(self, options) {
1166
1237
debug ( 'connect: dns options' , dnsopts ) ;
1167
1238
self . _host = host ;
1168
1239
const lookup = options . lookup || dns . lookup ;
1240
+
1241
+ if ( dnsopts . family !== 4 && dnsopts . family !== 6 && options . autoDetectFamily ) {
1242
+ debug ( 'connect: autodetecting family via happy eyeballs' ) ;
1243
+
1244
+ dnsopts . all = true ;
1245
+
1246
+ defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function ( ) {
1247
+ lookup ( host , dnsopts , function emitLookup ( err , addresses ) {
1248
+ const validAddresses = [ ] ;
1249
+
1250
+ // Gather all the addresses we can use for happy eyeballs
1251
+ for ( let i = 0 , l = addresses . length ; i < l ; i ++ ) {
1252
+ const address = addresses [ i ] ;
1253
+ const { address : ip , family : addressType } = address ;
1254
+ self . emit ( 'lookup' , err , ip , addressType , host ) ;
1255
+
1256
+ if ( isIP ( ip ) && ( addressType === 4 || addressType === 6 ) ) {
1257
+ validAddresses . push ( address ) ;
1258
+ }
1259
+ }
1260
+
1261
+ // It's possible we were destroyed while looking this up.
1262
+ // XXX it would be great if we could cancel the promise returned by
1263
+ // the look up.
1264
+ if ( ! self . connecting ) {
1265
+ return ;
1266
+ } else if ( err ) {
1267
+ // net.createConnection() creates a net.Socket object and immediately
1268
+ // calls net.Socket.connect() on it (that's us). There are no event
1269
+ // listeners registered yet so defer the error event to the next tick.
1270
+ process . nextTick ( connectErrorNT , self , err ) ;
1271
+ return ;
1272
+ }
1273
+
1274
+ const { address : firstIp , family : firstAddressType } = addresses [ 0 ] ;
1275
+
1276
+ if ( ! isIP ( firstIp ) ) {
1277
+ err = new ERR_INVALID_IP_ADDRESS ( firstIp ) ;
1278
+ process . nextTick ( connectErrorNT , self , err ) ;
1279
+ } else if ( firstAddressType !== 4 && firstAddressType !== 6 ) {
1280
+ err = new ERR_INVALID_ADDRESS_FAMILY ( firstAddressType ,
1281
+ options . host ,
1282
+ options . port ) ;
1283
+ process . nextTick ( connectErrorNT , self , err ) ;
1284
+ } else {
1285
+ self . _unrefTimer ( ) ;
1286
+ defaultTriggerAsyncIdScope (
1287
+ self [ async_id_symbol ] ,
1288
+ internalConnectMultiple ,
1289
+ self , validAddresses , port , localPort
1290
+ ) ;
1291
+ }
1292
+ } ) ;
1293
+ } ) ;
1294
+
1295
+ return ;
1296
+ }
1297
+
1169
1298
defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function ( ) {
1170
1299
lookup ( host , dnsopts , function emitLookup ( err , ip , addressType ) {
1171
1300
self . emit ( 'lookup' , err , ip , addressType , host ) ;
@@ -1294,6 +1423,57 @@ function afterConnect(status, handle, req, readable, writable) {
1294
1423
}
1295
1424
}
1296
1425
1426
+ function afterConnectMultiple ( context , status , handle , req , readable , writable ) {
1427
+ context . connecting -- ;
1428
+
1429
+ // Some error occurred, add to the list of exceptions
1430
+ if ( status !== 0 ) {
1431
+ let details ;
1432
+ if ( req . localAddress && req . localPort ) {
1433
+ details = req . localAddress + ':' + req . localPort ;
1434
+ }
1435
+ const ex = exceptionWithHostPort ( status ,
1436
+ 'connect' ,
1437
+ req . address ,
1438
+ req . port ,
1439
+ details ) ;
1440
+ if ( details ) {
1441
+ ex . localAddress = req . localAddress ;
1442
+ ex . localPort = req . localPort ;
1443
+ }
1444
+
1445
+ context . errors . push ( ex ) ;
1446
+
1447
+ if ( context . connecting === 0 ) {
1448
+ this . destroy ( aggregateErrors ( context . errors ) ) ;
1449
+ }
1450
+
1451
+ return ;
1452
+ }
1453
+
1454
+ // One of the connection has completed and correctly dispatched, ignore this one
1455
+ if ( context . completed ) {
1456
+ debug ( 'connect/happy eyeballs: ignoring successful connection to %s:%s' , req . address , req . port ) ;
1457
+ handle . close ( ) ;
1458
+ return ;
1459
+ }
1460
+
1461
+ // Mark the connection as successful
1462
+ context . completed = true ;
1463
+ this . _handle = handle ;
1464
+ initSocketHandle ( this ) ;
1465
+
1466
+ if ( hasObserver ( 'net' ) ) {
1467
+ startPerf (
1468
+ this ,
1469
+ kPerfHooksNetConnectContext ,
1470
+ { type : 'net' , name : 'connect' , detail : { host : req . address , port : req . port } }
1471
+ ) ;
1472
+ }
1473
+
1474
+ afterConnect ( status , handle , req , readable , writable ) ;
1475
+ }
1476
+
1297
1477
function addAbortSignalOption ( self , options ) {
1298
1478
if ( options ?. signal === undefined ) {
1299
1479
return ;
0 commit comments