fix(portproxy): Relax TLS handshake and connection timeout settings for improved stability in chained proxy scenarios; update TLS session cache defaults and add keep-alive flags to connection records.
This commit is contained in:
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '3.32.0',
|
||||
version: '3.32.1',
|
||||
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.'
|
||||
}
|
||||
|
@ -100,6 +100,8 @@ interface IConnectionRecord {
|
||||
|
||||
// Keep-alive tracking
|
||||
hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection
|
||||
incomingKeepAliveEnabled?: boolean; // Whether keep-alive is enabled on incoming socket
|
||||
outgoingKeepAliveEnabled?: boolean; // Whether keep-alive is enabled on outgoing socket
|
||||
inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued
|
||||
incomingTerminationReason?: string | null; // Reason for incoming termination
|
||||
outgoingTerminationReason?: string | null; // Reason for outgoing termination
|
||||
@ -135,11 +137,11 @@ interface ITlsSessionCacheConfig {
|
||||
enabled: boolean; // Whether session caching is enabled
|
||||
}
|
||||
|
||||
// Default configuration for session cache
|
||||
// Default configuration for session cache with relaxed timeouts
|
||||
const DEFAULT_SESSION_CACHE_CONFIG: ITlsSessionCacheConfig = {
|
||||
maxEntries: 10000, // Default max 10,000 entries
|
||||
expiryTime: 24 * 60 * 60 * 1000, // 24 hours default
|
||||
cleanupInterval: 10 * 60 * 1000, // Clean up every 10 minutes
|
||||
maxEntries: 20000, // Default max 20,000 entries (doubled)
|
||||
expiryTime: 7 * 24 * 60 * 60 * 1000, // 7 days default (increased from 24 hours)
|
||||
cleanupInterval: 30 * 60 * 1000, // Clean up every 30 minutes (relaxed from 10 minutes)
|
||||
enabled: true // Enabled by default
|
||||
};
|
||||
|
||||
@ -1025,32 +1027,33 @@ export class PortProxy {
|
||||
}
|
||||
|
||||
// Determine appropriate timeouts based on proxy chain position
|
||||
let socketTimeout = 1800000; // 30 minutes default
|
||||
// Much more relaxed socket timeouts
|
||||
let socketTimeout = 6 * 60 * 60 * 1000; // 6 hours default for standalone
|
||||
|
||||
if (isChainedProxy) {
|
||||
// Use shorter timeouts for chained proxies to prevent certificate issues
|
||||
// Still adjust based on chain position, but with more relaxed values
|
||||
const chainPosition = settingsArg.chainPosition || 'middle';
|
||||
|
||||
// Adjust timeouts based on position in chain
|
||||
// Adjust timeouts based on position in chain, but significantly relaxed
|
||||
switch (chainPosition) {
|
||||
case 'first':
|
||||
// First proxy can be a bit more lenient as it handles browser connections
|
||||
socketTimeout = 1500000; // 25 minutes
|
||||
// First proxy handling browser connections
|
||||
socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
|
||||
break;
|
||||
case 'middle':
|
||||
// Middle proxies need shorter timeouts
|
||||
socketTimeout = 1200000; // 20 minutes
|
||||
// Middle proxies
|
||||
socketTimeout = 5 * 60 * 60 * 1000; // 5 hours
|
||||
break;
|
||||
case 'last':
|
||||
// Last proxy directly connects to backend
|
||||
socketTimeout = 1800000; // 30 minutes
|
||||
// Last proxy connects to backend
|
||||
socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
|
||||
break;
|
||||
}
|
||||
|
||||
console.log(`Configured as ${chainPosition} proxy in chain. Using adjusted timeouts for optimal TLS handling.`);
|
||||
console.log(`Configured as ${chainPosition} proxy in chain. Using relaxed timeouts for better stability.`);
|
||||
}
|
||||
|
||||
// Set hardcoded sensible defaults for all settings with chain-aware adjustments
|
||||
// Set sensible defaults with significantly relaxed timeouts
|
||||
this.settings = {
|
||||
...settingsArg,
|
||||
targetIP: targetIP,
|
||||
@ -1060,39 +1063,39 @@ export class PortProxy {
|
||||
chainPosition: settingsArg.chainPosition || (isChainedProxy ? 'middle' : 'last'),
|
||||
aggressiveTlsRefresh: aggressiveTlsRefresh,
|
||||
|
||||
// Hardcoded timeout settings optimized for TLS safety in all deployment scenarios
|
||||
initialDataTimeout: 60000, // 60 seconds for initial handshake
|
||||
socketTimeout: socketTimeout, // Adjusted based on chain position
|
||||
inactivityCheckInterval: isChainedProxy ? 30000 : 60000, // More frequent checks for chains
|
||||
maxConnectionLifetime: isChainedProxy ? 2700000 : 3600000, // 45min or 1hr lifetime
|
||||
inactivityTimeout: isChainedProxy ? 1200000 : 1800000, // 20min or 30min inactivity timeout
|
||||
// Much more relaxed timeout settings
|
||||
initialDataTimeout: 120000, // 2 minutes for initial handshake (doubled)
|
||||
socketTimeout: socketTimeout, // 5-6 hours based on chain position
|
||||
inactivityCheckInterval: 5 * 60 * 1000, // 5 minutes between checks (relaxed)
|
||||
maxConnectionLifetime: 12 * 60 * 60 * 1000, // 12 hours lifetime
|
||||
inactivityTimeout: 4 * 60 * 60 * 1000, // 4 hours inactivity timeout
|
||||
|
||||
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
||||
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 60000, // 60 seconds
|
||||
|
||||
// Socket optimization settings
|
||||
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
||||
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
||||
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
|
||||
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
|
||||
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 30000, // 30 seconds (increased)
|
||||
maxPendingDataSize: settingsArg.maxPendingDataSize || 20 * 1024 * 1024, // 20MB to handle large TLS handshakes
|
||||
|
||||
// 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
|
||||
disableInactivityCheck: false, // Still enable inactivity checks
|
||||
enableKeepAliveProbes: true, // Still enable keep-alive probes
|
||||
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
||||
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
||||
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
||||
|
||||
// Rate limiting defaults
|
||||
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
|
||||
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
|
||||
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 200, // 200 connections per IP (doubled)
|
||||
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 500, // 500 per minute (increased)
|
||||
|
||||
// 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
|
||||
// Use shorter lifetime for chained proxies
|
||||
// Keep-alive settings with much more relaxed defaults
|
||||
keepAliveTreatment: 'extended', // Use extended keep-alive treatment
|
||||
keepAliveInactivityMultiplier: 3, // 3x normal inactivity timeout for longer extension
|
||||
// Much longer keep-alive lifetimes
|
||||
extendedKeepAliveLifetime: isChainedProxy
|
||||
? 2 * 60 * 60 * 1000 // 2 hours for chained proxies
|
||||
: 3 * 60 * 60 * 1000, // 3 hours for standalone proxies
|
||||
? 24 * 60 * 60 * 1000 // 24 hours for chained proxies
|
||||
: 48 * 60 * 60 * 1000, // 48 hours for standalone proxies
|
||||
};
|
||||
|
||||
// Store NetworkProxy instances if provided
|
||||
@ -1154,10 +1157,13 @@ export class PortProxy {
|
||||
);
|
||||
}
|
||||
|
||||
// Create a connection to the NetworkProxy
|
||||
// Create a connection to the NetworkProxy with optimized settings for reliability
|
||||
const proxySocket = plugins.net.connect({
|
||||
host: proxyHost,
|
||||
port: proxyPort,
|
||||
noDelay: true, // Disable Nagle's algorithm for NetworkProxy connections
|
||||
keepAlive: this.settings.keepAlive, // Use the same keepAlive setting as regular connections
|
||||
keepAliveInitialDelay: Math.max(this.settings.keepAliveInitialDelay - 5000, 5000) // Slightly faster
|
||||
});
|
||||
|
||||
// Store the outgoing socket in the record
|
||||
@ -1165,6 +1171,30 @@ export class PortProxy {
|
||||
record.outgoingStartTime = Date.now();
|
||||
record.usingNetworkProxy = true;
|
||||
record.networkProxyIndex = proxyIndex;
|
||||
|
||||
// Mark keep-alive as enabled on outgoing if requested
|
||||
if (this.settings.keepAlive) {
|
||||
record.outgoingKeepAliveEnabled = true;
|
||||
|
||||
// Apply enhanced TCP keep-alive options if enabled
|
||||
if (this.settings.enableKeepAliveProbes) {
|
||||
try {
|
||||
if ('setKeepAliveProbes' in proxySocket) {
|
||||
(proxySocket as any).setKeepAliveProbes(10);
|
||||
}
|
||||
if ('setKeepAliveInterval' in proxySocket) {
|
||||
(proxySocket as any).setKeepAliveInterval(800);
|
||||
}
|
||||
|
||||
console.log(`[${connectionId}] Enhanced TCP keep-alive configured for NetworkProxy connection`);
|
||||
} catch (err) {
|
||||
// Ignore errors - these are optional enhancements
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(`[${connectionId}] Enhanced keep-alive not supported for NetworkProxy: ${err}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up error handlers
|
||||
proxySocket.on('error', (err) => {
|
||||
@ -1213,6 +1243,41 @@ export class PortProxy {
|
||||
|
||||
// Update activity on data transfer from the proxy socket
|
||||
proxySocket.on('data', () => this.updateActivity(record));
|
||||
|
||||
// Special handling for application-level keep-alives on NetworkProxy connections
|
||||
if (this.settings.keepAlive && record.isTLS) {
|
||||
// Set up a timer to periodically send application-level keep-alives
|
||||
const keepAliveTimer = setInterval(() => {
|
||||
if (proxySocket && !proxySocket.destroyed && record && !record.connectionClosed) {
|
||||
try {
|
||||
// Send 0-byte packet as application-level keep-alive
|
||||
proxySocket.write(Buffer.alloc(0));
|
||||
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(`[${connectionId}] Sent application-level keep-alive to NetworkProxy connection`);
|
||||
}
|
||||
} catch (err) {
|
||||
// If we can't write, the connection is probably already dead
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(`[${connectionId}] Error sending application-level keep-alive to NetworkProxy: ${err}`);
|
||||
}
|
||||
|
||||
// Stop the timer if we hit an error
|
||||
clearInterval(keepAliveTimer);
|
||||
}
|
||||
} else {
|
||||
// Clean up timer if connection is gone
|
||||
clearInterval(keepAliveTimer);
|
||||
}
|
||||
}, 60000); // Send keep-alive every minute
|
||||
|
||||
// Make sure interval doesn't prevent process exit
|
||||
if (keepAliveTimer.unref) {
|
||||
keepAliveTimer.unref();
|
||||
}
|
||||
|
||||
console.log(`[${connectionId}] Application-level keep-alive configured for NetworkProxy connection`);
|
||||
}
|
||||
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(
|
||||
@ -1334,17 +1399,25 @@ export class PortProxy {
|
||||
|
||||
// Apply keep-alive settings to the outgoing connection as well
|
||||
if (this.settings.keepAlive) {
|
||||
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
||||
// Use a slightly shorter initial delay for outgoing to ensure it stays active
|
||||
const outgoingInitialDelay = Math.max(this.settings.keepAliveInitialDelay - 5000, 5000);
|
||||
targetSocket.setKeepAlive(true, outgoingInitialDelay);
|
||||
record.outgoingKeepAliveEnabled = true;
|
||||
|
||||
console.log(`[${connectionId}] Keep-alive enabled on outgoing connection with initial delay: ${outgoingInitialDelay}ms`);
|
||||
|
||||
// Apply enhanced TCP keep-alive options if enabled
|
||||
if (this.settings.enableKeepAliveProbes) {
|
||||
try {
|
||||
if ('setKeepAliveProbes' in targetSocket) {
|
||||
(targetSocket as any).setKeepAliveProbes(10);
|
||||
(targetSocket as any).setKeepAliveProbes(10); // Same probes as incoming
|
||||
}
|
||||
if ('setKeepAliveInterval' in targetSocket) {
|
||||
(targetSocket as any).setKeepAliveInterval(1000);
|
||||
// Use a shorter interval on outgoing for more reliable detection
|
||||
(targetSocket as any).setKeepAliveInterval(800); // Slightly faster than incoming
|
||||
}
|
||||
|
||||
console.log(`[${connectionId}] Enhanced TCP keep-alive probes configured on outgoing connection`);
|
||||
} catch (err) {
|
||||
// Ignore errors - these are optional enhancements
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
@ -1354,6 +1427,43 @@ export class PortProxy {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special handling for TLS keep-alive - we want to be more aggressive
|
||||
// with keeping the outgoing connection alive in TLS mode
|
||||
if (record.isTLS) {
|
||||
// Set a timer to periodically send empty data to keep connection alive
|
||||
// This is in addition to TCP keep-alive, works at application layer
|
||||
const keepAliveTimer = setInterval(() => {
|
||||
if (targetSocket && !targetSocket.destroyed && record && !record.connectionClosed) {
|
||||
try {
|
||||
// Send 0-byte packet as application-level keep-alive
|
||||
targetSocket.write(Buffer.alloc(0));
|
||||
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(`[${connectionId}] Sent application-level keep-alive to outgoing TLS connection`);
|
||||
}
|
||||
} catch (err) {
|
||||
// If we can't write, the connection is probably already dead
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
console.log(`[${connectionId}] Error sending application-level keep-alive: ${err}`);
|
||||
}
|
||||
|
||||
// Stop the timer if we hit an error
|
||||
clearInterval(keepAliveTimer);
|
||||
}
|
||||
} else {
|
||||
// Clean up timer if connection is gone
|
||||
clearInterval(keepAliveTimer);
|
||||
}
|
||||
}, 60000); // Send keep-alive every minute
|
||||
|
||||
// Make sure interval doesn't prevent process exit
|
||||
if (keepAliveTimer.unref) {
|
||||
keepAliveTimer.unref();
|
||||
}
|
||||
|
||||
console.log(`[${connectionId}] Application-level keep-alive configured for TLS outgoing connection`);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup specific error handler for connection phase with enhanced retries
|
||||
@ -1542,15 +1652,37 @@ export class PortProxy {
|
||||
|
||||
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
|
||||
if (serverName && record.isTLS) {
|
||||
// This listener handles TLS renegotiation detection
|
||||
// Create a flag to prevent double-processing of the same handshake packet
|
||||
let processingRenegotiation = false;
|
||||
|
||||
// This listener handles TLS renegotiation detection on the incoming socket
|
||||
socket.on('data', (renegChunk) => {
|
||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
||||
// Only check for content type 22 (handshake) and not already processing
|
||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22 && !processingRenegotiation) {
|
||||
processingRenegotiation = true;
|
||||
|
||||
// Always update activity timestamp for any handshake packet
|
||||
this.updateActivity(record);
|
||||
|
||||
try {
|
||||
// Enhanced logging for renegotiation
|
||||
console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
|
||||
|
||||
// Extract all TLS information including session resumption data
|
||||
const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
|
||||
|
||||
// Log details about the handshake packet
|
||||
if (this.settings.enableTlsDebugLogging) {
|
||||
console.log(`[${connectionId}] Handshake SNI extraction results:`, {
|
||||
isResumption: sniInfo?.isResumption || false,
|
||||
serverName: sniInfo?.serverName || 'none',
|
||||
resumedDomain: sniInfo?.resumedDomain || 'none',
|
||||
recordsExamined: sniInfo?.recordsExamined || 0,
|
||||
multipleRecords: sniInfo?.multipleRecords || false,
|
||||
partialExtract: sniInfo?.partialExtract || false
|
||||
});
|
||||
}
|
||||
|
||||
let newSNI = sniInfo?.serverName;
|
||||
|
||||
// Handle session resumption - if we recognize the session ID, we know what domain it belongs to
|
||||
@ -1559,10 +1691,17 @@ export class PortProxy {
|
||||
newSNI = sniInfo.resumedDomain;
|
||||
}
|
||||
|
||||
// IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through
|
||||
// IMPORTANT: If we can't extract an SNI from renegotiation, but we detected a TLS handshake,
|
||||
// we still need to make sure it's properly forwarded to maintain the TLS state
|
||||
if (newSNI === undefined) {
|
||||
console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
|
||||
return;
|
||||
console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
|
||||
|
||||
// Set a temporary timeout to reset the processing flag
|
||||
setTimeout(() => {
|
||||
processingRenegotiation = false;
|
||||
}, 500);
|
||||
|
||||
return; // Let the piping handle the forwarding
|
||||
}
|
||||
|
||||
// Check if the SNI has changed
|
||||
@ -1605,15 +1744,34 @@ export class PortProxy {
|
||||
} else {
|
||||
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
||||
this.initiateCleanupOnce(record, 'sni_mismatch');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
|
||||
} finally {
|
||||
// Reset the processing flag after a small delay to prevent double-processing
|
||||
// of packets that may be part of the same handshake
|
||||
setTimeout(() => {
|
||||
processingRenegotiation = false;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set up a listener on the outgoing socket to detect issues with renegotiation
|
||||
// This helps catch cases where the outgoing connection has closed but the incoming is still active
|
||||
targetSocket.on('error', (err) => {
|
||||
// If we get an error during what might be a renegotiation, log it specially
|
||||
if (processingRenegotiation) {
|
||||
console.log(`[${connectionId}] ERROR: Outgoing socket error during TLS renegotiation: ${err.message}`);
|
||||
// Force immediate cleanup to prevent hanging connections
|
||||
this.initiateCleanupOnce(record, 'renegotiation_error');
|
||||
}
|
||||
// The normal error handler will be called for other errors
|
||||
});
|
||||
}
|
||||
|
||||
// Now set up piping for future data and resume the socket
|
||||
@ -1651,15 +1809,37 @@ export class PortProxy {
|
||||
} else {
|
||||
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
|
||||
if (serverName && record.isTLS) {
|
||||
// This listener handles TLS renegotiation detection
|
||||
// Create a flag to prevent double-processing of the same handshake packet
|
||||
let processingRenegotiation = false;
|
||||
|
||||
// This listener handles TLS renegotiation detection on the incoming socket
|
||||
socket.on('data', (renegChunk) => {
|
||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22) {
|
||||
// Only check for content type 22 (handshake) and not already processing
|
||||
if (renegChunk.length > 0 && renegChunk.readUInt8(0) === 22 && !processingRenegotiation) {
|
||||
processingRenegotiation = true;
|
||||
|
||||
// Always update activity timestamp for any handshake packet
|
||||
this.updateActivity(record);
|
||||
|
||||
try {
|
||||
// Enhanced logging for renegotiation
|
||||
console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
|
||||
|
||||
// Extract all TLS information including session resumption data
|
||||
const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
|
||||
|
||||
// Log details about the handshake packet
|
||||
if (this.settings.enableTlsDebugLogging) {
|
||||
console.log(`[${connectionId}] Handshake SNI extraction results:`, {
|
||||
isResumption: sniInfo?.isResumption || false,
|
||||
serverName: sniInfo?.serverName || 'none',
|
||||
resumedDomain: sniInfo?.resumedDomain || 'none',
|
||||
recordsExamined: sniInfo?.recordsExamined || 0,
|
||||
multipleRecords: sniInfo?.multipleRecords || false,
|
||||
partialExtract: sniInfo?.partialExtract || false
|
||||
});
|
||||
}
|
||||
|
||||
let newSNI = sniInfo?.serverName;
|
||||
|
||||
// Handle session resumption - if we recognize the session ID, we know what domain it belongs to
|
||||
@ -1668,10 +1848,17 @@ export class PortProxy {
|
||||
newSNI = sniInfo.resumedDomain;
|
||||
}
|
||||
|
||||
// IMPORTANT: If we can't extract an SNI from renegotiation, we MUST allow it through
|
||||
// IMPORTANT: If we can't extract an SNI from renegotiation, but we detected a TLS handshake,
|
||||
// we still need to make sure it's properly forwarded to maintain the TLS state
|
||||
if (newSNI === undefined) {
|
||||
console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
|
||||
return;
|
||||
console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
|
||||
|
||||
// Set a temporary timeout to reset the processing flag
|
||||
setTimeout(() => {
|
||||
processingRenegotiation = false;
|
||||
}, 500);
|
||||
|
||||
return; // Let the piping handle the forwarding
|
||||
}
|
||||
|
||||
// Check if the SNI has changed
|
||||
@ -1745,15 +1932,45 @@ export class PortProxy {
|
||||
} else {
|
||||
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
||||
this.initiateCleanupOnce(record, 'sni_mismatch');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
|
||||
} finally {
|
||||
// Reset the processing flag after a small delay to prevent double-processing
|
||||
// of packets that may be part of the same handshake
|
||||
setTimeout(() => {
|
||||
processingRenegotiation = false;
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set up a listener on the outgoing socket to detect issues with renegotiation
|
||||
// This helps catch cases where the outgoing connection has closed but the incoming is still active
|
||||
targetSocket.on('error', (err) => {
|
||||
// If we get an error during what might be a renegotiation, log it specially
|
||||
if (processingRenegotiation) {
|
||||
console.log(`[${connectionId}] ERROR: Outgoing socket error during TLS renegotiation: ${err.message}`);
|
||||
// Force immediate cleanup to prevent hanging connections
|
||||
this.initiateCleanupOnce(record, 'renegotiation_error');
|
||||
}
|
||||
// The normal error handler will be called for other errors
|
||||
});
|
||||
|
||||
// Also monitor targetSocket for connection issues during client handshakes
|
||||
targetSocket.on('close', () => {
|
||||
// If the outgoing socket closes during renegotiation, it's a critical issue
|
||||
if (processingRenegotiation) {
|
||||
console.log(`[${connectionId}] CRITICAL: Outgoing socket closed during TLS renegotiation!`);
|
||||
console.log(`[${connectionId}] This likely explains cert mismatch errors in the browser.`);
|
||||
// Force immediate cleanup on the client side
|
||||
this.initiateCleanupOnce(record, 'target_closed_during_renegotiation');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Now set up piping
|
||||
@ -1974,15 +2191,16 @@ export class PortProxy {
|
||||
if (record.lastActivity > 0) {
|
||||
const timeDiff = now - record.lastActivity;
|
||||
|
||||
// Enhanced sleep detection with graduated thresholds
|
||||
// For chained proxies, we need to be more aggressive about refreshing connections
|
||||
const isChainedProxy = this.settings.targetIP === 'localhost' || this.settings.targetIP === '127.0.0.1';
|
||||
// Enhanced sleep detection with graduated thresholds - much more relaxed
|
||||
// Using chain detection from settings instead of recalculating
|
||||
const isChainedProxy = this.settings.isChainedProxy || false;
|
||||
const minuteInMs = 60 * 1000;
|
||||
const hourInMs = 60 * minuteInMs;
|
||||
|
||||
// Different thresholds based on connection type and configuration
|
||||
const shortInactivityThreshold = isChainedProxy ? 10 * minuteInMs : 15 * minuteInMs;
|
||||
const mediumInactivityThreshold = isChainedProxy ? 20 * minuteInMs : 30 * minuteInMs;
|
||||
const longInactivityThreshold = isChainedProxy ? 60 * minuteInMs : 120 * minuteInMs;
|
||||
// Significantly relaxed thresholds for better stability
|
||||
const shortInactivityThreshold = 30 * minuteInMs; // 30 minutes
|
||||
const mediumInactivityThreshold = 2 * hourInMs; // 2 hours
|
||||
const longInactivityThreshold = 8 * hourInMs; // 8 hours
|
||||
|
||||
// Short inactivity (10-15 mins) - Might be temporary network issue or short sleep
|
||||
if (timeDiff > shortInactivityThreshold) {
|
||||
@ -2467,12 +2685,20 @@ export class PortProxy {
|
||||
|
||||
// Initialize sleep detection fields
|
||||
possibleSystemSleep: false,
|
||||
|
||||
// Track keep-alive state for both sides of the connection
|
||||
incomingKeepAliveEnabled: false,
|
||||
outgoingKeepAliveEnabled: false,
|
||||
};
|
||||
|
||||
// Apply keep-alive settings if enabled
|
||||
if (this.settings.keepAlive) {
|
||||
// Configure incoming socket keep-alive
|
||||
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
||||
connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
|
||||
connectionRecord.incomingKeepAliveEnabled = true;
|
||||
|
||||
console.log(`[${connectionId}] Keep-alive enabled on incoming connection with initial delay: ${this.settings.keepAliveInitialDelay}ms`);
|
||||
|
||||
// Apply enhanced TCP keep-alive options if enabled
|
||||
if (this.settings.enableKeepAliveProbes) {
|
||||
@ -2484,6 +2710,8 @@ export class PortProxy {
|
||||
if ('setKeepAliveInterval' in socket) {
|
||||
(socket as any).setKeepAliveInterval(1000); // 1 second interval between probes
|
||||
}
|
||||
|
||||
console.log(`[${connectionId}] Enhanced TCP keep-alive probes configured on incoming connection`);
|
||||
} catch (err) {
|
||||
// Ignore errors - these are optional enhancements
|
||||
if (this.settings.enableDetailedLogging) {
|
||||
|
Reference in New Issue
Block a user