fix(PortProxy): Ensure timeout values are within Node.js safe limits

This commit is contained in:
Philipp Kunz 2025-03-06 23:08:57 +00:00
parent 138900ca8b
commit a14b7802c4
3 changed files with 32 additions and 17 deletions

View File

@ -1,5 +1,11 @@
# Changelog # Changelog
## 2025-03-06 - 3.28.3 - fix(PortProxy)
Ensure timeout values are within Node.js safe limits
- Implemented `ensureSafeTimeout` to keep timeout values under the maximum safe integer for Node.js.
- Updated timeout configurations in `PortProxy` to include safety checks.
## 2025-03-06 - 3.28.2 - fix(portproxy) ## 2025-03-06 - 3.28.2 - fix(portproxy)
Adjust safe timeout defaults in PortProxy to prevent overflow issues. Adjust safe timeout defaults in PortProxy to prevent overflow issues.

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '3.28.2', version: '3.28.3',
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.'
} }

View File

@ -290,10 +290,17 @@ const isTlsHandshake = (buffer: Buffer): boolean => {
return buffer.length > 0 && buffer[0] === 22; // ContentType.handshake return buffer.length > 0 && buffer[0] === 22; // ContentType.handshake
}; };
// Helper: Ensure timeout values don't exceed Node.js max safe integer
const ensureSafeTimeout = (timeout: number): number => {
const MAX_SAFE_TIMEOUT = 2147483647; // Maximum safe value (2^31 - 1)
return Math.min(Math.floor(timeout), MAX_SAFE_TIMEOUT);
};
// Helper: Generate a slightly randomized timeout to prevent thundering herd // Helper: Generate a slightly randomized timeout to prevent thundering herd
const randomizeTimeout = (baseTimeout: number, variationPercent: number = 5): number => { const randomizeTimeout = (baseTimeout: number, variationPercent: number = 5): number => {
const variation = baseTimeout * (variationPercent / 100); const safeBaseTimeout = ensureSafeTimeout(baseTimeout);
return baseTimeout + Math.floor(Math.random() * variation * 2) - variation; const variation = safeBaseTimeout * (variationPercent / 100);
return ensureSafeTimeout(safeBaseTimeout + Math.floor(Math.random() * variation * 2) - variation);
}; };
export class PortProxy { export class PortProxy {
@ -325,12 +332,12 @@ export class PortProxy {
...settingsArg, ...settingsArg,
targetIP: settingsArg.targetIP || 'localhost', targetIP: settingsArg.targetIP || 'localhost',
// Timeout settings with safe limits for Node.js // Timeout settings with safe maximum values
initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake
socketTimeout: settingsArg.socketTimeout || 2147483647, // Maximum safe value (~24.8 days) socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 2147483647), // Maximum safe value (~24.8 days)
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval
maxConnectionLifetime: settingsArg.maxConnectionLifetime || 2147483647, // Maximum safe value (~24.8 days) maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 2147483647), // Maximum safe value (~24.8 days)
inactivityTimeout: settingsArg.inactivityTimeout || 14400000, // 4 hours inactivity timeout inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
@ -415,19 +422,19 @@ export class PortProxy {
* Get connection timeout based on domain config or default settings * Get connection timeout based on domain config or default settings
*/ */
private getConnectionTimeout(record: IConnectionRecord): number { private getConnectionTimeout(record: IConnectionRecord): number {
// If the connection has a domain-specific timeout, use that // If the connection has a domain-specific timeout, use that with safety check
if (record.domainConfig?.connectionTimeout) { if (record.domainConfig?.connectionTimeout) {
return record.domainConfig.connectionTimeout; return ensureSafeTimeout(record.domainConfig.connectionTimeout);
} }
// Use default timeout, potentially randomized // Use default timeout, potentially randomized with safety check
const baseTimeout = this.settings.maxConnectionLifetime!; const baseTimeout = this.settings.maxConnectionLifetime!;
if (this.settings.enableRandomizedTimeouts) { if (this.settings.enableRandomizedTimeouts) {
return randomizeTimeout(baseTimeout); return randomizeTimeout(baseTimeout);
} }
return baseTimeout; return ensureSafeTimeout(baseTimeout);
} }
/** /**
@ -1040,9 +1047,9 @@ export class PortProxy {
initiateCleanupOnce('timeout_outgoing'); initiateCleanupOnce('timeout_outgoing');
}); });
// Set appropriate timeouts using the configured value // Set appropriate timeouts using the configured value with safety
socket.setTimeout(this.settings.socketTimeout || 3600000); socket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
targetSocket.setTimeout(this.settings.socketTimeout || 3600000); targetSocket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
// Track outgoing data for bytes counting // Track outgoing data for bytes counting
targetSocket.on('data', (chunk: Buffer) => { targetSocket.on('data', (chunk: Buffer) => {
@ -1169,8 +1176,10 @@ export class PortProxy {
clearTimeout(connectionRecord.cleanupTimer); clearTimeout(connectionRecord.cleanupTimer);
} }
// Set timeout based on domain config or default // Set timeout based on domain config or default with safety check
const connectionTimeout = this.getConnectionTimeout(connectionRecord); const connectionTimeout = this.getConnectionTimeout(connectionRecord);
const safeTimeout = ensureSafeTimeout(connectionTimeout); // Ensure timeout is safe
connectionRecord.cleanupTimer = setTimeout(() => { connectionRecord.cleanupTimer = setTimeout(() => {
console.log( console.log(
`[${connectionId}] Connection from ${remoteIP} exceeded max lifetime (${plugins.prettyMs( `[${connectionId}] Connection from ${remoteIP} exceeded max lifetime (${plugins.prettyMs(
@ -1178,7 +1187,7 @@ export class PortProxy {
)}), forcing cleanup.` )}), forcing cleanup.`
); );
initiateCleanupOnce('connection_timeout'); initiateCleanupOnce('connection_timeout');
}, connectionTimeout); }, safeTimeout);
// Make sure timeout doesn't keep the process alive // Make sure timeout doesn't keep the process alive
if (connectionRecord.cleanupTimer.unref) { if (connectionRecord.cleanupTimer.unref) {