fix(PortProxy): Fix TLS renegotiation handling and adjust TLS keep-alive timeouts in PortProxy implementation
This commit is contained in:
parent
009e3c4f0e
commit
c415a6c361
@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# 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)
|
## 2025-03-10 - 3.30.3 - fix(classes.portproxy.ts)
|
||||||
Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues
|
Simplify timeout management in PortProxy and fix chained proxy certificate refresh issues
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Just update the activity timestamp to prevent timeouts
|
||||||
record.lastActivity = Date.now();
|
record.lastActivity = Date.now();
|
||||||
}
|
}
|
||||||
@ -848,11 +850,28 @@ export class PortProxy {
|
|||||||
|
|
||||||
// Add the renegotiation listener for SNI validation
|
// Add the renegotiation listener for SNI validation
|
||||||
if (serverName) {
|
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) => {
|
socket.on('data', (renegChunk: Buffer) => {
|
||||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
||||||
try {
|
try {
|
||||||
// Try to extract SNI from potential renegotiation
|
// Try to extract SNI from potential renegotiation
|
||||||
const newSNI = extractSNI(renegChunk, this.settings.enableTlsDebugLogging);
|
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) {
|
if (newSNI && newSNI !== record.lockedDomain) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Rehandshake detected with different SNI: ${newSNI} vs locked ${record.lockedDomain}. Terminating connection.`
|
`[${connectionId}] Rehandshake detected with different SNI: ${newSNI} vs locked ${record.lockedDomain}. Terminating connection.`
|
||||||
@ -864,6 +883,8 @@ export class PortProxy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Always allow the renegotiation to continue if we encounter an error
|
||||||
|
// This ensures Chrome can complete its TLS renegotiation
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Error processing potential renegotiation: ${err}. Allowing connection to continue.`
|
`[${connectionId}] Error processing potential renegotiation: ${err}. Allowing connection to continue.`
|
||||||
);
|
);
|
||||||
@ -886,13 +907,12 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
// No cleanup timer for immortal connections
|
// No cleanup timer for immortal connections
|
||||||
}
|
}
|
||||||
// For TLS keep-alive connections, use an aggressive timeout to ensure
|
// For TLS keep-alive connections, use a more generous timeout now that
|
||||||
// certificates are regularly refreshed even in chained proxy scenarios
|
// we've fixed the renegotiation handling issue that was causing certificate problems
|
||||||
else if (record.hasKeepAlive && record.isTLS) {
|
else if (record.hasKeepAlive && record.isTLS) {
|
||||||
// Use a much shorter timeout for TLS connections to ensure certificate contexts are refreshed frequently
|
// Use a longer timeout for TLS connections now that renegotiation handling is fixed
|
||||||
// This prevents issues with stale certificates in browser tabs that have been idle for a while
|
// This reduces unnecessary reconnections while still ensuring certificate freshness
|
||||||
// 30 minutes is aggressive enough to handle multi-proxy chains without causing too many reconnects
|
const tlsKeepAliveTimeout = 4 * 60 * 60 * 1000; // 4 hours for TLS keep-alive - increased from 30 minutes
|
||||||
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(() => {
|
||||||
@ -1063,34 +1083,34 @@ 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) {
|
||||||
// Much more aggressive timeout (20 minutes) to ensure reliable operation in chained proxy scenarios
|
// More generous timeout now that we've fixed the renegotiation handling
|
||||||
if (timeDiff > 20 * 60 * 1000) {
|
if (timeDiff > 2 * 60 * 60 * 1000) {
|
||||||
// If inactive for more than 20 minutes (reduced from 4 hours)
|
// If inactive for more than 2 hours (increased from 20 minutes)
|
||||||
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) {
|
} else if (timeDiff > 30 * 60 * 1000) {
|
||||||
// For shorter but still significant inactivity (10+ minutes), be more aggressive with refresh
|
// For shorter but still significant inactivity (30+ minutes), refresh TLS state
|
||||||
console.log(
|
console.log(
|
||||||
`[${record.id}] TLS connection inactive for ${plugins.prettyMs(timeDiff)}. ` +
|
`[${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);
|
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 refreshCheckId = record.id;
|
||||||
const refreshCheck = setTimeout(() => {
|
const refreshCheck = setTimeout(() => {
|
||||||
const currentRecord = this.connectionRecords.get(refreshCheckId);
|
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(
|
console.log(
|
||||||
`[${refreshCheckId}] No activity detected after TLS refresh. ` +
|
`[${refreshCheckId}] No activity detected after TLS refresh. ` +
|
||||||
`Closing connection to ensure certificate freshness.`
|
`Closing connection to ensure certificate freshness.`
|
||||||
);
|
);
|
||||||
this.initiateCleanupOnce(currentRecord, 'tls_refresh_verification_failed');
|
this.initiateCleanupOnce(currentRecord, 'tls_refresh_verification_failed');
|
||||||
}
|
}
|
||||||
}, 5 * 60 * 1000);
|
}, 15 * 60 * 1000);
|
||||||
|
|
||||||
// Make sure timeout doesn't keep the process alive
|
// Make sure timeout doesn't keep the process alive
|
||||||
if (refreshCheck.unref) {
|
if (refreshCheck.unref) {
|
||||||
@ -1133,9 +1153,9 @@ 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, use a much more aggressive timeout
|
// For TLS browser connections, use a more generous timeout now that
|
||||||
// to avoid certificate issues, especially in chained proxy scenarios
|
// we've fixed the renegotiation handling issues
|
||||||
if (record.isTLS && record.hasKeepAlive && connectionAge > 45 * 60 * 1000) { // 45 minutes instead of 12 hours
|
if (record.isTLS && record.hasKeepAlive && connectionAge > 8 * hourInMs) { // 8 hours instead of 45 minutes
|
||||||
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 in proxy chain.`
|
`Closing to ensure proper certificate handling on browser reconnect in proxy chain.`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user