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:
@ -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
|
||||
|
Reference in New Issue
Block a user