@push.rocks/smartproxy 3.28.5 → 3.28.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "3.28.
|
|
3
|
+
"version": "3.28.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"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.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '3.28.
|
|
6
|
+
version: '3.28.6',
|
|
7
7
|
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.'
|
|
8
8
|
}
|
package/ts/classes.portproxy.ts
CHANGED
|
@@ -26,8 +26,8 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|
|
26
26
|
initialDataTimeout?: number; // Timeout for initial data/SNI (ms), default: 60000 (60s)
|
|
27
27
|
socketTimeout?: number; // Socket inactivity timeout (ms), default: 3600000 (1h)
|
|
28
28
|
inactivityCheckInterval?: number; // How often to check for inactive connections (ms), default: 60000 (60s)
|
|
29
|
-
maxConnectionLifetime?: number; // Default max connection lifetime (ms), default:
|
|
30
|
-
inactivityTimeout?: number; // Inactivity timeout (ms), default:
|
|
29
|
+
maxConnectionLifetime?: number; // Default max connection lifetime (ms), default: 86400000 (24h)
|
|
30
|
+
inactivityTimeout?: number; // Inactivity timeout (ms), default: 14400000 (4h)
|
|
31
31
|
|
|
32
32
|
gracefulShutdownTimeout?: number; // (ms) maximum time to wait for connections to close during shutdown
|
|
33
33
|
globalPortRanges: Array<{ from: number; to: number }>; // Global allowed port ranges
|
|
@@ -49,6 +49,11 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|
|
49
49
|
// Rate limiting and security
|
|
50
50
|
maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
|
|
51
51
|
connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
|
|
52
|
+
|
|
53
|
+
// Enhanced keep-alive settings
|
|
54
|
+
keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections
|
|
55
|
+
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
|
|
56
|
+
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
/**
|
|
@@ -77,6 +82,12 @@ interface IConnectionRecord {
|
|
|
77
82
|
tlsHandshakeComplete: boolean; // Whether the TLS handshake is complete
|
|
78
83
|
hasReceivedInitialData: boolean; // Whether initial data has been received
|
|
79
84
|
domainConfig?: IDomainConfig; // Associated domain config for this connection
|
|
85
|
+
|
|
86
|
+
// Keep-alive tracking
|
|
87
|
+
hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection
|
|
88
|
+
inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued
|
|
89
|
+
incomingTerminationReason?: string | null; // Reason for incoming termination
|
|
90
|
+
outgoingTerminationReason?: string | null; // Reason for outgoing termination
|
|
80
91
|
}
|
|
81
92
|
|
|
82
93
|
/**
|
|
@@ -332,11 +343,11 @@ export class PortProxy {
|
|
|
332
343
|
...settingsArg,
|
|
333
344
|
targetIP: settingsArg.targetIP || 'localhost',
|
|
334
345
|
|
|
335
|
-
// Timeout settings with
|
|
346
|
+
// Timeout settings with reasonable defaults
|
|
336
347
|
initialDataTimeout: settingsArg.initialDataTimeout || 60000, // 60 seconds for initial handshake
|
|
337
|
-
socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout ||
|
|
348
|
+
socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout
|
|
338
349
|
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval
|
|
339
|
-
maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime ||
|
|
350
|
+
maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default
|
|
340
351
|
inactivityTimeout: ensureSafeTimeout(settingsArg.inactivityTimeout || 14400000), // 4 hours inactivity timeout
|
|
341
352
|
|
|
342
353
|
gracefulShutdownTimeout: settingsArg.gracefulShutdownTimeout || 30000, // 30 seconds
|
|
@@ -344,19 +355,25 @@ export class PortProxy {
|
|
|
344
355
|
// Socket optimization settings
|
|
345
356
|
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
|
346
357
|
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
|
347
|
-
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay ||
|
|
358
|
+
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds (reduced for responsiveness)
|
|
348
359
|
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB to handle large TLS handshakes
|
|
349
360
|
|
|
350
361
|
// Feature flags
|
|
351
362
|
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
352
|
-
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes
|
|
363
|
+
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined
|
|
364
|
+
? settingsArg.enableKeepAliveProbes : true, // Enable by default
|
|
353
365
|
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
354
366
|
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
355
|
-
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts ||
|
|
367
|
+
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false, // Disable randomization by default
|
|
356
368
|
|
|
357
369
|
// Rate limiting defaults
|
|
358
370
|
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100, // 100 connections per IP
|
|
359
371
|
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300, // 300 per minute
|
|
372
|
+
|
|
373
|
+
// Enhanced keep-alive settings
|
|
374
|
+
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended', // Extended by default
|
|
375
|
+
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6, // 6x normal inactivity timeout
|
|
376
|
+
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
360
377
|
};
|
|
361
378
|
}
|
|
362
379
|
|
|
@@ -418,25 +435,6 @@ export class PortProxy {
|
|
|
418
435
|
this.terminationStats[side][reason] = (this.terminationStats[side][reason] || 0) + 1;
|
|
419
436
|
}
|
|
420
437
|
|
|
421
|
-
/**
|
|
422
|
-
* Get connection timeout based on domain config or default settings
|
|
423
|
-
*/
|
|
424
|
-
private getConnectionTimeout(record: IConnectionRecord): number {
|
|
425
|
-
// If the connection has a domain-specific timeout, use that with safety check
|
|
426
|
-
if (record.domainConfig?.connectionTimeout) {
|
|
427
|
-
return ensureSafeTimeout(record.domainConfig.connectionTimeout);
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Use default timeout, potentially randomized with safety check
|
|
431
|
-
const baseTimeout = this.settings.maxConnectionLifetime!;
|
|
432
|
-
|
|
433
|
-
if (this.settings.enableRandomizedTimeouts) {
|
|
434
|
-
return randomizeTimeout(baseTimeout);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return ensureSafeTimeout(baseTimeout);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
438
|
/**
|
|
441
439
|
* Cleans up a connection record.
|
|
442
440
|
* Destroys both incoming and outgoing sockets, clears timers, and removes the record.
|
|
@@ -534,7 +532,7 @@ export class PortProxy {
|
|
|
534
532
|
` Duration: ${plugins.prettyMs(
|
|
535
533
|
duration
|
|
536
534
|
)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
|
|
537
|
-
`TLS: ${record.isTLS ? 'Yes' : 'No'}`
|
|
535
|
+
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}`
|
|
538
536
|
);
|
|
539
537
|
} else {
|
|
540
538
|
console.log(
|
|
@@ -549,6 +547,11 @@ export class PortProxy {
|
|
|
549
547
|
*/
|
|
550
548
|
private updateActivity(record: IConnectionRecord): void {
|
|
551
549
|
record.lastActivity = Date.now();
|
|
550
|
+
|
|
551
|
+
// Clear any inactivity warning
|
|
552
|
+
if (record.inactivityWarningIssued) {
|
|
553
|
+
record.inactivityWarningIssued = false;
|
|
554
|
+
}
|
|
552
555
|
}
|
|
553
556
|
|
|
554
557
|
/**
|
|
@@ -563,6 +566,22 @@ export class PortProxy {
|
|
|
563
566
|
}
|
|
564
567
|
return this.settings.targetIP!;
|
|
565
568
|
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Initiates cleanup once for a connection
|
|
572
|
+
*/
|
|
573
|
+
private initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void {
|
|
574
|
+
if (this.settings.enableDetailedLogging) {
|
|
575
|
+
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
if (record.incomingTerminationReason === null || record.incomingTerminationReason === undefined) {
|
|
579
|
+
record.incomingTerminationReason = reason;
|
|
580
|
+
this.incrementTerminationStat('incoming', reason);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
this.cleanupConnection(record, reason);
|
|
584
|
+
}
|
|
566
585
|
|
|
567
586
|
/**
|
|
568
587
|
* Main method to start the proxy
|
|
@@ -609,25 +628,7 @@ export class PortProxy {
|
|
|
609
628
|
|
|
610
629
|
// Apply socket optimizations
|
|
611
630
|
socket.setNoDelay(this.settings.noDelay);
|
|
612
|
-
|
|
613
|
-
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// Apply enhanced TCP options if available
|
|
617
|
-
if (this.settings.enableKeepAliveProbes) {
|
|
618
|
-
try {
|
|
619
|
-
// These are platform-specific and may not be available
|
|
620
|
-
if ('setKeepAliveProbes' in socket) {
|
|
621
|
-
(socket as any).setKeepAliveProbes(10);
|
|
622
|
-
}
|
|
623
|
-
if ('setKeepAliveInterval' in socket) {
|
|
624
|
-
(socket as any).setKeepAliveInterval(1000);
|
|
625
|
-
}
|
|
626
|
-
} catch (err) {
|
|
627
|
-
// Ignore errors - these are optional enhancements
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
+
|
|
631
632
|
// Create a unique connection ID and record
|
|
632
633
|
const connectionId = generateConnectionId();
|
|
633
634
|
const connectionRecord: IConnectionRecord = {
|
|
@@ -648,7 +649,34 @@ export class PortProxy {
|
|
|
648
649
|
isTLS: false,
|
|
649
650
|
tlsHandshakeComplete: false,
|
|
650
651
|
hasReceivedInitialData: false,
|
|
652
|
+
hasKeepAlive: false, // Will set to true if keep-alive is applied
|
|
653
|
+
incomingTerminationReason: null,
|
|
654
|
+
outgoingTerminationReason: null
|
|
651
655
|
};
|
|
656
|
+
|
|
657
|
+
// Apply keep-alive settings if enabled
|
|
658
|
+
if (this.settings.keepAlive) {
|
|
659
|
+
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
660
|
+
connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
|
|
661
|
+
|
|
662
|
+
// Apply enhanced TCP keep-alive options if enabled
|
|
663
|
+
if (this.settings.enableKeepAliveProbes) {
|
|
664
|
+
try {
|
|
665
|
+
// These are platform-specific and may not be available
|
|
666
|
+
if ('setKeepAliveProbes' in socket) {
|
|
667
|
+
(socket as any).setKeepAliveProbes(10); // More aggressive probing
|
|
668
|
+
}
|
|
669
|
+
if ('setKeepAliveInterval' in socket) {
|
|
670
|
+
(socket as any).setKeepAliveInterval(1000); // 1 second interval between probes
|
|
671
|
+
}
|
|
672
|
+
} catch (err) {
|
|
673
|
+
// Ignore errors - these are optional enhancements
|
|
674
|
+
if (this.settings.enableDetailedLogging) {
|
|
675
|
+
console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
}
|
|
652
680
|
|
|
653
681
|
// Track connection by IP
|
|
654
682
|
this.trackConnectionByIP(remoteIP, connectionId);
|
|
@@ -656,7 +684,9 @@ export class PortProxy {
|
|
|
656
684
|
|
|
657
685
|
if (this.settings.enableDetailedLogging) {
|
|
658
686
|
console.log(
|
|
659
|
-
`[${connectionId}] New connection from ${remoteIP} on port ${localPort}.
|
|
687
|
+
`[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
|
|
688
|
+
`Keep-Alive: ${connectionRecord.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
|
|
689
|
+
`Active connections: ${this.connectionRecords.size}`
|
|
660
690
|
);
|
|
661
691
|
} else {
|
|
662
692
|
console.log(
|
|
@@ -668,11 +698,6 @@ export class PortProxy {
|
|
|
668
698
|
let incomingTerminationReason: string | null = null;
|
|
669
699
|
let outgoingTerminationReason: string | null = null;
|
|
670
700
|
|
|
671
|
-
// Local function for cleanupOnce
|
|
672
|
-
const cleanupOnce = () => {
|
|
673
|
-
this.cleanupConnection(connectionRecord);
|
|
674
|
-
};
|
|
675
|
-
|
|
676
701
|
// Define initiateCleanupOnce for compatibility
|
|
677
702
|
const initiateCleanupOnce = (reason: string = 'normal') => {
|
|
678
703
|
if (this.settings.enableDetailedLogging) {
|
|
@@ -680,9 +705,10 @@ export class PortProxy {
|
|
|
680
705
|
}
|
|
681
706
|
if (incomingTerminationReason === null) {
|
|
682
707
|
incomingTerminationReason = reason;
|
|
708
|
+
connectionRecord.incomingTerminationReason = reason;
|
|
683
709
|
this.incrementTerminationStat('incoming', reason);
|
|
684
710
|
}
|
|
685
|
-
|
|
711
|
+
this.cleanupConnection(connectionRecord, reason);
|
|
686
712
|
};
|
|
687
713
|
|
|
688
714
|
// Helper to reject an incoming connection
|
|
@@ -691,9 +717,10 @@ export class PortProxy {
|
|
|
691
717
|
socket.end();
|
|
692
718
|
if (incomingTerminationReason === null) {
|
|
693
719
|
incomingTerminationReason = reason;
|
|
720
|
+
connectionRecord.incomingTerminationReason = reason;
|
|
694
721
|
this.incrementTerminationStat('incoming', reason);
|
|
695
722
|
}
|
|
696
|
-
|
|
723
|
+
this.cleanupConnection(connectionRecord, reason);
|
|
697
724
|
};
|
|
698
725
|
|
|
699
726
|
// Set an initial timeout for SNI data if needed
|
|
@@ -706,10 +733,11 @@ export class PortProxy {
|
|
|
706
733
|
);
|
|
707
734
|
if (incomingTerminationReason === null) {
|
|
708
735
|
incomingTerminationReason = 'initial_timeout';
|
|
736
|
+
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
709
737
|
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
710
738
|
}
|
|
711
739
|
socket.end();
|
|
712
|
-
|
|
740
|
+
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
713
741
|
}
|
|
714
742
|
}, this.settings.initialDataTimeout!);
|
|
715
743
|
|
|
@@ -783,9 +811,11 @@ export class PortProxy {
|
|
|
783
811
|
|
|
784
812
|
if (side === 'incoming' && incomingTerminationReason === null) {
|
|
785
813
|
incomingTerminationReason = reason;
|
|
814
|
+
connectionRecord.incomingTerminationReason = reason;
|
|
786
815
|
this.incrementTerminationStat('incoming', reason);
|
|
787
816
|
} else if (side === 'outgoing' && outgoingTerminationReason === null) {
|
|
788
817
|
outgoingTerminationReason = reason;
|
|
818
|
+
connectionRecord.outgoingTerminationReason = reason;
|
|
789
819
|
this.incrementTerminationStat('outgoing', reason);
|
|
790
820
|
}
|
|
791
821
|
|
|
@@ -799,9 +829,11 @@ export class PortProxy {
|
|
|
799
829
|
|
|
800
830
|
if (side === 'incoming' && incomingTerminationReason === null) {
|
|
801
831
|
incomingTerminationReason = 'normal';
|
|
832
|
+
connectionRecord.incomingTerminationReason = 'normal';
|
|
802
833
|
this.incrementTerminationStat('incoming', 'normal');
|
|
803
834
|
} else if (side === 'outgoing' && outgoingTerminationReason === null) {
|
|
804
835
|
outgoingTerminationReason = 'normal';
|
|
836
|
+
connectionRecord.outgoingTerminationReason = 'normal';
|
|
805
837
|
this.incrementTerminationStat('outgoing', 'normal');
|
|
806
838
|
// Record the time when outgoing socket closed.
|
|
807
839
|
connectionRecord.outgoingClosedTime = Date.now();
|
|
@@ -956,21 +988,26 @@ export class PortProxy {
|
|
|
956
988
|
|
|
957
989
|
// Apply socket optimizations
|
|
958
990
|
targetSocket.setNoDelay(this.settings.noDelay);
|
|
991
|
+
|
|
992
|
+
// Apply keep-alive settings to the outgoing connection as well
|
|
959
993
|
if (this.settings.keepAlive) {
|
|
960
994
|
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
995
|
+
|
|
996
|
+
// Apply enhanced TCP keep-alive options if enabled
|
|
997
|
+
if (this.settings.enableKeepAliveProbes) {
|
|
998
|
+
try {
|
|
999
|
+
if ('setKeepAliveProbes' in targetSocket) {
|
|
1000
|
+
(targetSocket as any).setKeepAliveProbes(10);
|
|
1001
|
+
}
|
|
1002
|
+
if ('setKeepAliveInterval' in targetSocket) {
|
|
1003
|
+
(targetSocket as any).setKeepAliveInterval(1000);
|
|
1004
|
+
}
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
// Ignore errors - these are optional enhancements
|
|
1007
|
+
if (this.settings.enableDetailedLogging) {
|
|
1008
|
+
console.log(`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`);
|
|
1009
|
+
}
|
|
971
1010
|
}
|
|
972
|
-
} catch (err) {
|
|
973
|
-
// Ignore errors - these are optional enhancements
|
|
974
1011
|
}
|
|
975
1012
|
}
|
|
976
1013
|
|
|
@@ -1009,6 +1046,7 @@ export class PortProxy {
|
|
|
1009
1046
|
|
|
1010
1047
|
if (outgoingTerminationReason === null) {
|
|
1011
1048
|
outgoingTerminationReason = 'connection_failed';
|
|
1049
|
+
connectionRecord.outgoingTerminationReason = 'connection_failed';
|
|
1012
1050
|
this.incrementTerminationStat('outgoing', 'connection_failed');
|
|
1013
1051
|
}
|
|
1014
1052
|
|
|
@@ -1020,8 +1058,20 @@ export class PortProxy {
|
|
|
1020
1058
|
targetSocket.on('close', handleClose('outgoing'));
|
|
1021
1059
|
socket.on('close', handleClose('incoming'));
|
|
1022
1060
|
|
|
1023
|
-
// Handle timeouts
|
|
1061
|
+
// Handle timeouts with keep-alive awareness
|
|
1024
1062
|
socket.on('timeout', () => {
|
|
1063
|
+
// For keep-alive connections, just log a warning instead of closing
|
|
1064
|
+
if (connectionRecord.hasKeepAlive) {
|
|
1065
|
+
console.log(
|
|
1066
|
+
`[${connectionId}] Timeout event on incoming keep-alive connection from ${remoteIP} after ${plugins.prettyMs(
|
|
1067
|
+
this.settings.socketTimeout || 3600000
|
|
1068
|
+
)}. Connection preserved.`
|
|
1069
|
+
);
|
|
1070
|
+
// Don't close the connection - just log
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// For non-keep-alive connections, proceed with normal cleanup
|
|
1025
1075
|
console.log(
|
|
1026
1076
|
`[${connectionId}] Timeout on incoming side from ${remoteIP} after ${plugins.prettyMs(
|
|
1027
1077
|
this.settings.socketTimeout || 3600000
|
|
@@ -1029,12 +1079,25 @@ export class PortProxy {
|
|
|
1029
1079
|
);
|
|
1030
1080
|
if (incomingTerminationReason === null) {
|
|
1031
1081
|
incomingTerminationReason = 'timeout';
|
|
1082
|
+
connectionRecord.incomingTerminationReason = 'timeout';
|
|
1032
1083
|
this.incrementTerminationStat('incoming', 'timeout');
|
|
1033
1084
|
}
|
|
1034
1085
|
initiateCleanupOnce('timeout_incoming');
|
|
1035
1086
|
});
|
|
1036
1087
|
|
|
1037
1088
|
targetSocket.on('timeout', () => {
|
|
1089
|
+
// For keep-alive connections, just log a warning instead of closing
|
|
1090
|
+
if (connectionRecord.hasKeepAlive) {
|
|
1091
|
+
console.log(
|
|
1092
|
+
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${remoteIP} after ${plugins.prettyMs(
|
|
1093
|
+
this.settings.socketTimeout || 3600000
|
|
1094
|
+
)}. Connection preserved.`
|
|
1095
|
+
);
|
|
1096
|
+
// Don't close the connection - just log
|
|
1097
|
+
return;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// For non-keep-alive connections, proceed with normal cleanup
|
|
1038
1101
|
console.log(
|
|
1039
1102
|
`[${connectionId}] Timeout on outgoing side from ${remoteIP} after ${plugins.prettyMs(
|
|
1040
1103
|
this.settings.socketTimeout || 3600000
|
|
@@ -1042,14 +1105,26 @@ export class PortProxy {
|
|
|
1042
1105
|
);
|
|
1043
1106
|
if (outgoingTerminationReason === null) {
|
|
1044
1107
|
outgoingTerminationReason = 'timeout';
|
|
1108
|
+
connectionRecord.outgoingTerminationReason = 'timeout';
|
|
1045
1109
|
this.incrementTerminationStat('outgoing', 'timeout');
|
|
1046
1110
|
}
|
|
1047
1111
|
initiateCleanupOnce('timeout_outgoing');
|
|
1048
1112
|
});
|
|
1049
1113
|
|
|
1050
|
-
// Set appropriate timeouts
|
|
1051
|
-
|
|
1052
|
-
|
|
1114
|
+
// Set appropriate timeouts, or disable for immortal keep-alive connections
|
|
1115
|
+
if (connectionRecord.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
1116
|
+
// Disable timeouts completely for immortal connections
|
|
1117
|
+
socket.setTimeout(0);
|
|
1118
|
+
targetSocket.setTimeout(0);
|
|
1119
|
+
|
|
1120
|
+
if (this.settings.enableDetailedLogging) {
|
|
1121
|
+
console.log(`[${connectionId}] Disabled socket timeouts for immortal keep-alive connection`);
|
|
1122
|
+
}
|
|
1123
|
+
} else {
|
|
1124
|
+
// Set normal timeouts for other connections
|
|
1125
|
+
socket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
|
|
1126
|
+
targetSocket.setTimeout(ensureSafeTimeout(this.settings.socketTimeout || 3600000));
|
|
1127
|
+
}
|
|
1053
1128
|
|
|
1054
1129
|
// Track outgoing data for bytes counting
|
|
1055
1130
|
targetSocket.on('data', (chunk: Buffer) => {
|
|
@@ -1094,7 +1169,7 @@ export class PortProxy {
|
|
|
1094
1169
|
? ` (Port-based for domain: ${forcedDomain.domains.join(', ')})`
|
|
1095
1170
|
: ''
|
|
1096
1171
|
}` +
|
|
1097
|
-
` TLS: ${connectionRecord.isTLS ? 'Yes' : 'No'}`
|
|
1172
|
+
` TLS: ${connectionRecord.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${connectionRecord.hasKeepAlive ? 'Yes' : 'No'}`
|
|
1098
1173
|
);
|
|
1099
1174
|
} else {
|
|
1100
1175
|
console.log(
|
|
@@ -1125,7 +1200,7 @@ export class PortProxy {
|
|
|
1125
1200
|
? ` (Port-based for domain: ${forcedDomain.domains.join(', ')})`
|
|
1126
1201
|
: ''
|
|
1127
1202
|
}` +
|
|
1128
|
-
` TLS: ${connectionRecord.isTLS ? 'Yes' : 'No'}`
|
|
1203
|
+
` TLS: ${connectionRecord.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${connectionRecord.hasKeepAlive ? 'Yes' : 'No'}`
|
|
1129
1204
|
);
|
|
1130
1205
|
} else {
|
|
1131
1206
|
console.log(
|
|
@@ -1171,27 +1246,60 @@ export class PortProxy {
|
|
|
1171
1246
|
});
|
|
1172
1247
|
}
|
|
1173
1248
|
|
|
1174
|
-
// Set connection timeout
|
|
1249
|
+
// Set connection timeout with simpler logic
|
|
1175
1250
|
if (connectionRecord.cleanupTimer) {
|
|
1176
1251
|
clearTimeout(connectionRecord.cleanupTimer);
|
|
1177
1252
|
}
|
|
1178
|
-
|
|
1179
|
-
// Set timeout based on domain config or default with safety check
|
|
1180
|
-
const connectionTimeout = this.getConnectionTimeout(connectionRecord);
|
|
1181
|
-
const safeTimeout = ensureSafeTimeout(connectionTimeout); // Ensure timeout is safe
|
|
1182
1253
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1254
|
+
// For immortal keep-alive connections, skip setting a timeout completely
|
|
1255
|
+
if (connectionRecord.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
1256
|
+
if (this.settings.enableDetailedLogging) {
|
|
1257
|
+
console.log(`[${connectionId}] Keep-alive connection with immortal treatment - no max lifetime`);
|
|
1258
|
+
}
|
|
1259
|
+
// No cleanup timer for immortal connections
|
|
1260
|
+
}
|
|
1261
|
+
// For extended keep-alive connections, use extended timeout
|
|
1262
|
+
else if (connectionRecord.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
1263
|
+
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
1264
|
+
const safeTimeout = ensureSafeTimeout(extendedTimeout);
|
|
1265
|
+
|
|
1266
|
+
connectionRecord.cleanupTimer = setTimeout(() => {
|
|
1267
|
+
console.log(
|
|
1268
|
+
`[${connectionId}] Keep-alive connection from ${remoteIP} exceeded extended lifetime (${plugins.prettyMs(
|
|
1269
|
+
extendedTimeout
|
|
1270
|
+
)}), forcing cleanup.`
|
|
1271
|
+
);
|
|
1272
|
+
initiateCleanupOnce('extended_lifetime');
|
|
1273
|
+
}, safeTimeout);
|
|
1274
|
+
|
|
1275
|
+
// Make sure timeout doesn't keep the process alive
|
|
1276
|
+
if (connectionRecord.cleanupTimer.unref) {
|
|
1277
|
+
connectionRecord.cleanupTimer.unref();
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
if (this.settings.enableDetailedLogging) {
|
|
1281
|
+
console.log(`[${connectionId}] Keep-alive connection with extended lifetime of ${plugins.prettyMs(extendedTimeout)}`);
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
// For standard connections, use normal timeout
|
|
1285
|
+
else {
|
|
1286
|
+
// Use domain-specific timeout if available, otherwise use default
|
|
1287
|
+
const connectionTimeout = connectionRecord.domainConfig?.connectionTimeout || this.settings.maxConnectionLifetime!;
|
|
1288
|
+
const safeTimeout = ensureSafeTimeout(connectionTimeout);
|
|
1289
|
+
|
|
1290
|
+
connectionRecord.cleanupTimer = setTimeout(() => {
|
|
1291
|
+
console.log(
|
|
1292
|
+
`[${connectionId}] Connection from ${remoteIP} exceeded max lifetime (${plugins.prettyMs(
|
|
1293
|
+
connectionTimeout
|
|
1294
|
+
)}), forcing cleanup.`
|
|
1295
|
+
);
|
|
1296
|
+
initiateCleanupOnce('connection_timeout');
|
|
1297
|
+
}, safeTimeout);
|
|
1298
|
+
|
|
1299
|
+
// Make sure timeout doesn't keep the process alive
|
|
1300
|
+
if (connectionRecord.cleanupTimer.unref) {
|
|
1301
|
+
connectionRecord.cleanupTimer.unref();
|
|
1302
|
+
}
|
|
1195
1303
|
}
|
|
1196
1304
|
|
|
1197
1305
|
// Mark TLS handshake as complete for TLS connections
|
|
@@ -1385,6 +1493,7 @@ export class PortProxy {
|
|
|
1385
1493
|
let nonTlsConnections = 0;
|
|
1386
1494
|
let completedTlsHandshakes = 0;
|
|
1387
1495
|
let pendingTlsHandshakes = 0;
|
|
1496
|
+
let keepAliveConnections = 0;
|
|
1388
1497
|
|
|
1389
1498
|
// Create a copy of the keys to avoid modification during iteration
|
|
1390
1499
|
const connectionIds = [...this.connectionRecords.keys()];
|
|
@@ -1404,6 +1513,10 @@ export class PortProxy {
|
|
|
1404
1513
|
} else {
|
|
1405
1514
|
nonTlsConnections++;
|
|
1406
1515
|
}
|
|
1516
|
+
|
|
1517
|
+
if (record.hasKeepAlive) {
|
|
1518
|
+
keepAliveConnections++;
|
|
1519
|
+
}
|
|
1407
1520
|
|
|
1408
1521
|
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
1409
1522
|
if (record.outgoingStartTime) {
|
|
@@ -1440,19 +1553,58 @@ export class PortProxy {
|
|
|
1440
1553
|
);
|
|
1441
1554
|
}
|
|
1442
1555
|
|
|
1443
|
-
// Skip inactivity check if disabled
|
|
1444
|
-
if (!this.settings.disableInactivityCheck
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1556
|
+
// Skip inactivity check if disabled or for immortal keep-alive connections
|
|
1557
|
+
if (!this.settings.disableInactivityCheck &&
|
|
1558
|
+
!(record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')) {
|
|
1559
|
+
|
|
1448
1560
|
const inactivityTime = now - record.lastActivity;
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1561
|
+
|
|
1562
|
+
// Use extended timeout for extended-treatment keep-alive connections
|
|
1563
|
+
let effectiveTimeout = this.settings.inactivityTimeout!;
|
|
1564
|
+
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
1565
|
+
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
1566
|
+
effectiveTimeout = effectiveTimeout * multiplier;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
if (inactivityTime > effectiveTimeout && !record.connectionClosed) {
|
|
1570
|
+
// For keep-alive connections, issue a warning first
|
|
1571
|
+
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
|
|
1572
|
+
console.log(
|
|
1573
|
+
`[${id}] Warning: Keep-alive connection from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
|
1574
|
+
`Will close in 10 minutes if no activity.`
|
|
1575
|
+
);
|
|
1576
|
+
|
|
1577
|
+
// Set warning flag and add grace period
|
|
1578
|
+
record.inactivityWarningIssued = true;
|
|
1579
|
+
record.lastActivity = now - (effectiveTimeout - 600000);
|
|
1580
|
+
|
|
1581
|
+
// Try to stimulate activity with a probe packet
|
|
1582
|
+
if (record.outgoing && !record.outgoing.destroyed) {
|
|
1583
|
+
try {
|
|
1584
|
+
record.outgoing.write(Buffer.alloc(0));
|
|
1585
|
+
|
|
1586
|
+
if (this.settings.enableDetailedLogging) {
|
|
1587
|
+
console.log(`[${id}] Sent probe packet to test keep-alive connection`);
|
|
1588
|
+
}
|
|
1589
|
+
} catch (err) {
|
|
1590
|
+
console.log(`[${id}] Error sending probe packet: ${err}`);
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
} else {
|
|
1594
|
+
// For non-keep-alive or after warning, close the connection
|
|
1595
|
+
console.log(
|
|
1596
|
+
`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
|
|
1597
|
+
`for ${plugins.prettyMs(inactivityTime)}.` +
|
|
1598
|
+
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '')
|
|
1599
|
+
);
|
|
1600
|
+
this.cleanupConnection(record, 'inactivity');
|
|
1601
|
+
}
|
|
1602
|
+
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
|
1603
|
+
// If activity detected after warning, clear the warning
|
|
1604
|
+
if (this.settings.enableDetailedLogging) {
|
|
1605
|
+
console.log(`[${id}] Connection activity detected after inactivity warning, resetting warning`);
|
|
1606
|
+
}
|
|
1607
|
+
record.inactivityWarningIssued = false;
|
|
1456
1608
|
}
|
|
1457
1609
|
}
|
|
1458
1610
|
}
|
|
@@ -1460,7 +1612,8 @@ export class PortProxy {
|
|
|
1460
1612
|
// Log detailed stats periodically
|
|
1461
1613
|
console.log(
|
|
1462
1614
|
`Active connections: ${this.connectionRecords.size}. ` +
|
|
1463
|
-
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}),
|
|
1615
|
+
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
|
|
1616
|
+
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}. ` +
|
|
1464
1617
|
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(
|
|
1465
1618
|
maxOutgoing
|
|
1466
1619
|
)}. ` +
|