From c415a6c3615f933da984658cc13cac0f6897910c Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Mon, 10 Mar 2025 22:35:34 +0000 Subject: [PATCH] fix(PortProxy): Fix TLS renegotiation handling and adjust TLS keep-alive timeouts in PortProxy implementation --- changelog.md | 8 ++++++ ts/00_commitinfo_data.ts | 2 +- ts/classes.portproxy.ts | 58 +++++++++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 20 deletions(-) diff --git a/changelog.md b/changelog.md index f6e6ccd..5c8a094 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,13 @@ # Changelog +## 2025-03-10 - 3.30.4 - fix(PortProxy) +Fix TLS renegotiation handling and adjust TLS keep-alive timeouts in PortProxy implementation + +- Allow TLS renegotiation data without an explicit SNI extraction to pass through, ensuring valid renegotiations are not dropped (critical for Chrome). +- Update TLS keep-alive timeout from an aggressive 30 minutes to a more generous 4 hours to reduce unnecessary reconnections. +- Increase inactivity thresholds for TLS connections from 20 minutes to 2 hours with an additional verification interval extended from 5 to 15 minutes. +- Adjust long-lived TLS connection timeout from 45 minutes to 8 hours for improved certificate context refresh in chained proxy scenarios. + ## 2025-03-10 - 3.30.3 - fix(classes.portproxy.ts) Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 88e88d5..8c21e42 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '3.30.3', + version: '3.30.4', 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.' } diff --git a/ts/classes.portproxy.ts b/ts/classes.portproxy.ts index 26ac76f..55b9f4e 100644 --- a/ts/classes.portproxy.ts +++ b/ts/classes.portproxy.ts @@ -515,7 +515,9 @@ export class PortProxy { ); } - // Let the NetworkProxy handle the TLS renegotiation + // NOTE: We don't need to explicitly forward the renegotiation packets + // because socket.pipe(proxySocket) is already handling that. + // The pipe ensures all data (including renegotiation) flows through properly. // Just update the activity timestamp to prevent timeouts record.lastActivity = Date.now(); } @@ -848,11 +850,28 @@ export class PortProxy { // Add the renegotiation listener for SNI validation if (serverName) { + // This listener will check for TLS renegotiation attempts + // Note: We don't need to explicitly forward the renegotiation packets + // since socket.pipe(targetSocket) is already set up earlier and handles that socket.on('data', (renegChunk: Buffer) => { if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) { try { // Try to extract SNI from potential renegotiation const newSNI = extractSNI(renegChunk, this.settings.enableTlsDebugLogging); + + // IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through + // Otherwise valid renegotiations that don't explicitly repeat the SNI will break + if (newSNI === undefined) { + if (this.settings.enableDetailedLogging) { + console.log( + `[${connectionId}] Rehandshake detected without SNI, allowing it through.` + ); + } + // Let it pass through - this is critical for Chrome's TLS handling + return; + } + + // Only block if we positively identify a different SNI if (newSNI && newSNI !== record.lockedDomain) { console.log( `[${connectionId}] Rehandshake detected with different SNI: ${newSNI} vs locked ${record.lockedDomain}. Terminating connection.` @@ -864,6 +883,8 @@ export class PortProxy { ); } } catch (err) { + // Always allow the renegotiation to continue if we encounter an error + // This ensures Chrome can complete its TLS renegotiation console.log( `[${connectionId}] Error processing potential renegotiation: ${err}. Allowing connection to continue.` ); @@ -886,13 +907,12 @@ export class PortProxy { } // No cleanup timer for immortal connections } - // For TLS keep-alive connections, use an aggressive timeout to ensure - // certificates are regularly refreshed even in chained proxy scenarios + // For TLS keep-alive connections, use a more generous timeout now that + // we've fixed the renegotiation handling issue that was causing certificate problems else if (record.hasKeepAlive && record.isTLS) { - // 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 + // Use a longer timeout for TLS connections now that renegotiation handling is fixed + // This reduces unnecessary reconnections while still ensuring certificate freshness + const tlsKeepAliveTimeout = 4 * 60 * 60 * 1000; // 4 hours for TLS keep-alive - increased from 30 minutes const safeTimeout = ensureSafeTimeout(tlsKeepAliveTimeout); record.cleanupTimer = setTimeout(() => { @@ -1063,34 +1083,34 @@ 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) { - // 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) + // More generous timeout now that we've fixed the renegotiation handling + if (timeDiff > 2 * 60 * 60 * 1000) { + // If inactive for more than 2 hours (increased from 20 minutes) 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 + } else if (timeDiff > 30 * 60 * 1000) { + // For shorter but still significant inactivity (30+ minutes), refresh TLS state console.log( `[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` + - `Aggressively refreshing TLS state to prevent certificate issues in proxy chains.` + `Refreshing TLS state.` ); this.refreshTlsStateAfterSleep(record); - // Add an additional check in 5 minutes if no activity + // Add an additional check in 15 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) { + if (currentRecord && Date.now() - currentRecord.lastActivity > 15 * 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); + }, 15 * 60 * 1000); // Make sure timeout doesn't keep the process alive if (refreshCheck.unref) { @@ -1133,9 +1153,9 @@ export class PortProxy { const connectionAge = Date.now() - record.incomingStartTime; const hourInMs = 60 * 60 * 1000; - // 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 + // For TLS browser connections, use a more generous timeout now that + // we've fixed the renegotiation handling issues + if (record.isTLS && record.hasKeepAlive && connectionAge > 8 * hourInMs) { // 8 hours instead of 45 minutes console.log( `[${record.id}] Long-lived TLS connection (${plugins.prettyMs(connectionAge)}). ` + `Closing to ensure proper certificate handling on browser reconnect in proxy chain.`