network: fix premature connection closures with node 20 and above

the happy eyeballs implementation in node is buggy. ipv4 and ipv6 connections
are made in parallel and whichever responds first is chosen. when there is no
ipv6 (immediately errors with ENETUNREACH/EHOSTUNREACH) and when ipv4 is > 250ms,
the code erroneously times out.

see also https://github.com/nodejs/node/issues/54359

reproduction for those servers:

const options = {
  hostname: 'www.cloudron.io', port: 80, path: '/', method: 'HEAD',
  // family: 4, // uncomment to make it work
};

const req = require('http').request(options, (res) => {
  console.log('statusCode:', res.statusCode);
  res.on('data', () => {}); // drain
});

req.on('socket', (socket) => console.log('Socket assigned to request', socket););
req.on('error', (e) => console.error(e));
req.end();
This commit is contained in:
Girish Ramakrishnan
2024-10-31 09:38:40 +01:00
parent 553c256d31
commit aefa481c43
2 changed files with 15 additions and 0 deletions
+7
View File
@@ -13,6 +13,7 @@ const apptask = require('./apptask.js'),
externalLdap = require('./externalldap.js'),
fs = require('fs'),
mailServer = require('./mailserver.js'),
net = require('net'),
reverseProxy = require('./reverseproxy.js'),
safe = require('safetydance'),
system = require('./system.js'),
@@ -57,6 +58,11 @@ async function setupLogging() {
process.stdout.logFile = logFile; // used by update task
}
// happy eyeballs workaround. see box.js for detailed note
async function setupNetworking() {
net.setDefaultAutoSelectFamilyAttemptTimeout(5000);
}
// this is also used as the 'uncaughtException' handler which can only have synchronous functions
function exitSync(status) {
if (status.error) fs.write(logFd, status.error.stack + '\n', function () {});
@@ -70,6 +76,7 @@ const startTime = new Date();
async.series([
setupLogging,
setupNetworking,
database.initialize,
], async function (initError) {
if (initError) {