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 | # 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) | ## 2025-03-10 - 3.30.2 - fix(classes.portproxy.ts) | ||||||
| Adjust TLS keep-alive timeout to refresh certificate context. | Adjust TLS keep-alive timeout to refresh certificate context. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@push.rocks/smartproxy', |   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.' |   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) |   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 { | export interface IPortProxySettings extends plugins.tls.TlsOptions { | ||||||
|   fromPort: number; |   fromPort: number; | ||||||
|   toPort: number; |   toPort: number; | ||||||
| @@ -27,14 +32,10 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions { | |||||||
|   defaultBlockedIPs?: string[]; |   defaultBlockedIPs?: string[]; | ||||||
|   preserveSourceIP?: boolean; |   preserveSourceIP?: boolean; | ||||||
|  |  | ||||||
|   // Timeout settings |   // Simplified 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) |  | ||||||
|  |  | ||||||
|   gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown |   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 |   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 |   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) |   keepAliveInitialDelay?: number; // Initial delay before sending keepalive probes (ms) | ||||||
|   maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup |   maxPendingDataSize?: number; // Maximum bytes to buffer during connection setup | ||||||
|  |  | ||||||
|   // Enhanced features |   // Logging settings | ||||||
|   disableInactivityCheck?: boolean; // Disable inactivity checking entirely |  | ||||||
|   enableKeepAliveProbes?: boolean; // Enable TCP keep-alive probes |  | ||||||
|   enableDetailedLogging?: boolean; // Enable detailed connection logging |   enableDetailedLogging?: boolean; // Enable detailed connection logging | ||||||
|   enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging |   enableTlsDebugLogging?: boolean; // Enable TLS handshake debug logging | ||||||
|   enableRandomizedTimeouts?: boolean; // Randomize timeouts slightly to prevent thundering herd |   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 |   maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP | ||||||
|   connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP |   connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP | ||||||
|  |  | ||||||
|   // Enhanced keep-alive settings |   // NetworkProxy integration | ||||||
|   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 |  | ||||||
|   networkProxies?: NetworkProxy[]; // Array of NetworkProxy instances to use for TLS termination |   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 { | export class PortProxy { | ||||||
|   private netServers: plugins.net.Server[] = []; |   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 connectionRecords: Map<string, IConnectionRecord> = new Map(); | ||||||
|   private connectionLogger: NodeJS.Timeout | null = null; |   private connectionLogger: NodeJS.Timeout | null = null; | ||||||
|   private isShuttingDown: boolean = false; |   private isShuttingDown: boolean = false; | ||||||
| @@ -357,42 +366,41 @@ export class PortProxy { | |||||||
|   private networkProxies: NetworkProxy[] = []; |   private networkProxies: NetworkProxy[] = []; | ||||||
|  |  | ||||||
|   constructor(settingsArg: IPortProxySettings) { |   constructor(settingsArg: IPortProxySettings) { | ||||||
|     // Set reasonable defaults for all settings |     // Set hardcoded sensible defaults for all settings | ||||||
|     this.settings = { |     this.settings = { | ||||||
|       ...settingsArg, |       ...settingsArg, | ||||||
|       targetIP: settingsArg.targetIP || 'localhost', |       targetIP: settingsArg.targetIP || 'localhost', | ||||||
|  |  | ||||||
|       // Timeout settings with reasonable defaults |       // Hardcoded timeout settings optimized for TLS safety in all deployment scenarios | ||||||
|       initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake |       initialDataTimeout: 60000, // 60 seconds for initial handshake | ||||||
|       socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout |       socketTimeout: 1800000, // 30 minutes - short enough for regular certificate refresh | ||||||
|       inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval |       inactivityCheckInterval: 60000, // 60 seconds interval for regular cleanup | ||||||
|       maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default |       maxConnectionLifetime: 3600000, // 1 hour maximum lifetime for all connections | ||||||
|       inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout |       inactivityTimeout: 1800000, // 30 minutes inactivity timeout | ||||||
|        |        | ||||||
|       gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds |       gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds | ||||||
|  |  | ||||||
|       // Socket optimization settings |       // Socket optimization settings | ||||||
|       noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true, |       noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true, | ||||||
|       keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : 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 |       maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes | ||||||
|  |  | ||||||
|       // Feature flags |       // Feature flags - simplified with sensible defaults | ||||||
|       disableInactivityCheck: settingsArg.disableInactivityCheck || false, |       disableInactivityCheck: false, // Always enable inactivity checks for TLS safety | ||||||
|       enableKeepAliveProbes: |       enableKeepAliveProbes: true, // Always enable keep-alive probes for connection health | ||||||
|         settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true, // Enable by default |  | ||||||
|       enableDetailedLogging: settingsArg.enableDetailedLogging || false, |       enableDetailedLogging: settingsArg.enableDetailedLogging || false, | ||||||
|       enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false, |       enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false, | ||||||
|       enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, // Disable randomization by default |       enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, | ||||||
|  |  | ||||||
|       // Rate limiting defaults |       // Rate limiting defaults | ||||||
|       maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP |       maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP | ||||||
|       connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute |       connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute | ||||||
|  |  | ||||||
|       // Enhanced keep-alive settings |       // Keep-alive settings with sensible defaults that ensure certificate safety | ||||||
|       keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended', // Extended by default |       keepAliveTreatment: 'standard', // Always use standard treatment for certificate safety | ||||||
|       keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6, // 6x normal inactivity timeout |       keepAliveInactivityMultiplier: 2, // 2x normal inactivity timeout for minimal extension | ||||||
|       extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days |       extendedKeepAliveLifetime: 3 * 60 * 60 * 1000, // 3 hours maximum (previously was 7 days!) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // Store NetworkProxy instances if provided |     // Store NetworkProxy instances if provided | ||||||
| @@ -878,12 +886,13 @@ export class PortProxy { | |||||||
|         } |         } | ||||||
|         // No cleanup timer for immortal connections |         // No cleanup timer for immortal connections | ||||||
|       } |       } | ||||||
|       // For TLS keep-alive connections, use a moderately extended timeout |       // For TLS keep-alive connections, use an aggressive timeout to ensure | ||||||
|       // but not too long to prevent certificate issues |       // certificates are regularly refreshed even in chained proxy scenarios | ||||||
|       else if (record.hasKeepAlive && record.isTLS) { |       else if (record.hasKeepAlive && record.isTLS) { | ||||||
|         // Use a shorter timeout for TLS connections to ensure certificate contexts are refreshed periodically |         // 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 long time |         // This prevents issues with stale certificates in browser tabs that have been idle for a while | ||||||
|         const tlsKeepAliveTimeout = 8 * 60 * 60 * 1000; // 8 hours for TLS keep-alive - reduced from 14 days |         // 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); |         const safeTimeout = ensureSafeTimeout(tlsKeepAliveTimeout); | ||||||
|  |  | ||||||
|         record.cleanupTimer = setTimeout(() => { |         record.cleanupTimer = setTimeout(() => { | ||||||
| @@ -904,7 +913,7 @@ export class PortProxy { | |||||||
|  |  | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           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 |               tlsKeepAliveTimeout | ||||||
|             )}` |             )}` | ||||||
|           ); |           ); | ||||||
| @@ -1054,15 +1063,41 @@ export class PortProxy { | |||||||
|         // For TLS keep-alive connections after sleep/long inactivity, force close |         // For TLS keep-alive connections after sleep/long inactivity, force close | ||||||
|         // to make browser establish a new connection with fresh certificate context |         // to make browser establish a new connection with fresh certificate context | ||||||
|         if (record.isTLS && record.tlsHandshakeComplete) { |         if (record.isTLS && record.tlsHandshakeComplete) { | ||||||
|           if (timeDiff > 4 * 60 * 60 * 1000) { |           // Much more aggressive timeout (20 minutes) to ensure reliable operation in chained proxy scenarios | ||||||
|             // If inactive for more than 4 hours |           if (timeDiff > 20 * 60 * 1000) { | ||||||
|  |             // If inactive for more than 20 minutes (reduced from 4 hours) | ||||||
|             console.log( |             console.log( | ||||||
|               `[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` + |               `[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` + | ||||||
|                 `Closing to force new connection with fresh certificate.` |                 `Closing to force new connection with fresh certificate.` | ||||||
|             ); |             ); | ||||||
|             return this.initiateCleanupOnce(record, 'certificate_refresh_needed'); |             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 { |           } 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); |             this.refreshTlsStateAfterSleep(record); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
| @@ -1098,12 +1133,12 @@ export class PortProxy { | |||||||
|         const connectionAge = Date.now() - record.incomingStartTime; |         const connectionAge = Date.now() - record.incomingStartTime; | ||||||
|         const hourInMs = 60 * 60 * 1000; |         const hourInMs = 60 * 60 * 1000; | ||||||
|  |  | ||||||
|         // For TLS browser connections that are very old, it's better to force a new connection |         // For TLS browser connections, use a much more aggressive timeout | ||||||
|         // rather than trying to refresh the state, to avoid certificate issues |         // to avoid certificate issues, especially in chained proxy scenarios | ||||||
|         if (record.isTLS && record.hasKeepAlive && connectionAge > 12 * hourInMs) { |         if (record.isTLS && record.hasKeepAlive && connectionAge > 45 * 60 * 1000) { // 45 minutes instead of 12 hours | ||||||
|           console.log( |           console.log( | ||||||
|             `[${record.id}] Long-lived TLS connection (${plugins.prettyMs(connectionAge)}). ` + |             `[${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'); |           return this.initiateCleanupOnce(record, 'certificate_context_refresh'); | ||||||
|         } |         } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user