Fix socket error handling to prevent server crashes on ECONNREFUSED

This commit addresses critical issues where unhandled socket connection errors (ECONNREFUSED) would crash the server and cause memory leaks with rising connection counts.

Changes:
- Add createSocketWithErrorHandler() utility that attaches error handlers immediately upon socket creation
- Update https-passthrough-handler to use safe socket creation and clean up client sockets on server connection failure
- Update https-terminate-to-http-handler to use safe socket creation
- Ensure proper connection cleanup when server connections fail
- Document the fix in readme.hints.md and create implementation plan in readme.plan.md

The fix prevents race conditions where sockets could emit errors before handlers were attached, and ensures failed connections are properly cleaned up to prevent memory leaks.
This commit is contained in:
2025-06-01 13:15:50 +00:00
parent 9fdc2d5069
commit d45485985a
6 changed files with 258 additions and 101 deletions

View File

@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
import { ForwardingHandler } from './base-handler.js';
import type { IForwardConfig } from '../config/forwarding-types.js';
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
import { createSocketCleanupHandler, setupSocketHandlers } from '../../core/utils/socket-utils.js';
import { createSocketCleanupHandler, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
/**
* Handler for HTTPS termination with HTTP backend
@ -141,19 +141,41 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
if (dataBuffer.includes(Buffer.from('\r\n\r\n')) && !connectionEstablished) {
const target = this.getTargetFromConfig();
// Create backend connection
backendSocket = plugins.net.connect(target.port, target.host, () => {
connectionEstablished = true;
// Send buffered data
if (dataBuffer.length > 0) {
backendSocket!.write(dataBuffer);
dataBuffer = Buffer.alloc(0);
// Create backend connection with immediate error handling
backendSocket = createSocketWithErrorHandler({
port: target.port,
host: target.host,
onError: (error) => {
this.emit(ForwardingHandlerEvents.ERROR, {
error: error.message,
code: (error as any).code || 'UNKNOWN',
remoteAddress,
target: `${target.host}:${target.port}`
});
// Clean up the TLS socket since we can't forward
if (!tlsSocket.destroyed) {
tlsSocket.destroy();
}
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
remoteAddress,
reason: `backend_connection_failed: ${error.message}`
});
},
onConnect: () => {
connectionEstablished = true;
// Send buffered data
if (dataBuffer.length > 0) {
backendSocket!.write(dataBuffer);
dataBuffer = Buffer.alloc(0);
}
// Set up bidirectional data flow
tlsSocket.pipe(backendSocket!);
backendSocket!.pipe(tlsSocket);
}
// Set up bidirectional data flow
tlsSocket.pipe(backendSocket!);
backendSocket!.pipe(tlsSocket);
});
// Update the cleanup handler with the backend socket