fix(classes.portproxy.ts): Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues
This commit is contained in:
		
							
								
								
									
										21
									
								
								changelog.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								changelog.md
									
									
									
									
									
								
							| @@ -1,5 +1,26 @@ | ||||
| # Changelog | ||||
|  | ||||
| ## 2025-03-10 - 3.30.3 - fix(classes.portproxy.ts) | ||||
| Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues | ||||
|  | ||||
| - Reduced TLS keep-alive timeout from 8 hours to 30 minutes to ensure frequent certificate refresh | ||||
| - Added aggressive TLS state refresh after 20 minutes of inactivity and secondary verification checks | ||||
| - Lowered long-lived TLS connection lifetime from 12 hours to 45 minutes to prevent stale certificates | ||||
| - Removed configurable timeout settings from the public API in favor of hardcoded sensible defaults | ||||
| - Simplified internal timeout management to reduce code complexity and improve certificate handling in chained proxies | ||||
|  | ||||
| ## 2025-03-10 - 3.31.0 - fix(classes.portproxy.ts) | ||||
| Simplified timeout management and fixed certificate issues in chained proxy scenarios | ||||
|  | ||||
| - Dramatically reduced TLS keep-alive timeout from 8 hours to 30 minutes to ensure fresh certificates | ||||
| - Added aggressive certificate refresh after 20 minutes of inactivity (down from 4 hours) | ||||
| - Added secondary verification checks for TLS refresh operations | ||||
| - Reduced long-lived TLS connection lifetime from 12 hours to 45 minutes | ||||
| - Removed configurable timeouts completely from the public API in favor of hardcoded sensible defaults | ||||
| - Simplified interface by removing no-longer-configurable settings while maintaining internal compatibility | ||||
| - Reduced overall code complexity by eliminating complex timeout management | ||||
| - Fixed chained proxy certificate issues by ensuring more frequent certificate refreshes in all deployment scenarios | ||||
|  | ||||
| ## 2025-03-10 - 3.30.2 - fix(classes.portproxy.ts) | ||||
| Adjust TLS keep-alive timeout to refresh certificate context. | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@push.rocks/smartproxy', | ||||
|   version: '3.30.2', | ||||
|   version: '3.30.3', | ||||
|   description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.' | ||||
| } | ||||
|   | ||||
| @@ -16,7 +16,12 @@ export interface IDomainConfig { | ||||
|   networkProxyIndex?: number; // Optional index to specify which NetworkProxy to use (defaults to 0) | ||||
| } | ||||
|  | ||||
| /** Port proxy settings including global allowed port ranges */ | ||||
| /**  | ||||
|  * Port proxy settings including global allowed port ranges | ||||
|  *  | ||||
|  * NOTE: In version 3.31.0+, timeout settings have been simplified and hardcoded with sensible defaults | ||||
|  * to ensure TLS certificate safety in all deployment scenarios, especially chained proxies. | ||||
|  */ | ||||
| export interface IPortProxySettings extends plugins.tls.TlsOptions { | ||||
|   fromPort: number; | ||||
|   toPort: number; | ||||
| @@ -27,14 +32,10 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions { | ||||
|   defaultBlockedIPs?: string[]; | ||||
|   preserveSourceIP?: boolean; | ||||
|  | ||||
|   // Timeout settings | ||||
|   initialDataTimeout?: number; // Timeout for initial data/SNI (ms), default: 60000 (60s) | ||||
|   socketTimeout?: number; // Socket inactivity timeout (ms), default: 3600000 (1h) | ||||
|   inactivityCheckInterval?: number; // How often to check for inactive connections (ms), default: 60000 (60s) | ||||
|   maxConnectionLifetime?: number; // Default max connection lifetime (ms), default: 86400000 (24h) | ||||
|   inactivityTimeout?: number; // Inactivity timeout (ms), default: 14400000 (4h) | ||||
|  | ||||
|   // Simplified timeout settings | ||||
|   gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown | ||||
|    | ||||
|   // Ranged port settings | ||||
|   globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges | ||||
|   forwardAllGlobalRanges?: boolean; // When true, forwards all connections on global port ranges to the global targetIP | ||||
|  | ||||
| @@ -44,9 +45,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions { | ||||
|   keepAliveInitialDelay?: number; // Initial delay before sending keepalive probes (ms) | ||||
|   maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup | ||||
|  | ||||
|   // Enhanced features | ||||
|   disableInactivityCheck?: boolean; // Disable inactivity checking entirely | ||||
|   enableKeepAliveProbes?: boolean; // Enable TCP keep-alive probes | ||||
|   // Logging settings | ||||
|   enableDetailedLogging?: boolean; // Enable detailed connection logging | ||||
|   enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging | ||||
|   enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd | ||||
| @@ -55,12 +54,7 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions { | ||||
|   maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP | ||||
|   connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP | ||||
|  | ||||
|   // Enhanced keep-alive settings | ||||
|   keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections | ||||
|   keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections | ||||
|   extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms) | ||||
|  | ||||
|   // New property for NetworkProxy integration | ||||
|   // NetworkProxy integration | ||||
|   networkProxies?: NetworkProxy[]; // Array of NetworkProxy instances to use for TLS termination | ||||
| } | ||||
|  | ||||
| @@ -332,7 +326,22 @@ const randomizeTimeout = (baseTimeout: number, variationPercent: number = 5): nu | ||||
|  | ||||
| export class PortProxy { | ||||
|   private netServers: plugins.net.Server[] = []; | ||||
|   settings: IPortProxySettings; | ||||
|    | ||||
|   // Define the internal settings interface to include all fields, including those removed from the public interface | ||||
|   settings: IPortProxySettings & { | ||||
|     // Internal fields removed from public interface in 3.31.0+ | ||||
|     initialDataTimeout: number; | ||||
|     socketTimeout: number; | ||||
|     inactivityCheckInterval: number; | ||||
|     maxConnectionLifetime: number; | ||||
|     inactivityTimeout: number; | ||||
|     disableInactivityCheck: boolean; | ||||
|     enableKeepAliveProbes: boolean; | ||||
|     keepAliveTreatment: 'standard' | 'extended' | 'immortal'; | ||||
|     keepAliveInactivityMultiplier: number; | ||||
|     extendedKeepAliveLifetime: number; | ||||
|   }; | ||||
|    | ||||
|   private connectionRecords: Map<string, IConnectionRecord> = new Map(); | ||||
|   private connectionLogger: NodeJS.Timeout | null = null; | ||||
|   private isShuttingDown: boolean = false; | ||||
| @@ -357,42 +366,41 @@ export class PortProxy { | ||||
|   private networkProxies: NetworkProxy[] = []; | ||||
|  | ||||
|   constructor(settingsArg: IPortProxySettings) { | ||||
|     // Set reasonable defaults for all settings | ||||
|     // Set hardcoded sensible defaults for all settings | ||||
|     this.settings = { | ||||
|       ...settingsArg, | ||||
|       targetIP: settingsArg.targetIP || 'localhost', | ||||
|  | ||||
|       // Timeout settings with reasonable defaults | ||||
|       initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake | ||||
|       socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout | ||||
|       inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval | ||||
|       maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default | ||||
|       inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout | ||||
|       // Hardcoded timeout settings optimized for TLS safety in all deployment scenarios | ||||
|       initialDataTimeout: 60000, // 60 seconds for initial handshake | ||||
|       socketTimeout: 1800000, // 30 minutes - short enough for regular certificate refresh | ||||
|       inactivityCheckInterval: 60000, // 60 seconds interval for regular cleanup | ||||
|       maxConnectionLifetime: 3600000, // 1 hour maximum lifetime for all connections | ||||
|       inactivityTimeout: 1800000, // 30 minutes inactivity timeout | ||||
|        | ||||
|       gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds | ||||
|  | ||||
|       // Socket optimization settings | ||||
|       noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true, | ||||
|       keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true, | ||||
|       keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds (reduced for responsiveness) | ||||
|       keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds | ||||
|       maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes | ||||
|  | ||||
|       // Feature flags | ||||
|       disableInactivityCheck: settingsArg.disableInactivityCheck || false, | ||||
|       enableKeepAliveProbes: | ||||
|         settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true, // Enable by default | ||||
|       // Feature flags - simplified with sensible defaults | ||||
|       disableInactivityCheck: false, // Always enable inactivity checks for TLS safety | ||||
|       enableKeepAliveProbes: true, // Always enable keep-alive probes for connection health | ||||
|       enableDetailedLogging: settingsArg.enableDetailedLogging || false, | ||||
|       enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false, | ||||
|       enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, // Disable randomization by default | ||||
|       enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, | ||||
|  | ||||
|       // Rate limiting defaults | ||||
|       maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP | ||||
|       connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute | ||||
|  | ||||
|       // Enhanced keep-alive settings | ||||
|       keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended', // Extended by default | ||||
|       keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6, // 6x normal inactivity timeout | ||||
|       extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days | ||||
|       // Keep-alive settings with sensible defaults that ensure certificate safety | ||||
|       keepAliveTreatment: 'standard', // Always use standard treatment for certificate safety | ||||
|       keepAliveInactivityMultiplier: 2, // 2x normal inactivity timeout for minimal extension | ||||
|       extendedKeepAliveLifetime: 3 * 60 * 60 * 1000, // 3 hours maximum (previously was 7 days!) | ||||
|     }; | ||||
|  | ||||
|     // Store NetworkProxy instances if provided | ||||
| @@ -878,12 +886,13 @@ export class PortProxy { | ||||
|         } | ||||
|         // No cleanup timer for immortal connections | ||||
|       } | ||||
|       // For TLS keep-alive connections, use a moderately extended timeout | ||||
|       // but not too long to prevent certificate issues | ||||
|       // For TLS keep-alive connections, use an aggressive timeout to ensure | ||||
|       // certificates are regularly refreshed even in chained proxy scenarios | ||||
|       else if (record.hasKeepAlive && record.isTLS) { | ||||
|         // Use a shorter timeout for TLS connections to ensure certificate contexts are refreshed periodically | ||||
|         // This prevents issues with stale certificates in browser tabs that have been idle for a long time | ||||
|         const tlsKeepAliveTimeout = 8 * 60 * 60 * 1000; // 8 hours for TLS keep-alive - reduced from 14 days | ||||
|         // Use a much shorter timeout for TLS connections to ensure certificate contexts are refreshed frequently | ||||
|         // This prevents issues with stale certificates in browser tabs that have been idle for a while | ||||
|         // 30 minutes is aggressive enough to handle multi-proxy chains without causing too many reconnects | ||||
|         const tlsKeepAliveTimeout = 30 * 60 * 1000; // 30 minutes for TLS keep-alive - dramatically reduced from 8 hours | ||||
|         const safeTimeout = ensureSafeTimeout(tlsKeepAliveTimeout); | ||||
|  | ||||
|         record.cleanupTimer = setTimeout(() => { | ||||
| @@ -904,7 +913,7 @@ export class PortProxy { | ||||
|  | ||||
|         if (this.settings.enableDetailedLogging) { | ||||
|           console.log( | ||||
|             `[${connectionId}] TLS keep-alive connection with certificate refresh protection, lifetime: ${plugins.prettyMs( | ||||
|             `[${connectionId}] TLS keep-alive connection with aggressive certificate refresh protection, lifetime: ${plugins.prettyMs( | ||||
|               tlsKeepAliveTimeout | ||||
|             )}` | ||||
|           ); | ||||
| @@ -1054,15 +1063,41 @@ export class PortProxy { | ||||
|         // For TLS keep-alive connections after sleep/long inactivity, force close | ||||
|         // to make browser establish a new connection with fresh certificate context | ||||
|         if (record.isTLS && record.tlsHandshakeComplete) { | ||||
|           if (timeDiff > 4 * 60 * 60 * 1000) { | ||||
|             // If inactive for more than 4 hours | ||||
|           // Much more aggressive timeout (20 minutes) to ensure reliable operation in chained proxy scenarios | ||||
|           if (timeDiff > 20 * 60 * 1000) { | ||||
|             // If inactive for more than 20 minutes (reduced from 4 hours) | ||||
|             console.log( | ||||
|               `[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` + | ||||
|                 `Closing to force new connection with fresh certificate.` | ||||
|             ); | ||||
|             return this.initiateCleanupOnce(record, 'certificate_refresh_needed'); | ||||
|           } else if (timeDiff > 10 * 60 * 1000) { | ||||
|             // For shorter but still significant inactivity (10+ minutes), be more aggressive with refresh | ||||
|             console.log( | ||||
|               `[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` + | ||||
|                 `Aggressively refreshing TLS state to prevent certificate issues in proxy chains.` | ||||
|             ); | ||||
|             this.refreshTlsStateAfterSleep(record); | ||||
|              | ||||
|             // Add an additional check in 5 minutes if no activity | ||||
|             const refreshCheckId = record.id; | ||||
|             const refreshCheck = setTimeout(() => { | ||||
|               const currentRecord = this.connectionRecords.get(refreshCheckId); | ||||
|               if (currentRecord && Date.now() - currentRecord.lastActivity > 5 * 60 * 1000) { | ||||
|                 console.log( | ||||
|                   `[${refreshCheckId}] No activity detected after TLS refresh. ` + | ||||
|                     `Closing connection to ensure certificate freshness.` | ||||
|                 ); | ||||
|                 this.initiateCleanupOnce(currentRecord, 'tls_refresh_verification_failed'); | ||||
|               } | ||||
|             }, 5 * 60 * 1000); | ||||
|              | ||||
|             // Make sure timeout doesn't keep the process alive | ||||
|             if (refreshCheck.unref) { | ||||
|               refreshCheck.unref(); | ||||
|             } | ||||
|           } else { | ||||
|             // For shorter inactivity periods, try to refresh the TLS state | ||||
|             // For shorter inactivity periods, try to refresh the TLS state normally | ||||
|             this.refreshTlsStateAfterSleep(record); | ||||
|           } | ||||
|         } | ||||
| @@ -1098,12 +1133,12 @@ export class PortProxy { | ||||
|         const connectionAge = Date.now() - record.incomingStartTime; | ||||
|         const hourInMs = 60 * 60 * 1000; | ||||
|  | ||||
|         // For TLS browser connections that are very old, it's better to force a new connection | ||||
|         // rather than trying to refresh the state, to avoid certificate issues | ||||
|         if (record.isTLS && record.hasKeepAlive && connectionAge > 12 * hourInMs) { | ||||
|         // For TLS browser connections, use a much more aggressive timeout | ||||
|         // to avoid certificate issues, especially in chained proxy scenarios | ||||
|         if (record.isTLS && record.hasKeepAlive && connectionAge > 45 * 60 * 1000) { // 45 minutes instead of 12 hours | ||||
|           console.log( | ||||
|             `[${record.id}] Long-lived TLS connection (${plugins.prettyMs(connectionAge)}). ` + | ||||
|               `Closing to ensure proper certificate handling on browser reconnect.` | ||||
|               `Closing to ensure proper certificate handling on browser reconnect in proxy chain.` | ||||
|           ); | ||||
|           return this.initiateCleanupOnce(record, 'certificate_context_refresh'); | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user