feat(PortProxy): Add advanced TLS keep-alive handling and system sleep detection
This commit is contained in:
parent
54fbe5beac
commit
9d7ed21cba
@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# 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)
|
## 2025-03-07 - 3.29.3 - fix(core)
|
||||||
Fix functional errors in the proxy setup and enhance pnpm configuration
|
Fix functional errors in the proxy setup and enhance pnpm configuration
|
||||||
|
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
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.'
|
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,10 @@ interface IConnectionRecord {
|
|||||||
// New field for NetworkProxy tracking
|
// New field for NetworkProxy tracking
|
||||||
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
|
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
|
||||||
networkProxyIndex?: number; // Which NetworkProxy instance is being used
|
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
|
// 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));
|
proxySocket.on('data', () => this.updateActivity(record));
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
@ -835,6 +853,31 @@ export class PortProxy {
|
|||||||
}
|
}
|
||||||
// No cleanup timer for immortal connections
|
// 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
|
// For extended keep-alive connections, use extended timeout
|
||||||
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
||||||
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
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;
|
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.
|
* Cleans up a connection record.
|
||||||
* Destroys both incoming and outgoing sockets, clears timers, and removes the 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
|
* Get target IP with round-robin support
|
||||||
*/
|
*/
|
||||||
@ -1245,7 +1344,10 @@ export class PortProxy {
|
|||||||
outgoingTerminationReason: null,
|
outgoingTerminationReason: null,
|
||||||
|
|
||||||
// Initialize NetworkProxy tracking fields
|
// Initialize NetworkProxy tracking fields
|
||||||
usingNetworkProxy: false
|
usingNetworkProxy: false,
|
||||||
|
|
||||||
|
// Initialize sleep detection fields
|
||||||
|
possibleSystemSleep: false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply keep-alive settings if enabled
|
// Apply keep-alive settings if enabled
|
||||||
@ -1711,6 +1813,42 @@ export class PortProxy {
|
|||||||
|
|
||||||
const inactivityTime = now - record.lastActivity;
|
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
|
// Use extended timeout for extended-treatment keep-alive connections
|
||||||
let effectiveTimeout = this.settings.inactivityTimeout!;
|
let effectiveTimeout = this.settings.inactivityTimeout!;
|
||||||
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
||||||
@ -1742,6 +1880,18 @@ export class PortProxy {
|
|||||||
console.log(`[${id}] Error sending probe packet: ${err}`);
|
console.log(`[${id}] Error sending probe packet: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 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 {
|
} else {
|
||||||
// For non-keep-alive or after warning, close the connection
|
// For non-keep-alive or after warning, close the connection
|
||||||
console.log(
|
console.log(
|
||||||
@ -1751,6 +1901,7 @@ export class PortProxy {
|
|||||||
);
|
);
|
||||||
this.cleanupConnection(record, 'inactivity');
|
this.cleanupConnection(record, 'inactivity');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
||||||
// If activity detected after warning, clear the warning
|
// If activity detected after warning, clear the warning
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user