feat(PortProxy): Add advanced TLS keep-alive handling and system sleep detection

This commit is contained in:
Philipp Kunz 2025-03-08 12:40:55 +00:00
parent 54fbe5beac
commit 9d7ed21cba
3 changed files with 180 additions and 22 deletions

View File

@ -1,5 +1,12 @@
# Changelog
## 2025-03-08 - 3.30.0 - feat(PortProxy)
Add advanced TLS keep-alive handling and system sleep detection
- Implemented system sleep detection to maintain keep-alive connections.
- Enhanced TLS keep-alive connections with extended timeout and sleep detection mechanisms.
- Introduced automatic TLS state refresh after system wake-up to prevent connection drops.
## 2025-03-07 - 3.29.3 - fix(core)
Fix functional errors in the proxy setup and enhance pnpm configuration

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '3.29.3',
version: '3.30.0',
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,10 @@ interface IConnectionRecord {
// New field for NetworkProxy tracking
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
networkProxyIndex?: number; // Which NetworkProxy instance is being used
// Sleep detection fields
possibleSystemSleep?: boolean; // Flag to indicate a possible system sleep was detected
lastSleepDetection?: number; // Timestamp of the last sleep detection
}
/**
@ -481,7 +485,21 @@ export class PortProxy {
});
// Update activity on data transfer
socket.on('data', () => this.updateActivity(record));
socket.on('data', (chunk: Buffer) => {
this.updateActivity(record);
// Check for potential TLS renegotiation or reconnection packets
if (chunk.length > 0 && chunk[0] === 22) { // ContentType.handshake
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Detected potential TLS handshake data while connected to NetworkProxy`);
}
// Let the NetworkProxy handle the TLS renegotiation
// Just update the activity timestamp to prevent timeouts
record.lastActivity = Date.now();
}
});
proxySocket.on('data', () => this.updateActivity(record));
if (this.settings.enableDetailedLogging) {
@ -835,6 +853,31 @@ export class PortProxy {
}
// No cleanup timer for immortal connections
}
// For TLS keep-alive connections, use a very extended timeout
else if (record.hasKeepAlive && record.isTLS) {
// For TLS keep-alive connections, use a very extended timeout
// This helps prevent certificate errors after sleep/wake cycles
const tlsKeepAliveTimeout = 14 * 24 * 60 * 60 * 1000; // 14 days for TLS keep-alive
const safeTimeout = ensureSafeTimeout(tlsKeepAliveTimeout);
record.cleanupTimer = setTimeout(() => {
console.log(
`[${connectionId}] TLS keep-alive connection from ${record.remoteIP} exceeded extended lifetime (${plugins.prettyMs(
tlsKeepAliveTimeout
)}), forcing cleanup.`
);
this.initiateCleanupOnce(record, 'tls_extended_lifetime');
}, safeTimeout);
// Make sure timeout doesn't keep the process alive
if (record.cleanupTimer.unref) {
record.cleanupTimer.unref();
}
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] TLS keep-alive connection with enhanced protection, lifetime: ${plugins.prettyMs(tlsKeepAliveTimeout)}`);
}
}
// For extended keep-alive connections, use extended timeout
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
@ -950,6 +993,74 @@ export class PortProxy {
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
}
/**
* Update connection activity timestamp with sleep detection
*/
private updateActivity(record: IConnectionRecord): void {
// Get the current time
const now = Date.now();
// Check if there was a large time gap that suggests system sleep
if (record.lastActivity > 0) {
const timeDiff = now - record.lastActivity;
// If time difference is very large (> 30 minutes) and this is a keep-alive connection,
// this might indicate system sleep rather than just inactivity
if (timeDiff > 30 * 60 * 1000 && record.hasKeepAlive) {
if (this.settings.enableDetailedLogging) {
console.log(
`[${record.id}] Detected possible system sleep for ${plugins.prettyMs(timeDiff)}. ` +
`Preserving keep-alive connection.`
);
}
// For keep-alive connections after sleep, we should refresh the TLS state if needed
if (record.isTLS && record.tlsHandshakeComplete) {
this.refreshTlsStateAfterSleep(record);
}
// Mark that we detected sleep
record.possibleSystemSleep = true;
record.lastSleepDetection = now;
}
}
// Update the activity timestamp
record.lastActivity = now;
// Clear any inactivity warning
if (record.inactivityWarningIssued) {
record.inactivityWarningIssued = false;
}
}
/**
* Refresh TLS state after sleep detection
*/
private refreshTlsStateAfterSleep(record: IConnectionRecord): void {
// Skip if we're using a NetworkProxy as it handles its own TLS state
if (record.usingNetworkProxy) {
return;
}
try {
// For outgoing connections that might need to be refreshed
if (record.outgoing && !record.outgoing.destroyed) {
// Send a zero-byte packet to test the connection
record.outgoing.write(Buffer.alloc(0));
if (this.settings.enableDetailedLogging) {
console.log(`[${record.id}] Sent refresh packet after sleep detection`);
}
}
} catch (err) {
console.log(`[${record.id}] Error refreshing TLS state: ${err}`);
// If we can't refresh, don't terminate - client will re-establish if needed
// Just log the issue but preserve the connection
}
}
/**
* Cleans up a connection record.
* Destroys both incoming and outgoing sockets, clears timers, and removes the record.
@ -1058,18 +1169,6 @@ export class PortProxy {
}
}
/**
* Update connection activity timestamp
*/
private updateActivity(record: IConnectionRecord): void {
record.lastActivity = Date.now();
// Clear any inactivity warning
if (record.inactivityWarningIssued) {
record.inactivityWarningIssued = false;
}
}
/**
* Get target IP with round-robin support
*/
@ -1245,7 +1344,10 @@ export class PortProxy {
outgoingTerminationReason: null,
// Initialize NetworkProxy tracking fields
usingNetworkProxy: false
usingNetworkProxy: false,
// Initialize sleep detection fields
possibleSystemSleep: false
};
// Apply keep-alive settings if enabled
@ -1711,6 +1813,42 @@ export class PortProxy {
const inactivityTime = now - record.lastActivity;
// Special handling for TLS keep-alive connections
if (record.hasKeepAlive && record.isTLS && inactivityTime > this.settings.inactivityTimeout! / 2) {
// For TLS keep-alive connections that are getting stale, try to refresh before closing
if (!record.inactivityWarningIssued) {
console.log(
`[${id}] TLS keep-alive connection from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
`Attempting to preserve connection.`
);
// Set warning flag but give a much longer grace period for TLS connections
record.inactivityWarningIssued = true;
// For TLS connections, extend the last activity time considerably
// This gives browsers more time to re-establish the connection properly
record.lastActivity = now - (this.settings.inactivityTimeout! / 3);
// Try to stimulate the connection with a probe packet
if (record.outgoing && !record.outgoing.destroyed) {
try {
// For TLS connections, send a proper TLS heartbeat-like packet
// This is just a small empty buffer that won't affect the TLS session
record.outgoing.write(Buffer.alloc(0));
if (this.settings.enableDetailedLogging) {
console.log(`[${id}] Sent TLS keep-alive probe packet`);
}
} catch (err) {
console.log(`[${id}] Error sending TLS probe packet: ${err}`);
}
}
// Don't proceed to the normal inactivity check logic
continue;
}
}
// Use extended timeout for extended-treatment keep-alive connections
let effectiveTimeout = this.settings.inactivityTimeout!;
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
@ -1743,13 +1881,26 @@ export class PortProxy {
}
}
} else {
// For non-keep-alive or after warning, close the connection
console.log(
`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
`for ${plugins.prettyMs(inactivityTime)}.` +
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '')
);
this.cleanupConnection(record, 'inactivity');
// MODIFIED: For TLS connections, be more lenient before closing
if (record.isTLS && record.hasKeepAlive) {
// For TLS keep-alive connections, add additional grace period
// This helps with browsers reconnecting after sleep
console.log(
`[${id}] TLS keep-alive connection from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
`Adding extra grace period.`
);
// Give additional time for browsers to reconnect properly
record.lastActivity = now - (effectiveTimeout / 2);
} else {
// For non-keep-alive or after warning, close the connection
console.log(
`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
`for ${plugins.prettyMs(inactivityTime)}.` +
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '')
);
this.cleanupConnection(record, 'inactivity');
}
}
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
// If activity detected after warning, clear the warning