@@ -96,6 +96,7 @@ const {
9696 ERR_SOCKET_CLOSED ,
9797 ERR_MISSING_ARGS ,
9898 } ,
99+ aggregateErrors,
99100 errnoException,
100101 exceptionWithHostPort,
101102 genericNodeError,
@@ -1042,6 +1043,76 @@ function internalConnect(
10421043}
10431044
10441045
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+
10451116Socket . prototype . connect = function ( ...args ) {
10461117 let normalized ;
10471118 // If passed an array, it's treated as an array of arguments that have
@@ -1166,6 +1237,64 @@ function lookupAndConnect(self, options) {
11661237 debug ( 'connect: dns options' , dnsopts ) ;
11671238 self . _host = host ;
11681239 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+
11691298 defaultTriggerAsyncIdScope ( self [ async_id_symbol ] , function ( ) {
11701299 lookup ( host , dnsopts , function emitLookup ( err , ip , addressType ) {
11711300 self . emit ( 'lookup' , err , ip , addressType , host ) ;
@@ -1294,6 +1423,57 @@ function afterConnect(status, handle, req, readable, writable) {
12941423 }
12951424}
12961425
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+
12971477function addAbortSignalOption ( self , options ) {
12981478 if ( options ?. signal === undefined ) {
12991479 return ;
0 commit comments