From 300ab1a0778335fc8ad9c922f9347a642b8f6b60 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Sun, 1 Jun 2025 13:42:46 +0000 Subject: [PATCH] Fix connection leak in route-connection-handler by using safe socket creation The previous fix only addressed ForwardingHandler classes but missed the critical setupDirectConnection() method in route-connection-handler.ts where SmartProxy actually handles connections. This caused active connections to rise indefinitely on ECONNREFUSED errors. Changes: - Import createSocketWithErrorHandler in route-connection-handler.ts - Replace net.connect() with createSocketWithErrorHandler() in setupDirectConnection() - Properly clean up connection records when server connection fails - Add connectionFailed flag to prevent setup of failed connections This ensures connection records are cleaned up immediately when backend connections fail, preventing memory leaks. --- readme.hints.md | 9 +++- .../smart-proxy/route-connection-handler.ts | 54 ++++++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/readme.hints.md b/readme.hints.md index 09f85d7..29b74dd 100644 --- a/readme.hints.md +++ b/readme.hints.md @@ -457,4 +457,11 @@ const socket = createSocketWithErrorHandler({ - `test/test.forwarding-error-fix.node.ts` - Tests forwarding handlers handle errors gracefully ### Configuration -No configuration changes needed. The fix is transparent to users. \ No newline at end of file +No configuration changes needed. The fix is transparent to users. + +### Important Note +The fix was applied in two places: +1. **ForwardingHandler classes** (`https-passthrough-handler.ts`, etc.) - These are standalone forwarding utilities +2. **SmartProxy route-connection-handler** (`route-connection-handler.ts`) - This is where the actual SmartProxy connection handling happens + +The critical fix for SmartProxy was in `setupDirectConnection()` method in route-connection-handler.ts, which now uses `createSocketWithErrorHandler()` to properly handle connection failures and clean up connection records. \ No newline at end of file diff --git a/ts/proxies/smart-proxy/route-connection-handler.ts b/ts/proxies/smart-proxy/route-connection-handler.ts index 3e90342..282c79b 100644 --- a/ts/proxies/smart-proxy/route-connection-handler.ts +++ b/ts/proxies/smart-proxy/route-connection-handler.ts @@ -9,7 +9,7 @@ import { TlsManager } from './tls-manager.js'; import { HttpProxyBridge } from './http-proxy-bridge.js'; import { TimeoutManager } from './timeout-manager.js'; import { RouteManager } from './route-manager.js'; -import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers } from '../../core/utils/socket-utils.js'; +import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js'; /** * Handles new connection processing and setup logic with support for route-based configuration @@ -1073,13 +1073,52 @@ export class RouteConnectionHandler { record.pendingDataSize = initialChunk.length; } - // Create the target socket - const targetSocket = plugins.net.connect(connectionOptions); - record.outgoing = targetSocket; - record.outgoingStartTime = Date.now(); + // Create the target socket with immediate error handling + let targetSocket: plugins.net.Socket; + + // Flag to track if initial connection failed + let connectionFailed = false; + + targetSocket = createSocketWithErrorHandler({ + port: finalTargetPort, + host: finalTargetHost, + onError: (error) => { + // Mark connection as failed + connectionFailed = true; + + // Connection failed - clean up immediately + logger.log('error', + `Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`, + { + connectionId, + targetHost: finalTargetHost, + targetPort: finalTargetPort, + errorMessage: error.message, + errorCode: (error as any).code, + component: 'route-handler' + } + ); + + // Resume the incoming socket to prevent it from hanging + socket.resume(); + + // Clean up the incoming socket + if (!socket.destroyed) { + socket.destroy(); + } + + // Clean up the connection record + this.connectionManager.initiateCleanupOnce(record, `connection_failed_${(error as any).code || 'unknown'}`); + } + }); + + // Only proceed with setup if connection didn't fail immediately + if (!connectionFailed) { + record.outgoing = targetSocket; + record.outgoingStartTime = Date.now(); - // Apply socket optimizations - targetSocket.setNoDelay(this.settings.noDelay); + // Apply socket optimizations + targetSocket.setNoDelay(this.settings.noDelay); // Apply keep-alive settings if enabled if (this.settings.keepAlive) { @@ -1346,5 +1385,6 @@ export class RouteConnectionHandler { record.tlsHandshakeComplete = true; } }); + } // End of if (!connectionFailed) } } \ No newline at end of file