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:
parent
58ba0d9362
commit
865d21b36a
10
changelog.md
10
changelog.md
@ -1,5 +1,15 @@
|
|||||||
# Changelog
|
# 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 5–6 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)
|
## 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.
|
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.
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
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.'
|
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
|
// Keep-alive tracking
|
||||||
hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection
|
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
|
inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued
|
||||||
incomingTerminationReason?: string | null; // Reason for incoming termination
|
incomingTerminationReason?: string | null; // Reason for incoming termination
|
||||||
outgoingTerminationReason?: string | null; // Reason for outgoing termination
|
outgoingTerminationReason?: string | null; // Reason for outgoing termination
|
||||||
@ -135,11 +137,11 @@ interface ITlsSessionCacheConfig {
|
|||||||
enabled: boolean; // Whether session caching is enabled
|
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 = {
|
const DEFAULT_SESSION_CACHE_CONFIG: ITlsSessionCacheConfig = {
|
||||||
maxEntries: 10000, // Default max 10,000 entries
|
maxEntries: 20000, // Default max 20,000 entries (doubled)
|
||||||
expiryTime: 24 * 60 * 60 * 1000, // 24 hours default
|
expiryTime: 7 * 24 * 60 * 60 * 1000, // 7 days default (increased from 24 hours)
|
||||||
cleanupInterval: 10 * 60 * 1000, // Clean up every 10 minutes
|
cleanupInterval: 30 * 60 * 1000, // Clean up every 30 minutes (relaxed from 10 minutes)
|
||||||
enabled: true // Enabled by default
|
enabled: true // Enabled by default
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1025,32 +1027,33 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Determine appropriate timeouts based on proxy chain position
|
// 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) {
|
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';
|
const chainPosition = settingsArg.chainPosition || 'middle';
|
||||||
|
|
||||||
// Adjust timeouts based on position in chain
|
// Adjust timeouts based on position in chain, but significantly relaxed
|
||||||
switch (chainPosition) {
|
switch (chainPosition) {
|
||||||
case 'first':
|
case 'first':
|
||||||
// First proxy can be a bit more lenient as it handles browser connections
|
// First proxy handling browser connections
|
||||||
socketTimeout = 1500000; // 25 minutes
|
socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
|
||||||
break;
|
break;
|
||||||
case 'middle':
|
case 'middle':
|
||||||
// Middle proxies need shorter timeouts
|
// Middle proxies
|
||||||
socketTimeout = 1200000; // 20 minutes
|
socketTimeout = 5 * 60 * 60 * 1000; // 5 hours
|
||||||
break;
|
break;
|
||||||
case 'last':
|
case 'last':
|
||||||
// Last proxy directly connects to backend
|
// Last proxy connects to backend
|
||||||
socketTimeout = 1800000; // 30 minutes
|
socketTimeout = 6 * 60 * 60 * 1000; // 6 hours
|
||||||
break;
|
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 = {
|
this.settings = {
|
||||||
...settingsArg,
|
...settingsArg,
|
||||||
targetIP: targetIP,
|
targetIP: targetIP,
|
||||||
@ -1060,39 +1063,39 @@ export class PortProxy {
|
|||||||
chainPosition: settingsArg.chainPosition || (isChainedProxy ? 'middle' : 'last'),
|
chainPosition: settingsArg.chainPosition || (isChainedProxy ? 'middle' : 'last'),
|
||||||
aggressiveTlsRefresh: aggressiveTlsRefresh,
|
aggressiveTlsRefresh: aggressiveTlsRefresh,
|
||||||
|
|
||||||
// Hardcoded timeout settings optimized for TLS safety in all deployment scenarios
|
// Much more relaxed timeout settings
|
||||||
initialDataTimeout: 60000, // 60 seconds for initial handshake
|
initialDataTimeout: 120000, // 2 minutes for initial handshake (doubled)
|
||||||
socketTimeout: socketTimeout, // Adjusted based on chain position
|
socketTimeout: socketTimeout, // 5-6 hours based on chain position
|
||||||
inactivityCheckInterval: isChainedProxy ? 30000 : 60000, // More frequent checks for chains
|
inactivityCheckInterval: 5 * 60 * 1000, // 5 minutes between checks (relaxed)
|
||||||
maxConnectionLifetime: isChainedProxy ? 2700000 : 3600000, // 45min or 1hr lifetime
|
maxConnectionLifetime: 12 * 60 * 60 * 1000, // 12 hours lifetime
|
||||||
inactivityTimeout: isChainedProxy ? 1200000 : 1800000, // 20min or 30min inactivity timeout
|
inactivityTimeout: 4 * 60 * 60 * 1000, // 4 hours inactivity timeout
|
||||||
|
|
||||||
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 60000, // 60 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
|
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 30000, // 30 seconds (increased)
|
||||||
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
|
maxPendingDataSize: settingsArg.maxPendingDataSize || 20 * 1024 * 1024, // 20MB to handle large TLS handshakes
|
||||||
|
|
||||||
// Feature flags - simplified with sensible defaults
|
// Feature flags - simplified with sensible defaults
|
||||||
disableInactivityCheck: false, // Always enable inactivity checks for TLS safety
|
disableInactivityCheck: false, // Still enable inactivity checks
|
||||||
enableKeepAliveProbes: true, // Always enable keep-alive probes for connection health
|
enableKeepAliveProbes: true, // Still enable keep-alive probes
|
||||||
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
||||||
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
||||||
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
||||||
|
|
||||||
// Rate limiting defaults
|
// Rate limiting defaults
|
||||||
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
|
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 200, // 200 connections per IP (doubled)
|
||||||
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
|
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 500, // 500 per minute (increased)
|
||||||
|
|
||||||
// Keep-alive settings with sensible defaults that ensure certificate safety
|
// Keep-alive settings with much more relaxed defaults
|
||||||
keepAliveTreatment: 'standard', // Always use standard treatment for certificate safety
|
keepAliveTreatment: 'extended', // Use extended keep-alive treatment
|
||||||
keepAliveInactivityMultiplier: 2, // 2x normal inactivity timeout for minimal extension
|
keepAliveInactivityMultiplier: 3, // 3x normal inactivity timeout for longer extension
|
||||||
// Use shorter lifetime for chained proxies
|
// Much longer keep-alive lifetimes
|
||||||
extendedKeepAliveLifetime: isChainedProxy
|
extendedKeepAliveLifetime: isChainedProxy
|
||||||
? 2 * 60 * 60 * 1000 // 2 hours for chained proxies
|
? 24 * 60 * 60 * 1000 // 24 hours for chained proxies
|
||||||
: 3 * 60 * 60 * 1000, // 3 hours for standalone proxies
|
: 48 * 60 * 60 * 1000, // 48 hours for standalone proxies
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store NetworkProxy instances if provided
|
// 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({
|
const proxySocket = plugins.net.connect({
|
||||||
host: proxyHost,
|
host: proxyHost,
|
||||||
port: proxyPort,
|
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
|
// Store the outgoing socket in the record
|
||||||
@ -1165,6 +1171,30 @@ export class PortProxy {
|
|||||||
record.outgoingStartTime = Date.now();
|
record.outgoingStartTime = Date.now();
|
||||||
record.usingNetworkProxy = true;
|
record.usingNetworkProxy = true;
|
||||||
record.networkProxyIndex = proxyIndex;
|
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
|
// Set up error handlers
|
||||||
proxySocket.on('error', (err) => {
|
proxySocket.on('error', (err) => {
|
||||||
@ -1213,6 +1243,41 @@ export class PortProxy {
|
|||||||
|
|
||||||
// Update activity on data transfer from the proxy socket
|
// Update activity on data transfer from the proxy socket
|
||||||
proxySocket.on('data', () => this.updateActivity(record));
|
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) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(
|
console.log(
|
||||||
@ -1334,17 +1399,25 @@ export class PortProxy {
|
|||||||
|
|
||||||
// Apply keep-alive settings to the outgoing connection as well
|
// Apply keep-alive settings to the outgoing connection as well
|
||||||
if (this.settings.keepAlive) {
|
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
|
// Apply enhanced TCP keep-alive options if enabled
|
||||||
if (this.settings.enableKeepAliveProbes) {
|
if (this.settings.enableKeepAliveProbes) {
|
||||||
try {
|
try {
|
||||||
if ('setKeepAliveProbes' in targetSocket) {
|
if ('setKeepAliveProbes' in targetSocket) {
|
||||||
(targetSocket as any).setKeepAliveProbes(10);
|
(targetSocket as any).setKeepAliveProbes(10); // Same probes as incoming
|
||||||
}
|
}
|
||||||
if ('setKeepAliveInterval' in targetSocket) {
|
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) {
|
} catch (err) {
|
||||||
// Ignore errors - these are optional enhancements
|
// Ignore errors - these are optional enhancements
|
||||||
if (this.settings.enableDetailedLogging) {
|
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
|
// 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
|
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
|
||||||
if (serverName && record.isTLS) {
|
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) => {
|
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
|
// Always update activity timestamp for any handshake packet
|
||||||
this.updateActivity(record);
|
this.updateActivity(record);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Enhanced logging for renegotiation
|
||||||
|
console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
|
||||||
|
|
||||||
// Extract all TLS information including session resumption data
|
// Extract all TLS information including session resumption data
|
||||||
const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
|
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;
|
let newSNI = sniInfo?.serverName;
|
||||||
|
|
||||||
// Handle session resumption - if we recognize the session ID, we know what domain it belongs to
|
// 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;
|
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) {
|
if (newSNI === undefined) {
|
||||||
console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
|
console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
|
||||||
return;
|
|
||||||
|
// 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
|
// Check if the SNI has changed
|
||||||
@ -1605,15 +1744,34 @@ export class PortProxy {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
||||||
this.initiateCleanupOnce(record, 'sni_mismatch');
|
this.initiateCleanupOnce(record, 'sni_mismatch');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
|
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
|
// Now set up piping for future data and resume the socket
|
||||||
@ -1651,15 +1809,37 @@ export class PortProxy {
|
|||||||
} else {
|
} else {
|
||||||
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
|
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
|
||||||
if (serverName && record.isTLS) {
|
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) => {
|
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
|
// Always update activity timestamp for any handshake packet
|
||||||
this.updateActivity(record);
|
this.updateActivity(record);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Enhanced logging for renegotiation
|
||||||
|
console.log(`[${connectionId}] TLS handshake/renegotiation packet detected (${renegChunk.length} bytes)`);
|
||||||
|
|
||||||
// Extract all TLS information including session resumption data
|
// Extract all TLS information including session resumption data
|
||||||
const sniInfo = extractSNIInfo(renegChunk, this.settings.enableTlsDebugLogging);
|
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;
|
let newSNI = sniInfo?.serverName;
|
||||||
|
|
||||||
// Handle session resumption - if we recognize the session ID, we know what domain it belongs to
|
// 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;
|
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) {
|
if (newSNI === undefined) {
|
||||||
console.log(`[${connectionId}] Rehandshake detected without SNI, allowing it through.`);
|
console.log(`[${connectionId}] Rehandshake detected without SNI, forwarding transparently.`);
|
||||||
return;
|
|
||||||
|
// 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
|
// Check if the SNI has changed
|
||||||
@ -1745,15 +1932,45 @@ export class PortProxy {
|
|||||||
} else {
|
} else {
|
||||||
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
console.log(`[${connectionId}] Rehandshake SNI ${newSNI} not allowed. Terminating connection.`);
|
||||||
this.initiateCleanupOnce(record, 'sni_mismatch');
|
this.initiateCleanupOnce(record, 'sni_mismatch');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
console.log(`[${connectionId}] Rehandshake with same SNI: ${newSNI}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`[${connectionId}] Error processing renegotiation: ${err}. Allowing to continue.`);
|
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
|
// Now set up piping
|
||||||
@ -1974,15 +2191,16 @@ export class PortProxy {
|
|||||||
if (record.lastActivity > 0) {
|
if (record.lastActivity > 0) {
|
||||||
const timeDiff = now - record.lastActivity;
|
const timeDiff = now - record.lastActivity;
|
||||||
|
|
||||||
// Enhanced sleep detection with graduated thresholds
|
// Enhanced sleep detection with graduated thresholds - much more relaxed
|
||||||
// For chained proxies, we need to be more aggressive about refreshing connections
|
// Using chain detection from settings instead of recalculating
|
||||||
const isChainedProxy = this.settings.targetIP === 'localhost' || this.settings.targetIP === '127.0.0.1';
|
const isChainedProxy = this.settings.isChainedProxy || false;
|
||||||
const minuteInMs = 60 * 1000;
|
const minuteInMs = 60 * 1000;
|
||||||
|
const hourInMs = 60 * minuteInMs;
|
||||||
|
|
||||||
// Different thresholds based on connection type and configuration
|
// Significantly relaxed thresholds for better stability
|
||||||
const shortInactivityThreshold = isChainedProxy ? 10 * minuteInMs : 15 * minuteInMs;
|
const shortInactivityThreshold = 30 * minuteInMs; // 30 minutes
|
||||||
const mediumInactivityThreshold = isChainedProxy ? 20 * minuteInMs : 30 * minuteInMs;
|
const mediumInactivityThreshold = 2 * hourInMs; // 2 hours
|
||||||
const longInactivityThreshold = isChainedProxy ? 60 * minuteInMs : 120 * minuteInMs;
|
const longInactivityThreshold = 8 * hourInMs; // 8 hours
|
||||||
|
|
||||||
// Short inactivity (10-15 mins) - Might be temporary network issue or short sleep
|
// Short inactivity (10-15 mins) - Might be temporary network issue or short sleep
|
||||||
if (timeDiff > shortInactivityThreshold) {
|
if (timeDiff > shortInactivityThreshold) {
|
||||||
@ -2467,12 +2685,20 @@ export class PortProxy {
|
|||||||
|
|
||||||
// Initialize sleep detection fields
|
// Initialize sleep detection fields
|
||||||
possibleSystemSleep: false,
|
possibleSystemSleep: false,
|
||||||
|
|
||||||
|
// Track keep-alive state for both sides of the connection
|
||||||
|
incomingKeepAliveEnabled: false,
|
||||||
|
outgoingKeepAliveEnabled: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply keep-alive settings if enabled
|
// Apply keep-alive settings if enabled
|
||||||
if (this.settings.keepAlive) {
|
if (this.settings.keepAlive) {
|
||||||
|
// Configure incoming socket keep-alive
|
||||||
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
||||||
connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
|
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
|
// Apply enhanced TCP keep-alive options if enabled
|
||||||
if (this.settings.enableKeepAliveProbes) {
|
if (this.settings.enableKeepAliveProbes) {
|
||||||
@ -2484,6 +2710,8 @@ export class PortProxy {
|
|||||||
if ('setKeepAliveInterval' in socket) {
|
if ('setKeepAliveInterval' in socket) {
|
||||||
(socket as any).setKeepAliveInterval(1000); // 1 second interval between probes
|
(socket as any).setKeepAliveInterval(1000); // 1 second interval between probes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(`[${connectionId}] Enhanced TCP keep-alive probes configured on incoming connection`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Ignore errors - these are optional enhancements
|
// Ignore errors - these are optional enhancements
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user