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:
Philipp Kunz 2025-03-11 04:39:17 +00:00
parent 58ba0d9362
commit 865d21b36a
3 changed files with 294 additions and 56 deletions

View File

@ -1,5 +1,15 @@
# Changelog
## 2025-03-11 - 3.32.1 - 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.
- Increased TLS session cache maximum entries from 10,000 to 20,000, expiry time from 24 hours to 7 days, and cleanup interval from 10 minutes to 30 minutes
- Relaxed socket timeouts: standalone connections now use up to 6 hours, with chained proxies adjusted for 56 hours based on proxy position
- Updated inactivity, connection, and initial handshake timeouts to provide a more relaxed behavior under high-traffic chained proxy scenarios
- Increased keepAliveInitialDelay from 10 seconds to 30 seconds and introduced separate incoming and outgoing keep-alive flags
- Enhanced TLS renegotiation handling with more detailed logging and temporary processing flags to avoid duplicate processing
- Updated NetworkProxy integration to use optimized connection settings and more aggressive application-level keep-alive probes
## 2025-03-11 - 3.32.0 - feat(PortProxy)
Enhance TLS session cache, SNI extraction, and chained proxy support in PortProxy. Improve handling of multiple and fragmented TLS records, and add new configuration options (isChainedProxy, chainPosition, aggressiveTlsRefresh, tlsSessionCache) for robust TLS certificate refresh.

View File

@ -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.'
}

View File

@ -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) {