@push.rocks/smartproxy 3.32.2 → 3.34.0
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.portproxy.d.ts +7 -15
- package/dist_ts/classes.portproxy.js +330 -218
- package/dist_ts/classes.router.d.ts +22 -0
- package/dist_ts/classes.router.js +70 -2
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +552 -394
- package/ts/classes.router.ts +82 -1
package/ts/classes.portproxy.ts
CHANGED
|
@@ -10,10 +10,6 @@ export interface IDomainConfig {
|
|
|
10
10
|
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
|
|
11
11
|
// Allow domain-specific timeout override
|
|
12
12
|
connectionTimeout?: number; // Connection timeout override (ms)
|
|
13
|
-
|
|
14
|
-
// New properties for NetworkProxy integration
|
|
15
|
-
useNetworkProxy?: boolean; // When true, forwards TLS connections to NetworkProxy
|
|
16
|
-
networkProxyIndex?: number; // Optional index to specify which NetworkProxy to use (defaults to 0)
|
|
17
13
|
}
|
|
18
14
|
|
|
19
15
|
/** Port proxy settings including global allowed port ranges */
|
|
@@ -54,14 +50,15 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|
|
54
50
|
// Rate limiting and security
|
|
55
51
|
maxConnectionsPerIP?: number; // Maximum simultaneous connections from a single IP
|
|
56
52
|
connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
|
|
57
|
-
|
|
53
|
+
|
|
58
54
|
// Enhanced keep-alive settings
|
|
59
55
|
keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections
|
|
60
56
|
keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
|
|
61
57
|
extendedKeepAliveLifetime?: number; // Extended lifetime for keep-alive connections (ms)
|
|
62
|
-
|
|
58
|
+
|
|
63
59
|
// New property for NetworkProxy integration
|
|
64
|
-
|
|
60
|
+
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
|
61
|
+
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
|
65
62
|
}
|
|
66
63
|
|
|
67
64
|
/**
|
|
@@ -90,16 +87,22 @@ interface IConnectionRecord {
|
|
|
90
87
|
tlsHandshakeComplete: boolean; // Whether the TLS handshake is complete
|
|
91
88
|
hasReceivedInitialData: boolean; // Whether initial data has been received
|
|
92
89
|
domainConfig?: IDomainConfig; // Associated domain config for this connection
|
|
93
|
-
|
|
90
|
+
|
|
94
91
|
// Keep-alive tracking
|
|
95
92
|
hasKeepAlive: boolean; // Whether keep-alive is enabled for this connection
|
|
96
93
|
inactivityWarningIssued?: boolean; // Whether an inactivity warning has been issued
|
|
97
94
|
incomingTerminationReason?: string | null; // Reason for incoming termination
|
|
98
95
|
outgoingTerminationReason?: string | null; // Reason for outgoing termination
|
|
99
|
-
|
|
100
|
-
//
|
|
96
|
+
|
|
97
|
+
// NetworkProxy tracking
|
|
101
98
|
usingNetworkProxy?: boolean; // Whether this connection is using a NetworkProxy
|
|
102
|
-
|
|
99
|
+
|
|
100
|
+
// Renegotiation handler
|
|
101
|
+
renegotiationHandler?: (chunk: Buffer) => void; // Handler for renegotiation detection
|
|
102
|
+
|
|
103
|
+
// Browser connection tracking
|
|
104
|
+
isBrowserConnection?: boolean; // Whether this connection appears to be from a browser
|
|
105
|
+
domainSwitches?: number; // Number of times the domain has been switched on this connection
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
/**
|
|
@@ -266,6 +269,29 @@ function extractSNI(buffer: Buffer, enableLogging: boolean = false): string | un
|
|
|
266
269
|
}
|
|
267
270
|
}
|
|
268
271
|
|
|
272
|
+
/**
|
|
273
|
+
* Checks if a TLS record is a proper ClientHello message (more accurate than just checking record type)
|
|
274
|
+
* @param buffer - Buffer containing the TLS record
|
|
275
|
+
* @returns true if the buffer contains a proper ClientHello message
|
|
276
|
+
*/
|
|
277
|
+
function isClientHello(buffer: Buffer): boolean {
|
|
278
|
+
try {
|
|
279
|
+
if (buffer.length < 9) return false; // Too small for a proper ClientHello
|
|
280
|
+
|
|
281
|
+
// Check record type (has to be handshake - 22)
|
|
282
|
+
if (buffer.readUInt8(0) !== 22) return false;
|
|
283
|
+
|
|
284
|
+
// After the TLS record header (5 bytes), check the handshake type (1 for ClientHello)
|
|
285
|
+
if (buffer.readUInt8(5) !== 1) return false;
|
|
286
|
+
|
|
287
|
+
// Basic checks passed, this appears to be a ClientHello
|
|
288
|
+
return true;
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.log(`Error checking for ClientHello: ${err}`);
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
269
295
|
// Helper: Check if a port falls within any of the given port ranges
|
|
270
296
|
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
|
271
297
|
return ranges.some((range) => port >= range.from && port <= range.to);
|
|
@@ -348,9 +374,9 @@ export class PortProxy {
|
|
|
348
374
|
// Connection tracking by IP for rate limiting
|
|
349
375
|
private connectionsByIP: Map<string, Set<string>> = new Map();
|
|
350
376
|
private connectionRateByIP: Map<string, number[]> = new Map();
|
|
351
|
-
|
|
352
|
-
//
|
|
353
|
-
private
|
|
377
|
+
|
|
378
|
+
// NetworkProxy instance for TLS termination
|
|
379
|
+
private networkProxy: NetworkProxy | null = null;
|
|
354
380
|
|
|
355
381
|
constructor(settingsArg: IPortProxySettings) {
|
|
356
382
|
// Set reasonable defaults for all settings
|
|
@@ -370,29 +396,49 @@ export class PortProxy {
|
|
|
370
396
|
// Socket optimization settings
|
|
371
397
|
noDelay: settingsArg.noDelay !== undefined ? settingsArg.noDelay : true,
|
|
372
398
|
keepAlive: settingsArg.keepAlive !== undefined ? settingsArg.keepAlive : true,
|
|
373
|
-
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
|
|
374
|
-
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB
|
|
399
|
+
keepAliveInitialDelay: settingsArg.keepAliveInitialDelay || 10000, // 10 seconds
|
|
400
|
+
maxPendingDataSize: settingsArg.maxPendingDataSize || 10 * 1024 * 1024, // 10MB
|
|
375
401
|
|
|
376
402
|
// Feature flags
|
|
377
403
|
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
378
404
|
enableKeepAliveProbes: settingsArg.enableKeepAliveProbes !== undefined
|
|
379
|
-
|
|
405
|
+
? settingsArg.enableKeepAliveProbes : true,
|
|
380
406
|
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
381
407
|
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
382
|
-
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
408
|
+
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
383
409
|
|
|
384
410
|
// Rate limiting defaults
|
|
385
|
-
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
386
|
-
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
|
|
387
|
-
|
|
411
|
+
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
412
|
+
connectionRateLimitPerMinute: settingsArg.connectionRateLimitPerMinute || 300,
|
|
413
|
+
|
|
388
414
|
// Enhanced keep-alive settings
|
|
389
|
-
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
390
|
-
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
415
|
+
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
416
|
+
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
391
417
|
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
418
|
+
|
|
419
|
+
// NetworkProxy settings
|
|
420
|
+
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
|
|
392
421
|
};
|
|
393
|
-
|
|
394
|
-
//
|
|
395
|
-
this.
|
|
422
|
+
|
|
423
|
+
// Initialize NetworkProxy if enabled
|
|
424
|
+
if (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0) {
|
|
425
|
+
this.initializeNetworkProxy();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Initialize NetworkProxy instance
|
|
431
|
+
*/
|
|
432
|
+
private initializeNetworkProxy(): void {
|
|
433
|
+
if (!this.networkProxy) {
|
|
434
|
+
this.networkProxy = new NetworkProxy({
|
|
435
|
+
port: this.settings.networkProxyPort!,
|
|
436
|
+
portProxyIntegration: true,
|
|
437
|
+
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
|
441
|
+
}
|
|
396
442
|
}
|
|
397
443
|
|
|
398
444
|
/**
|
|
@@ -400,71 +446,69 @@ export class PortProxy {
|
|
|
400
446
|
* @param connectionId - Unique connection identifier
|
|
401
447
|
* @param socket - The incoming client socket
|
|
402
448
|
* @param record - The connection record
|
|
403
|
-
* @param domainConfig - The domain configuration
|
|
404
449
|
* @param initialData - Initial data chunk (TLS ClientHello)
|
|
405
|
-
* @param serverName - SNI hostname (if available)
|
|
406
450
|
*/
|
|
407
451
|
private forwardToNetworkProxy(
|
|
408
452
|
connectionId: string,
|
|
409
453
|
socket: plugins.net.Socket,
|
|
410
454
|
record: IConnectionRecord,
|
|
411
|
-
|
|
412
|
-
initialData: Buffer,
|
|
413
|
-
serverName?: string
|
|
455
|
+
initialData: Buffer
|
|
414
456
|
): void {
|
|
415
|
-
//
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
// Validate the NetworkProxy index
|
|
421
|
-
if (proxyIndex < 0 || proxyIndex >= this.networkProxies.length) {
|
|
422
|
-
console.log(`[${connectionId}] Invalid NetworkProxy index: ${proxyIndex}. Using fallback direct connection.`);
|
|
457
|
+
// Ensure NetworkProxy is initialized
|
|
458
|
+
if (!this.networkProxy) {
|
|
459
|
+
console.log(
|
|
460
|
+
`[${connectionId}] NetworkProxy not initialized. Using fallback direct connection.`
|
|
461
|
+
);
|
|
423
462
|
// Fall back to direct connection
|
|
424
|
-
return this.setupDirectConnection(
|
|
463
|
+
return this.setupDirectConnection(
|
|
464
|
+
connectionId,
|
|
465
|
+
socket,
|
|
466
|
+
record,
|
|
467
|
+
undefined,
|
|
468
|
+
undefined,
|
|
469
|
+
initialData
|
|
470
|
+
);
|
|
425
471
|
}
|
|
426
|
-
|
|
427
|
-
const
|
|
428
|
-
const proxyPort = networkProxy.getListeningPort();
|
|
472
|
+
|
|
473
|
+
const proxyPort = this.networkProxy.getListeningPort();
|
|
429
474
|
const proxyHost = 'localhost'; // Assuming NetworkProxy runs locally
|
|
430
|
-
|
|
475
|
+
|
|
431
476
|
if (this.settings.enableDetailedLogging) {
|
|
432
477
|
console.log(
|
|
433
|
-
`[${connectionId}] Forwarding TLS connection to NetworkProxy
|
|
478
|
+
`[${connectionId}] Forwarding TLS connection to NetworkProxy at ${proxyHost}:${proxyPort}`
|
|
434
479
|
);
|
|
435
480
|
}
|
|
436
|
-
|
|
481
|
+
|
|
437
482
|
// Create a connection to the NetworkProxy
|
|
438
483
|
const proxySocket = plugins.net.connect({
|
|
439
484
|
host: proxyHost,
|
|
440
|
-
port: proxyPort
|
|
485
|
+
port: proxyPort,
|
|
441
486
|
});
|
|
442
|
-
|
|
487
|
+
|
|
443
488
|
// Store the outgoing socket in the record
|
|
444
489
|
record.outgoing = proxySocket;
|
|
445
490
|
record.outgoingStartTime = Date.now();
|
|
446
491
|
record.usingNetworkProxy = true;
|
|
447
|
-
|
|
448
|
-
|
|
492
|
+
|
|
449
493
|
// Set up error handlers
|
|
450
494
|
proxySocket.on('error', (err) => {
|
|
451
495
|
console.log(`[${connectionId}] Error connecting to NetworkProxy: ${err.message}`);
|
|
452
496
|
this.cleanupConnection(record, 'network_proxy_connect_error');
|
|
453
497
|
});
|
|
454
|
-
|
|
498
|
+
|
|
455
499
|
// Handle connection to NetworkProxy
|
|
456
500
|
proxySocket.on('connect', () => {
|
|
457
501
|
if (this.settings.enableDetailedLogging) {
|
|
458
502
|
console.log(`[${connectionId}] Connected to NetworkProxy at ${proxyHost}:${proxyPort}`);
|
|
459
503
|
}
|
|
460
|
-
|
|
504
|
+
|
|
461
505
|
// First send the initial data that contains the TLS ClientHello
|
|
462
506
|
proxySocket.write(initialData);
|
|
463
|
-
|
|
507
|
+
|
|
464
508
|
// Now set up bidirectional piping between client and NetworkProxy
|
|
465
509
|
socket.pipe(proxySocket);
|
|
466
510
|
proxySocket.pipe(socket);
|
|
467
|
-
|
|
511
|
+
|
|
468
512
|
// Setup cleanup handlers
|
|
469
513
|
proxySocket.on('close', () => {
|
|
470
514
|
if (this.settings.enableDetailedLogging) {
|
|
@@ -472,26 +516,28 @@ export class PortProxy {
|
|
|
472
516
|
}
|
|
473
517
|
this.cleanupConnection(record, 'network_proxy_closed');
|
|
474
518
|
});
|
|
475
|
-
|
|
519
|
+
|
|
476
520
|
socket.on('close', () => {
|
|
477
521
|
if (this.settings.enableDetailedLogging) {
|
|
478
|
-
console.log(
|
|
522
|
+
console.log(
|
|
523
|
+
`[${connectionId}] Client connection closed after forwarding to NetworkProxy`
|
|
524
|
+
);
|
|
479
525
|
}
|
|
480
526
|
this.cleanupConnection(record, 'client_closed');
|
|
481
527
|
});
|
|
482
|
-
|
|
528
|
+
|
|
483
529
|
// Update activity on data transfer
|
|
484
530
|
socket.on('data', () => this.updateActivity(record));
|
|
485
531
|
proxySocket.on('data', () => this.updateActivity(record));
|
|
486
|
-
|
|
532
|
+
|
|
487
533
|
if (this.settings.enableDetailedLogging) {
|
|
488
534
|
console.log(
|
|
489
|
-
`[${connectionId}] TLS connection successfully forwarded to NetworkProxy
|
|
535
|
+
`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`
|
|
490
536
|
);
|
|
491
537
|
}
|
|
492
538
|
});
|
|
493
539
|
}
|
|
494
|
-
|
|
540
|
+
|
|
495
541
|
/**
|
|
496
542
|
* Sets up a direct connection to the target (original behavior)
|
|
497
543
|
* This is used when NetworkProxy isn't configured or as a fallback
|
|
@@ -568,11 +614,11 @@ export class PortProxy {
|
|
|
568
614
|
|
|
569
615
|
// Apply socket optimizations
|
|
570
616
|
targetSocket.setNoDelay(this.settings.noDelay);
|
|
571
|
-
|
|
617
|
+
|
|
572
618
|
// Apply keep-alive settings to the outgoing connection as well
|
|
573
619
|
if (this.settings.keepAlive) {
|
|
574
620
|
targetSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
575
|
-
|
|
621
|
+
|
|
576
622
|
// Apply enhanced TCP keep-alive options if enabled
|
|
577
623
|
if (this.settings.enableKeepAliveProbes) {
|
|
578
624
|
try {
|
|
@@ -585,7 +631,9 @@ export class PortProxy {
|
|
|
585
631
|
} catch (err) {
|
|
586
632
|
// Ignore errors - these are optional enhancements
|
|
587
633
|
if (this.settings.enableDetailedLogging) {
|
|
588
|
-
console.log(
|
|
634
|
+
console.log(
|
|
635
|
+
`[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}`
|
|
636
|
+
);
|
|
589
637
|
}
|
|
590
638
|
}
|
|
591
639
|
}
|
|
@@ -642,19 +690,21 @@ export class PortProxy {
|
|
|
642
690
|
// For keep-alive connections, just log a warning instead of closing
|
|
643
691
|
if (record.hasKeepAlive) {
|
|
644
692
|
console.log(
|
|
645
|
-
`[${connectionId}] Timeout event on incoming keep-alive connection from ${
|
|
693
|
+
`[${connectionId}] Timeout event on incoming keep-alive connection from ${
|
|
694
|
+
record.remoteIP
|
|
695
|
+
} after ${plugins.prettyMs(
|
|
646
696
|
this.settings.socketTimeout || 3600000
|
|
647
697
|
)}. Connection preserved.`
|
|
648
698
|
);
|
|
649
699
|
// Don't close the connection - just log
|
|
650
700
|
return;
|
|
651
701
|
}
|
|
652
|
-
|
|
702
|
+
|
|
653
703
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
654
704
|
console.log(
|
|
655
|
-
`[${connectionId}] Timeout on incoming side from ${
|
|
656
|
-
|
|
657
|
-
)}`
|
|
705
|
+
`[${connectionId}] Timeout on incoming side from ${
|
|
706
|
+
record.remoteIP
|
|
707
|
+
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
|
|
658
708
|
);
|
|
659
709
|
if (record.incomingTerminationReason === null) {
|
|
660
710
|
record.incomingTerminationReason = 'timeout';
|
|
@@ -667,19 +717,21 @@ export class PortProxy {
|
|
|
667
717
|
// For keep-alive connections, just log a warning instead of closing
|
|
668
718
|
if (record.hasKeepAlive) {
|
|
669
719
|
console.log(
|
|
670
|
-
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${
|
|
720
|
+
`[${connectionId}] Timeout event on outgoing keep-alive connection from ${
|
|
721
|
+
record.remoteIP
|
|
722
|
+
} after ${plugins.prettyMs(
|
|
671
723
|
this.settings.socketTimeout || 3600000
|
|
672
724
|
)}. Connection preserved.`
|
|
673
725
|
);
|
|
674
726
|
// Don't close the connection - just log
|
|
675
727
|
return;
|
|
676
728
|
}
|
|
677
|
-
|
|
729
|
+
|
|
678
730
|
// For non-keep-alive connections, proceed with normal cleanup
|
|
679
731
|
console.log(
|
|
680
|
-
`[${connectionId}] Timeout on outgoing side from ${
|
|
681
|
-
|
|
682
|
-
)}`
|
|
732
|
+
`[${connectionId}] Timeout on outgoing side from ${
|
|
733
|
+
record.remoteIP
|
|
734
|
+
} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`
|
|
683
735
|
);
|
|
684
736
|
if (record.outgoingTerminationReason === null) {
|
|
685
737
|
record.outgoingTerminationReason = 'timeout';
|
|
@@ -693,9 +745,11 @@ export class PortProxy {
|
|
|
693
745
|
// Disable timeouts completely for immortal connections
|
|
694
746
|
socket.setTimeout(0);
|
|
695
747
|
targetSocket.setTimeout(0);
|
|
696
|
-
|
|
748
|
+
|
|
697
749
|
if (this.settings.enableDetailedLogging) {
|
|
698
|
-
console.log(
|
|
750
|
+
console.log(
|
|
751
|
+
`[${connectionId}] Disabled socket timeouts for immortal keep-alive connection`
|
|
752
|
+
);
|
|
699
753
|
}
|
|
700
754
|
} else {
|
|
701
755
|
// Set normal timeouts for other connections
|
|
@@ -725,9 +779,7 @@ export class PortProxy {
|
|
|
725
779
|
const combinedData = Buffer.concat(record.pendingData);
|
|
726
780
|
targetSocket.write(combinedData, (err) => {
|
|
727
781
|
if (err) {
|
|
728
|
-
console.log(
|
|
729
|
-
`[${connectionId}] Error writing pending data to target: ${err.message}`
|
|
730
|
-
);
|
|
782
|
+
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
|
731
783
|
return this.initiateCleanupOnce(record, 'write_error');
|
|
732
784
|
}
|
|
733
785
|
|
|
@@ -746,7 +798,9 @@ export class PortProxy {
|
|
|
746
798
|
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
747
799
|
: ''
|
|
748
800
|
}` +
|
|
749
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
801
|
+
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
802
|
+
record.hasKeepAlive ? 'Yes' : 'No'
|
|
803
|
+
}`
|
|
750
804
|
);
|
|
751
805
|
} else {
|
|
752
806
|
console.log(
|
|
@@ -777,7 +831,9 @@ export class PortProxy {
|
|
|
777
831
|
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
778
832
|
: ''
|
|
779
833
|
}` +
|
|
780
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
834
|
+
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
835
|
+
record.hasKeepAlive ? 'Yes' : 'No'
|
|
836
|
+
}`
|
|
781
837
|
);
|
|
782
838
|
} else {
|
|
783
839
|
console.log(
|
|
@@ -797,82 +853,104 @@ export class PortProxy {
|
|
|
797
853
|
record.pendingData = [];
|
|
798
854
|
record.pendingDataSize = 0;
|
|
799
855
|
|
|
800
|
-
// Add the renegotiation
|
|
856
|
+
// Add the renegotiation handler for SNI validation with strict domain enforcement
|
|
801
857
|
if (serverName) {
|
|
802
|
-
|
|
803
|
-
|
|
858
|
+
// Define a handler for checking renegotiation with improved detection
|
|
859
|
+
const renegotiationHandler = (renegChunk: Buffer) => {
|
|
860
|
+
// Only process if this looks like a TLS ClientHello
|
|
861
|
+
if (isClientHello(renegChunk)) {
|
|
804
862
|
try {
|
|
805
|
-
//
|
|
863
|
+
// Extract SNI from ClientHello
|
|
806
864
|
const newSNI = extractSNI(renegChunk, this.settings.enableTlsDebugLogging);
|
|
807
|
-
|
|
865
|
+
|
|
866
|
+
// Skip if no SNI was found
|
|
867
|
+
if (!newSNI) return;
|
|
868
|
+
|
|
869
|
+
// Handle SNI change during renegotiation - always terminate for domain switches
|
|
870
|
+
if (newSNI !== record.lockedDomain) {
|
|
871
|
+
// Log and terminate the connection for any SNI change
|
|
808
872
|
console.log(
|
|
809
|
-
`[${connectionId}]
|
|
873
|
+
`[${connectionId}] Renegotiation with different SNI: ${record.lockedDomain} -> ${newSNI}. ` +
|
|
874
|
+
`Terminating connection - SNI domain switching is not allowed.`
|
|
810
875
|
);
|
|
811
876
|
this.initiateCleanupOnce(record, 'sni_mismatch');
|
|
812
|
-
} else if (
|
|
877
|
+
} else if (this.settings.enableDetailedLogging) {
|
|
813
878
|
console.log(
|
|
814
|
-
`[${connectionId}]
|
|
879
|
+
`[${connectionId}] Renegotiation detected with same SNI: ${newSNI}. Allowing.`
|
|
815
880
|
);
|
|
816
881
|
}
|
|
817
882
|
} catch (err) {
|
|
818
883
|
console.log(
|
|
819
|
-
`[${connectionId}] Error processing
|
|
884
|
+
`[${connectionId}] Error processing ClientHello: ${err}. Allowing connection to continue.`
|
|
820
885
|
);
|
|
821
886
|
}
|
|
822
887
|
}
|
|
823
|
-
}
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
// Store the handler in the connection record so we can remove it during cleanup
|
|
891
|
+
record.renegotiationHandler = renegotiationHandler;
|
|
892
|
+
|
|
893
|
+
// Add the listener
|
|
894
|
+
socket.on('data', renegotiationHandler);
|
|
824
895
|
}
|
|
825
896
|
|
|
826
897
|
// Set connection timeout with simpler logic
|
|
827
898
|
if (record.cleanupTimer) {
|
|
828
899
|
clearTimeout(record.cleanupTimer);
|
|
829
900
|
}
|
|
830
|
-
|
|
901
|
+
|
|
831
902
|
// For immortal keep-alive connections, skip setting a timeout completely
|
|
832
903
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal') {
|
|
833
904
|
if (this.settings.enableDetailedLogging) {
|
|
834
|
-
console.log(
|
|
905
|
+
console.log(
|
|
906
|
+
`[${connectionId}] Keep-alive connection with immortal treatment - no max lifetime`
|
|
907
|
+
);
|
|
835
908
|
}
|
|
836
909
|
// No cleanup timer for immortal connections
|
|
837
|
-
}
|
|
910
|
+
}
|
|
838
911
|
// For extended keep-alive connections, use extended timeout
|
|
839
912
|
else if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
840
913
|
const extendedTimeout = this.settings.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000; // 7 days
|
|
841
914
|
const safeTimeout = ensureSafeTimeout(extendedTimeout);
|
|
842
|
-
|
|
915
|
+
|
|
843
916
|
record.cleanupTimer = setTimeout(() => {
|
|
844
917
|
console.log(
|
|
845
|
-
`[${connectionId}] Keep-alive connection from ${
|
|
846
|
-
|
|
847
|
-
)}), forcing cleanup.`
|
|
918
|
+
`[${connectionId}] Keep-alive connection from ${
|
|
919
|
+
record.remoteIP
|
|
920
|
+
} exceeded extended lifetime (${plugins.prettyMs(extendedTimeout)}), forcing cleanup.`
|
|
848
921
|
);
|
|
849
922
|
this.initiateCleanupOnce(record, 'extended_lifetime');
|
|
850
923
|
}, safeTimeout);
|
|
851
|
-
|
|
924
|
+
|
|
852
925
|
// Make sure timeout doesn't keep the process alive
|
|
853
926
|
if (record.cleanupTimer.unref) {
|
|
854
927
|
record.cleanupTimer.unref();
|
|
855
928
|
}
|
|
856
|
-
|
|
929
|
+
|
|
857
930
|
if (this.settings.enableDetailedLogging) {
|
|
858
|
-
console.log(
|
|
931
|
+
console.log(
|
|
932
|
+
`[${connectionId}] Keep-alive connection with extended lifetime of ${plugins.prettyMs(
|
|
933
|
+
extendedTimeout
|
|
934
|
+
)}`
|
|
935
|
+
);
|
|
859
936
|
}
|
|
860
937
|
}
|
|
861
938
|
// For standard connections, use normal timeout
|
|
862
939
|
else {
|
|
863
940
|
// Use domain-specific timeout if available, otherwise use default
|
|
864
|
-
const connectionTimeout =
|
|
941
|
+
const connectionTimeout =
|
|
942
|
+
record.domainConfig?.connectionTimeout || this.settings.maxConnectionLifetime!;
|
|
865
943
|
const safeTimeout = ensureSafeTimeout(connectionTimeout);
|
|
866
|
-
|
|
944
|
+
|
|
867
945
|
record.cleanupTimer = setTimeout(() => {
|
|
868
946
|
console.log(
|
|
869
|
-
`[${connectionId}] Connection from ${
|
|
870
|
-
|
|
871
|
-
)}), forcing cleanup.`
|
|
947
|
+
`[${connectionId}] Connection from ${
|
|
948
|
+
record.remoteIP
|
|
949
|
+
} exceeded max lifetime (${plugins.prettyMs(connectionTimeout)}), forcing cleanup.`
|
|
872
950
|
);
|
|
873
951
|
this.initiateCleanupOnce(record, 'connection_timeout');
|
|
874
952
|
}, safeTimeout);
|
|
875
|
-
|
|
953
|
+
|
|
876
954
|
// Make sure timeout doesn't keep the process alive
|
|
877
955
|
if (record.cleanupTimer.unref) {
|
|
878
956
|
record.cleanupTimer.unref();
|
|
@@ -973,6 +1051,16 @@ export class PortProxy {
|
|
|
973
1051
|
const bytesReceived = record.bytesReceived;
|
|
974
1052
|
const bytesSent = record.bytesSent;
|
|
975
1053
|
|
|
1054
|
+
// Remove the renegotiation handler if present
|
|
1055
|
+
if (record.renegotiationHandler && record.incoming) {
|
|
1056
|
+
try {
|
|
1057
|
+
record.incoming.removeListener('data', record.renegotiationHandler);
|
|
1058
|
+
record.renegotiationHandler = undefined;
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
console.log(`[${record.id}] Error removing renegotiation handler: ${err}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
976
1064
|
try {
|
|
977
1065
|
if (!record.incoming.destroyed) {
|
|
978
1066
|
// Try graceful shutdown first, then force destroy after a short timeout
|
|
@@ -1047,8 +1135,11 @@ export class PortProxy {
|
|
|
1047
1135
|
` Duration: ${plugins.prettyMs(
|
|
1048
1136
|
duration
|
|
1049
1137
|
)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` +
|
|
1050
|
-
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
1051
|
-
|
|
1138
|
+
`TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
1139
|
+
record.hasKeepAlive ? 'Yes' : 'No'
|
|
1140
|
+
}` +
|
|
1141
|
+
`${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` +
|
|
1142
|
+
`${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}`
|
|
1052
1143
|
);
|
|
1053
1144
|
} else {
|
|
1054
1145
|
console.log(
|
|
@@ -1063,7 +1154,7 @@ export class PortProxy {
|
|
|
1063
1154
|
*/
|
|
1064
1155
|
private updateActivity(record: IConnectionRecord): void {
|
|
1065
1156
|
record.lastActivity = Date.now();
|
|
1066
|
-
|
|
1157
|
+
|
|
1067
1158
|
// Clear any inactivity warning
|
|
1068
1159
|
if (record.inactivityWarningIssued) {
|
|
1069
1160
|
record.inactivityWarningIssued = false;
|
|
@@ -1082,7 +1173,7 @@ export class PortProxy {
|
|
|
1082
1173
|
}
|
|
1083
1174
|
return this.settings.targetIP!;
|
|
1084
1175
|
}
|
|
1085
|
-
|
|
1176
|
+
|
|
1086
1177
|
/**
|
|
1087
1178
|
* Initiates cleanup once for a connection
|
|
1088
1179
|
*/
|
|
@@ -1090,12 +1181,15 @@ export class PortProxy {
|
|
|
1090
1181
|
if (this.settings.enableDetailedLogging) {
|
|
1091
1182
|
console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`);
|
|
1092
1183
|
}
|
|
1093
|
-
|
|
1094
|
-
if (
|
|
1184
|
+
|
|
1185
|
+
if (
|
|
1186
|
+
record.incomingTerminationReason === null ||
|
|
1187
|
+
record.incomingTerminationReason === undefined
|
|
1188
|
+
) {
|
|
1095
1189
|
record.incomingTerminationReason = reason;
|
|
1096
1190
|
this.incrementTerminationStat('incoming', reason);
|
|
1097
1191
|
}
|
|
1098
|
-
|
|
1192
|
+
|
|
1099
1193
|
this.cleanupConnection(record, reason);
|
|
1100
1194
|
}
|
|
1101
1195
|
|
|
@@ -1184,6 +1278,12 @@ export class PortProxy {
|
|
|
1184
1278
|
return;
|
|
1185
1279
|
}
|
|
1186
1280
|
|
|
1281
|
+
// Start NetworkProxy if configured
|
|
1282
|
+
if (this.networkProxy) {
|
|
1283
|
+
await this.networkProxy.start();
|
|
1284
|
+
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1187
1287
|
// Define a unified connection handler for all listening ports.
|
|
1188
1288
|
const connectionHandler = (socket: plugins.net.Socket) => {
|
|
1189
1289
|
if (this.isShuttingDown) {
|
|
@@ -1219,7 +1319,7 @@ export class PortProxy {
|
|
|
1219
1319
|
|
|
1220
1320
|
// Apply socket optimizations
|
|
1221
1321
|
socket.setNoDelay(this.settings.noDelay);
|
|
1222
|
-
|
|
1322
|
+
|
|
1223
1323
|
// Create a unique connection ID and record
|
|
1224
1324
|
const connectionId = generateConnectionId();
|
|
1225
1325
|
const connectionRecord: IConnectionRecord = {
|
|
@@ -1243,16 +1343,20 @@ export class PortProxy {
|
|
|
1243
1343
|
hasKeepAlive: false, // Will set to true if keep-alive is applied
|
|
1244
1344
|
incomingTerminationReason: null,
|
|
1245
1345
|
outgoingTerminationReason: null,
|
|
1246
|
-
|
|
1247
|
-
// Initialize NetworkProxy tracking
|
|
1248
|
-
usingNetworkProxy: false
|
|
1346
|
+
|
|
1347
|
+
// Initialize NetworkProxy tracking
|
|
1348
|
+
usingNetworkProxy: false,
|
|
1349
|
+
|
|
1350
|
+
// Initialize browser connection tracking
|
|
1351
|
+
isBrowserConnection: false,
|
|
1352
|
+
domainSwitches: 0,
|
|
1249
1353
|
};
|
|
1250
|
-
|
|
1354
|
+
|
|
1251
1355
|
// Apply keep-alive settings if enabled
|
|
1252
1356
|
if (this.settings.keepAlive) {
|
|
1253
1357
|
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
|
|
1254
1358
|
connectionRecord.hasKeepAlive = true; // Mark connection as having keep-alive
|
|
1255
|
-
|
|
1359
|
+
|
|
1256
1360
|
// Apply enhanced TCP keep-alive options if enabled
|
|
1257
1361
|
if (this.settings.enableKeepAliveProbes) {
|
|
1258
1362
|
try {
|
|
@@ -1266,7 +1370,9 @@ export class PortProxy {
|
|
|
1266
1370
|
} catch (err) {
|
|
1267
1371
|
// Ignore errors - these are optional enhancements
|
|
1268
1372
|
if (this.settings.enableDetailedLogging) {
|
|
1269
|
-
console.log(
|
|
1373
|
+
console.log(
|
|
1374
|
+
`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`
|
|
1375
|
+
);
|
|
1270
1376
|
}
|
|
1271
1377
|
}
|
|
1272
1378
|
}
|
|
@@ -1279,8 +1385,8 @@ export class PortProxy {
|
|
|
1279
1385
|
if (this.settings.enableDetailedLogging) {
|
|
1280
1386
|
console.log(
|
|
1281
1387
|
`[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
|
|
1282
|
-
|
|
1283
|
-
|
|
1388
|
+
`Keep-Alive: ${connectionRecord.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
|
|
1389
|
+
`Active connections: ${this.connectionRecords.size}`
|
|
1284
1390
|
);
|
|
1285
1391
|
} else {
|
|
1286
1392
|
console.log(
|
|
@@ -1288,23 +1394,16 @@ export class PortProxy {
|
|
|
1288
1394
|
);
|
|
1289
1395
|
}
|
|
1290
1396
|
|
|
1291
|
-
|
|
1397
|
+
// Check if this connection should be forwarded directly to NetworkProxy based on port
|
|
1398
|
+
const shouldUseNetworkProxy = this.settings.useNetworkProxy &&
|
|
1399
|
+
this.settings.useNetworkProxy.includes(localPort);
|
|
1292
1400
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
socket.end();
|
|
1297
|
-
if (connectionRecord.incomingTerminationReason === null) {
|
|
1298
|
-
connectionRecord.incomingTerminationReason = reason;
|
|
1299
|
-
this.incrementTerminationStat('incoming', reason);
|
|
1300
|
-
}
|
|
1301
|
-
this.cleanupConnection(connectionRecord, reason);
|
|
1302
|
-
};
|
|
1401
|
+
if (shouldUseNetworkProxy) {
|
|
1402
|
+
// For NetworkProxy ports, we want to capture the TLS handshake and forward directly
|
|
1403
|
+
let initialDataReceived = false;
|
|
1303
1404
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
if (this.settings.sniEnabled) {
|
|
1307
|
-
initialTimeout = setTimeout(() => {
|
|
1405
|
+
// Set an initial timeout for handshake data
|
|
1406
|
+
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
|
|
1308
1407
|
if (!initialDataReceived) {
|
|
1309
1408
|
console.log(
|
|
1310
1409
|
`[${connectionId}] Initial data timeout (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP} on port ${localPort}`
|
|
@@ -1322,278 +1421,331 @@ export class PortProxy {
|
|
|
1322
1421
|
if (initialTimeout.unref) {
|
|
1323
1422
|
initialTimeout.unref();
|
|
1324
1423
|
}
|
|
1325
|
-
} else {
|
|
1326
|
-
initialDataReceived = true;
|
|
1327
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1328
|
-
}
|
|
1329
1424
|
|
|
1330
|
-
|
|
1425
|
+
socket.on('error', this.handleError('incoming', connectionRecord));
|
|
1331
1426
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1427
|
+
// First data handler to capture initial TLS handshake for NetworkProxy
|
|
1428
|
+
socket.once('data', (chunk: Buffer) => {
|
|
1429
|
+
// Clear the initial timeout since we've received data
|
|
1430
|
+
if (initialTimeout) {
|
|
1431
|
+
clearTimeout(initialTimeout);
|
|
1432
|
+
initialTimeout = null;
|
|
1433
|
+
}
|
|
1336
1434
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
connectionRecord.isTLS = true;
|
|
1435
|
+
initialDataReceived = true;
|
|
1436
|
+
connectionRecord.hasReceivedInitialData = true;
|
|
1340
1437
|
|
|
1341
|
-
if
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
//
|
|
1346
|
-
|
|
1438
|
+
// Check if this looks like a TLS handshake
|
|
1439
|
+
if (isTlsHandshake(chunk)) {
|
|
1440
|
+
connectionRecord.isTLS = true;
|
|
1441
|
+
|
|
1442
|
+
// Forward directly to NetworkProxy without SNI processing
|
|
1443
|
+
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk);
|
|
1444
|
+
} else {
|
|
1445
|
+
// If not TLS, use normal direct connection
|
|
1446
|
+
console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${localPort}`);
|
|
1447
|
+
this.setupDirectConnection(connectionId, socket, connectionRecord, undefined, undefined, chunk);
|
|
1347
1448
|
}
|
|
1348
|
-
}
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
) => {
|
|
1364
|
-
// Clear the initial timeout since we've received data
|
|
1365
|
-
if (initialTimeout) {
|
|
1366
|
-
clearTimeout(initialTimeout);
|
|
1367
|
-
initialTimeout = null;
|
|
1368
|
-
}
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1451
|
+
} else {
|
|
1452
|
+
// For non-NetworkProxy ports, proceed with normal processing
|
|
1453
|
+
|
|
1454
|
+
// Define helpers for rejecting connections
|
|
1455
|
+
const rejectIncomingConnection = (reason: string, logMessage: string) => {
|
|
1456
|
+
console.log(`[${connectionId}] ${logMessage}`);
|
|
1457
|
+
socket.end();
|
|
1458
|
+
if (connectionRecord.incomingTerminationReason === null) {
|
|
1459
|
+
connectionRecord.incomingTerminationReason = reason;
|
|
1460
|
+
this.incrementTerminationStat('incoming', reason);
|
|
1461
|
+
}
|
|
1462
|
+
this.cleanupConnection(connectionRecord, reason);
|
|
1463
|
+
};
|
|
1369
1464
|
|
|
1370
|
-
|
|
1371
|
-
initialDataReceived = true;
|
|
1372
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1465
|
+
let initialDataReceived = false;
|
|
1373
1466
|
|
|
1374
|
-
//
|
|
1375
|
-
|
|
1376
|
-
if (
|
|
1377
|
-
|
|
1467
|
+
// Set an initial timeout for SNI data if needed
|
|
1468
|
+
let initialTimeout: NodeJS.Timeout | null = null;
|
|
1469
|
+
if (this.settings.sniEnabled) {
|
|
1470
|
+
initialTimeout = setTimeout(() => {
|
|
1471
|
+
if (!initialDataReceived) {
|
|
1472
|
+
console.log(
|
|
1473
|
+
`[${connectionId}] Initial data timeout (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP} on port ${localPort}`
|
|
1474
|
+
);
|
|
1475
|
+
if (connectionRecord.incomingTerminationReason === null) {
|
|
1476
|
+
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
1477
|
+
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
1478
|
+
}
|
|
1479
|
+
socket.end();
|
|
1480
|
+
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
1481
|
+
}
|
|
1482
|
+
}, this.settings.initialDataTimeout!);
|
|
1378
1483
|
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
);
|
|
1484
|
+
// Make sure timeout doesn't keep the process alive
|
|
1485
|
+
if (initialTimeout.unref) {
|
|
1486
|
+
initialTimeout.unref();
|
|
1383
1487
|
}
|
|
1488
|
+
} else {
|
|
1489
|
+
initialDataReceived = true;
|
|
1490
|
+
connectionRecord.hasReceivedInitialData = true;
|
|
1384
1491
|
}
|
|
1385
1492
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
const effectiveBlockedIPs: string[] = [
|
|
1405
|
-
...(domainConfig.blockedIPs || []),
|
|
1406
|
-
...(this.settings.defaultBlockedIPs || []),
|
|
1407
|
-
];
|
|
1408
|
-
|
|
1409
|
-
// Skip IP validation if allowedIPs is empty
|
|
1410
|
-
if (
|
|
1411
|
-
domainConfig.allowedIPs.length > 0 &&
|
|
1412
|
-
!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)
|
|
1413
|
-
) {
|
|
1414
|
-
return rejectIncomingConnection(
|
|
1415
|
-
'rejected',
|
|
1416
|
-
`Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(
|
|
1417
|
-
', '
|
|
1418
|
-
)}`
|
|
1419
|
-
);
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
// Check if we should forward this to a NetworkProxy
|
|
1423
|
-
if (
|
|
1424
|
-
isTlsHandshakeDetected &&
|
|
1425
|
-
domainConfig.useNetworkProxy === true &&
|
|
1426
|
-
initialChunk &&
|
|
1427
|
-
this.networkProxies.length > 0
|
|
1428
|
-
) {
|
|
1429
|
-
return this.forwardToNetworkProxy(
|
|
1430
|
-
connectionId,
|
|
1431
|
-
socket,
|
|
1432
|
-
connectionRecord,
|
|
1433
|
-
domainConfig,
|
|
1434
|
-
initialChunk,
|
|
1435
|
-
serverName
|
|
1436
|
-
);
|
|
1493
|
+
socket.on('error', this.handleError('incoming', connectionRecord));
|
|
1494
|
+
|
|
1495
|
+
// Track data for bytes counting
|
|
1496
|
+
socket.on('data', (chunk: Buffer) => {
|
|
1497
|
+
connectionRecord.bytesReceived += chunk.length;
|
|
1498
|
+
this.updateActivity(connectionRecord);
|
|
1499
|
+
|
|
1500
|
+
// Check for TLS handshake if this is the first chunk
|
|
1501
|
+
if (!connectionRecord.isTLS && isTlsHandshake(chunk)) {
|
|
1502
|
+
connectionRecord.isTLS = true;
|
|
1503
|
+
|
|
1504
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
1505
|
+
console.log(
|
|
1506
|
+
`[${connectionId}] TLS handshake detected from ${remoteIP}, ${chunk.length} bytes`
|
|
1507
|
+
);
|
|
1508
|
+
// Try to extract SNI and log detailed debug info
|
|
1509
|
+
extractSNI(chunk, true);
|
|
1510
|
+
}
|
|
1437
1511
|
}
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
/**
|
|
1515
|
+
* Sets up the connection to the target host.
|
|
1516
|
+
* @param serverName - The SNI hostname (unused when forcedDomain is provided).
|
|
1517
|
+
* @param initialChunk - Optional initial data chunk.
|
|
1518
|
+
* @param forcedDomain - If provided, overrides SNI/domain lookup (used for port-based routing).
|
|
1519
|
+
* @param overridePort - If provided, use this port for the outgoing connection.
|
|
1520
|
+
*/
|
|
1521
|
+
const setupConnection = (
|
|
1522
|
+
serverName: string,
|
|
1523
|
+
initialChunk?: Buffer,
|
|
1524
|
+
forcedDomain?: IDomainConfig,
|
|
1525
|
+
overridePort?: number
|
|
1526
|
+
) => {
|
|
1527
|
+
// Clear the initial timeout since we've received data
|
|
1528
|
+
if (initialTimeout) {
|
|
1529
|
+
clearTimeout(initialTimeout);
|
|
1530
|
+
initialTimeout = null;
|
|
1450
1531
|
}
|
|
1451
|
-
}
|
|
1452
1532
|
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
socket,
|
|
1457
|
-
connectionRecord,
|
|
1458
|
-
domainConfig,
|
|
1459
|
-
serverName,
|
|
1460
|
-
initialChunk,
|
|
1461
|
-
overridePort
|
|
1462
|
-
);
|
|
1463
|
-
};
|
|
1533
|
+
// Mark that we've received initial data
|
|
1534
|
+
initialDataReceived = true;
|
|
1535
|
+
connectionRecord.hasReceivedInitialData = true;
|
|
1464
1536
|
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)
|
|
1476
|
-
) {
|
|
1477
|
-
console.log(
|
|
1478
|
-
`[${connectionId}] Connection from ${remoteIP} rejected: IP ${remoteIP} not allowed in global default allowed list.`
|
|
1479
|
-
);
|
|
1480
|
-
socket.end();
|
|
1481
|
-
return;
|
|
1482
|
-
}
|
|
1483
|
-
if (this.settings.enableDetailedLogging) {
|
|
1484
|
-
console.log(
|
|
1485
|
-
`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.`
|
|
1486
|
-
);
|
|
1537
|
+
// Check if this looks like a TLS handshake
|
|
1538
|
+
const isTlsHandshakeDetected = initialChunk && isTlsHandshake(initialChunk);
|
|
1539
|
+
if (isTlsHandshakeDetected) {
|
|
1540
|
+
connectionRecord.isTLS = true;
|
|
1541
|
+
|
|
1542
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
1543
|
+
console.log(
|
|
1544
|
+
`[${connectionId}] TLS handshake detected in setup, ${initialChunk.length} bytes`
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1487
1547
|
}
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
const forcedDomain = this.settings.domainConfigs.find(
|
|
1504
|
-
(domain) =>
|
|
1505
|
-
domain.portRanges &&
|
|
1506
|
-
domain.portRanges.length > 0 &&
|
|
1507
|
-
isPortInRanges(localPort, domain.portRanges)
|
|
1508
|
-
);
|
|
1509
|
-
if (forcedDomain) {
|
|
1548
|
+
|
|
1549
|
+
// If a forcedDomain is provided (port-based routing), use it; otherwise, use SNI-based lookup.
|
|
1550
|
+
const domainConfig = forcedDomain
|
|
1551
|
+
? forcedDomain
|
|
1552
|
+
: serverName
|
|
1553
|
+
? this.settings.domainConfigs.find((config) =>
|
|
1554
|
+
config.domains.some((d) => plugins.minimatch(serverName, d))
|
|
1555
|
+
)
|
|
1556
|
+
: undefined;
|
|
1557
|
+
|
|
1558
|
+
// Save domain config in connection record
|
|
1559
|
+
connectionRecord.domainConfig = domainConfig;
|
|
1560
|
+
|
|
1561
|
+
// IP validation is skipped if allowedIPs is empty
|
|
1562
|
+
if (domainConfig) {
|
|
1510
1563
|
const effectiveAllowedIPs: string[] = [
|
|
1511
|
-
...
|
|
1564
|
+
...domainConfig.allowedIPs,
|
|
1512
1565
|
...(this.settings.defaultAllowedIPs || []),
|
|
1513
1566
|
];
|
|
1514
1567
|
const effectiveBlockedIPs: string[] = [
|
|
1515
|
-
...(
|
|
1568
|
+
...(domainConfig.blockedIPs || []),
|
|
1516
1569
|
...(this.settings.defaultBlockedIPs || []),
|
|
1517
1570
|
];
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1571
|
+
|
|
1572
|
+
// Skip IP validation if allowedIPs is empty
|
|
1573
|
+
if (
|
|
1574
|
+
domainConfig.allowedIPs.length > 0 &&
|
|
1575
|
+
!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)
|
|
1576
|
+
) {
|
|
1577
|
+
return rejectIncomingConnection(
|
|
1578
|
+
'rejected',
|
|
1579
|
+
`Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(
|
|
1521
1580
|
', '
|
|
1522
|
-
)}
|
|
1581
|
+
)}`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
} else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
|
|
1585
|
+
if (
|
|
1586
|
+
!isGlobIPAllowed(
|
|
1587
|
+
remoteIP,
|
|
1588
|
+
this.settings.defaultAllowedIPs,
|
|
1589
|
+
this.settings.defaultBlockedIPs || []
|
|
1590
|
+
)
|
|
1591
|
+
) {
|
|
1592
|
+
return rejectIncomingConnection(
|
|
1593
|
+
'rejected',
|
|
1594
|
+
`Connection rejected: IP ${remoteIP} not allowed by default allowed list`
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// Save the initial SNI
|
|
1600
|
+
if (serverName) {
|
|
1601
|
+
connectionRecord.lockedDomain = serverName;
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// Set up the direct connection
|
|
1605
|
+
return this.setupDirectConnection(
|
|
1606
|
+
connectionId,
|
|
1607
|
+
socket,
|
|
1608
|
+
connectionRecord,
|
|
1609
|
+
domainConfig,
|
|
1610
|
+
serverName,
|
|
1611
|
+
initialChunk,
|
|
1612
|
+
overridePort
|
|
1613
|
+
);
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
// --- PORT RANGE-BASED HANDLING ---
|
|
1617
|
+
// Only apply port-based rules if the incoming port is within one of the global port ranges.
|
|
1618
|
+
if (
|
|
1619
|
+
this.settings.globalPortRanges &&
|
|
1620
|
+
isPortInRanges(localPort, this.settings.globalPortRanges)
|
|
1621
|
+
) {
|
|
1622
|
+
if (this.settings.forwardAllGlobalRanges) {
|
|
1623
|
+
if (
|
|
1624
|
+
this.settings.defaultAllowedIPs &&
|
|
1625
|
+
this.settings.defaultAllowedIPs.length > 0 &&
|
|
1626
|
+
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)
|
|
1627
|
+
) {
|
|
1628
|
+
console.log(
|
|
1629
|
+
`[${connectionId}] Connection from ${remoteIP} rejected: IP ${remoteIP} not allowed in global default allowed list.`
|
|
1523
1630
|
);
|
|
1524
1631
|
socket.end();
|
|
1525
1632
|
return;
|
|
1526
1633
|
}
|
|
1527
1634
|
if (this.settings.enableDetailedLogging) {
|
|
1528
1635
|
console.log(
|
|
1529
|
-
`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort}
|
|
1530
|
-
', '
|
|
1531
|
-
)}.`
|
|
1636
|
+
`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort} forwarded to global target IP ${this.settings.targetIP}.`
|
|
1532
1637
|
);
|
|
1533
1638
|
}
|
|
1534
|
-
setupConnection(
|
|
1639
|
+
setupConnection(
|
|
1640
|
+
'',
|
|
1641
|
+
undefined,
|
|
1642
|
+
{
|
|
1643
|
+
domains: ['global'],
|
|
1644
|
+
allowedIPs: this.settings.defaultAllowedIPs || [],
|
|
1645
|
+
blockedIPs: this.settings.defaultBlockedIPs || [],
|
|
1646
|
+
targetIPs: [this.settings.targetIP!],
|
|
1647
|
+
portRanges: [],
|
|
1648
|
+
},
|
|
1649
|
+
localPort
|
|
1650
|
+
);
|
|
1535
1651
|
return;
|
|
1652
|
+
} else {
|
|
1653
|
+
// Attempt to find a matching forced domain config based on the local port.
|
|
1654
|
+
const forcedDomain = this.settings.domainConfigs.find(
|
|
1655
|
+
(domain) =>
|
|
1656
|
+
domain.portRanges &&
|
|
1657
|
+
domain.portRanges.length > 0 &&
|
|
1658
|
+
isPortInRanges(localPort, domain.portRanges)
|
|
1659
|
+
);
|
|
1660
|
+
if (forcedDomain) {
|
|
1661
|
+
const effectiveAllowedIPs: string[] = [
|
|
1662
|
+
...forcedDomain.allowedIPs,
|
|
1663
|
+
...(this.settings.defaultAllowedIPs || []),
|
|
1664
|
+
];
|
|
1665
|
+
const effectiveBlockedIPs: string[] = [
|
|
1666
|
+
...(forcedDomain.blockedIPs || []),
|
|
1667
|
+
...(this.settings.defaultBlockedIPs || []),
|
|
1668
|
+
];
|
|
1669
|
+
if (!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) {
|
|
1670
|
+
console.log(
|
|
1671
|
+
`[${connectionId}] Connection from ${remoteIP} rejected: IP not allowed for domain ${forcedDomain.domains.join(
|
|
1672
|
+
', '
|
|
1673
|
+
)} on port ${localPort}.`
|
|
1674
|
+
);
|
|
1675
|
+
socket.end();
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
if (this.settings.enableDetailedLogging) {
|
|
1679
|
+
console.log(
|
|
1680
|
+
`[${connectionId}] Port-based connection from ${remoteIP} on port ${localPort} matched domain ${forcedDomain.domains.join(
|
|
1681
|
+
', '
|
|
1682
|
+
)}.`
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
setupConnection('', undefined, forcedDomain, localPort);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
// Fall through to SNI/default handling if no forced domain config is found.
|
|
1536
1689
|
}
|
|
1537
|
-
// Fall through to SNI/default handling if no forced domain config is found.
|
|
1538
1690
|
}
|
|
1539
|
-
}
|
|
1540
1691
|
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1692
|
+
// --- FALLBACK: SNI-BASED HANDLING (or default when SNI is disabled) ---
|
|
1693
|
+
if (this.settings.sniEnabled) {
|
|
1694
|
+
initialDataReceived = false;
|
|
1544
1695
|
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1696
|
+
socket.once('data', (chunk: Buffer) => {
|
|
1697
|
+
if (initialTimeout) {
|
|
1698
|
+
clearTimeout(initialTimeout);
|
|
1699
|
+
initialTimeout = null;
|
|
1700
|
+
}
|
|
1550
1701
|
|
|
1551
|
-
|
|
1702
|
+
initialDataReceived = true;
|
|
1552
1703
|
|
|
1553
|
-
|
|
1554
|
-
|
|
1704
|
+
// Try to extract SNI
|
|
1705
|
+
let serverName = '';
|
|
1555
1706
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1707
|
+
if (isTlsHandshake(chunk)) {
|
|
1708
|
+
connectionRecord.isTLS = true;
|
|
1558
1709
|
|
|
1559
|
-
|
|
1710
|
+
if (this.settings.enableTlsDebugLogging) {
|
|
1711
|
+
console.log(
|
|
1712
|
+
`[${connectionId}] Extracting SNI from TLS handshake, ${chunk.length} bytes`
|
|
1713
|
+
);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
serverName = extractSNI(chunk, this.settings.enableTlsDebugLogging) || '';
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
// Lock the connection to the negotiated SNI.
|
|
1720
|
+
connectionRecord.lockedDomain = serverName;
|
|
1721
|
+
|
|
1722
|
+
if (this.settings.enableDetailedLogging) {
|
|
1560
1723
|
console.log(
|
|
1561
|
-
`[${connectionId}]
|
|
1724
|
+
`[${connectionId}] Received connection from ${remoteIP} with SNI: ${
|
|
1725
|
+
serverName || '(empty)'
|
|
1726
|
+
}`
|
|
1562
1727
|
);
|
|
1563
1728
|
}
|
|
1564
1729
|
|
|
1565
|
-
serverName
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
connectionRecord.
|
|
1730
|
+
setupConnection(serverName, chunk);
|
|
1731
|
+
});
|
|
1732
|
+
} else {
|
|
1733
|
+
initialDataReceived = true;
|
|
1734
|
+
connectionRecord.hasReceivedInitialData = true;
|
|
1570
1735
|
|
|
1571
|
-
if (
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1736
|
+
if (
|
|
1737
|
+
this.settings.defaultAllowedIPs &&
|
|
1738
|
+
this.settings.defaultAllowedIPs.length > 0 &&
|
|
1739
|
+
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)
|
|
1740
|
+
) {
|
|
1741
|
+
return rejectIncomingConnection(
|
|
1742
|
+
'rejected',
|
|
1743
|
+
`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`
|
|
1576
1744
|
);
|
|
1577
1745
|
}
|
|
1578
1746
|
|
|
1579
|
-
setupConnection(
|
|
1580
|
-
});
|
|
1581
|
-
} else {
|
|
1582
|
-
initialDataReceived = true;
|
|
1583
|
-
connectionRecord.hasReceivedInitialData = true;
|
|
1584
|
-
|
|
1585
|
-
if (
|
|
1586
|
-
this.settings.defaultAllowedIPs &&
|
|
1587
|
-
this.settings.defaultAllowedIPs.length > 0 &&
|
|
1588
|
-
!isAllowed(remoteIP, this.settings.defaultAllowedIPs)
|
|
1589
|
-
) {
|
|
1590
|
-
return rejectIncomingConnection(
|
|
1591
|
-
'rejected',
|
|
1592
|
-
`Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`
|
|
1593
|
-
);
|
|
1747
|
+
setupConnection('');
|
|
1594
1748
|
}
|
|
1595
|
-
|
|
1596
|
-
setupConnection('');
|
|
1597
1749
|
}
|
|
1598
1750
|
};
|
|
1599
1751
|
|
|
@@ -1619,10 +1771,11 @@ export class PortProxy {
|
|
|
1619
1771
|
console.log(`Server Error on port ${port}: ${err.message}`);
|
|
1620
1772
|
});
|
|
1621
1773
|
server.listen(port, () => {
|
|
1774
|
+
const isNetworkProxyPort = this.settings.useNetworkProxy?.includes(port);
|
|
1622
1775
|
console.log(
|
|
1623
1776
|
`PortProxy -> OK: Now listening on port ${port}${
|
|
1624
|
-
this.settings.sniEnabled ? ' (SNI passthrough enabled)' : ''
|
|
1625
|
-
}${
|
|
1777
|
+
this.settings.sniEnabled && !isNetworkProxyPort ? ' (SNI passthrough enabled)' : ''
|
|
1778
|
+
}${isNetworkProxyPort ? ' (NetworkProxy forwarding enabled)' : ''}`
|
|
1626
1779
|
);
|
|
1627
1780
|
});
|
|
1628
1781
|
this.netServers.push(server);
|
|
@@ -1642,6 +1795,7 @@ export class PortProxy {
|
|
|
1642
1795
|
let pendingTlsHandshakes = 0;
|
|
1643
1796
|
let keepAliveConnections = 0;
|
|
1644
1797
|
let networkProxyConnections = 0;
|
|
1798
|
+
let domainSwitchedConnections = 0;
|
|
1645
1799
|
|
|
1646
1800
|
// Create a copy of the keys to avoid modification during iteration
|
|
1647
1801
|
const connectionIds = [...this.connectionRecords.keys()];
|
|
@@ -1661,20 +1815,23 @@ export class PortProxy {
|
|
|
1661
1815
|
} else {
|
|
1662
1816
|
nonTlsConnections++;
|
|
1663
1817
|
}
|
|
1664
|
-
|
|
1818
|
+
|
|
1665
1819
|
if (record.hasKeepAlive) {
|
|
1666
1820
|
keepAliveConnections++;
|
|
1667
1821
|
}
|
|
1668
|
-
|
|
1822
|
+
|
|
1669
1823
|
if (record.usingNetworkProxy) {
|
|
1670
1824
|
networkProxyConnections++;
|
|
1671
1825
|
}
|
|
1672
1826
|
|
|
1827
|
+
if (record.domainSwitches && record.domainSwitches > 0) {
|
|
1828
|
+
domainSwitchedConnections++;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1673
1831
|
maxIncoming = Math.max(maxIncoming, now - record.incomingStartTime);
|
|
1674
1832
|
if (record.outgoingStartTime) {
|
|
1675
1833
|
maxOutgoing = Math.max(maxOutgoing, now - record.outgoingStartTime);
|
|
1676
1834
|
}
|
|
1677
|
-
|
|
1678
1835
|
// Parity check: if outgoing socket closed and incoming remains active
|
|
1679
1836
|
if (
|
|
1680
1837
|
record.outgoingClosedTime &&
|
|
@@ -1706,35 +1863,38 @@ export class PortProxy {
|
|
|
1706
1863
|
}
|
|
1707
1864
|
|
|
1708
1865
|
// Skip inactivity check if disabled or for immortal keep-alive connections
|
|
1709
|
-
if (
|
|
1710
|
-
|
|
1711
|
-
|
|
1866
|
+
if (
|
|
1867
|
+
!this.settings.disableInactivityCheck &&
|
|
1868
|
+
!(record.hasKeepAlive && this.settings.keepAliveTreatment === 'immortal')
|
|
1869
|
+
) {
|
|
1712
1870
|
const inactivityTime = now - record.lastActivity;
|
|
1713
|
-
|
|
1871
|
+
|
|
1714
1872
|
// Use extended timeout for extended-treatment keep-alive connections
|
|
1715
1873
|
let effectiveTimeout = this.settings.inactivityTimeout!;
|
|
1716
1874
|
if (record.hasKeepAlive && this.settings.keepAliveTreatment === 'extended') {
|
|
1717
1875
|
const multiplier = this.settings.keepAliveInactivityMultiplier || 6;
|
|
1718
1876
|
effectiveTimeout = effectiveTimeout * multiplier;
|
|
1719
1877
|
}
|
|
1720
|
-
|
|
1878
|
+
|
|
1721
1879
|
if (inactivityTime > effectiveTimeout && !record.connectionClosed) {
|
|
1722
1880
|
// For keep-alive connections, issue a warning first
|
|
1723
1881
|
if (record.hasKeepAlive && !record.inactivityWarningIssued) {
|
|
1724
1882
|
console.log(
|
|
1725
|
-
`[${id}] Warning: Keep-alive connection from ${
|
|
1726
|
-
|
|
1883
|
+
`[${id}] Warning: Keep-alive connection from ${
|
|
1884
|
+
record.remoteIP
|
|
1885
|
+
} inactive for ${plugins.prettyMs(inactivityTime)}. ` +
|
|
1886
|
+
`Will close in 10 minutes if no activity.`
|
|
1727
1887
|
);
|
|
1728
|
-
|
|
1888
|
+
|
|
1729
1889
|
// Set warning flag and add grace period
|
|
1730
1890
|
record.inactivityWarningIssued = true;
|
|
1731
1891
|
record.lastActivity = now - (effectiveTimeout - 600000);
|
|
1732
|
-
|
|
1892
|
+
|
|
1733
1893
|
// Try to stimulate activity with a probe packet
|
|
1734
1894
|
if (record.outgoing && !record.outgoing.destroyed) {
|
|
1735
1895
|
try {
|
|
1736
1896
|
record.outgoing.write(Buffer.alloc(0));
|
|
1737
|
-
|
|
1897
|
+
|
|
1738
1898
|
if (this.settings.enableDetailedLogging) {
|
|
1739
1899
|
console.log(`[${id}] Sent probe packet to test keep-alive connection`);
|
|
1740
1900
|
}
|
|
@@ -1746,15 +1906,17 @@ export class PortProxy {
|
|
|
1746
1906
|
// For non-keep-alive or after warning, close the connection
|
|
1747
1907
|
console.log(
|
|
1748
1908
|
`[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` +
|
|
1749
|
-
|
|
1750
|
-
|
|
1909
|
+
`for ${plugins.prettyMs(inactivityTime)}.` +
|
|
1910
|
+
(record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '')
|
|
1751
1911
|
);
|
|
1752
1912
|
this.cleanupConnection(record, 'inactivity');
|
|
1753
1913
|
}
|
|
1754
1914
|
} else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) {
|
|
1755
1915
|
// If activity detected after warning, clear the warning
|
|
1756
1916
|
if (this.settings.enableDetailedLogging) {
|
|
1757
|
-
console.log(
|
|
1917
|
+
console.log(
|
|
1918
|
+
`[${id}] Connection activity detected after inactivity warning, resetting warning`
|
|
1919
|
+
);
|
|
1758
1920
|
}
|
|
1759
1921
|
record.inactivityWarningIssued = false;
|
|
1760
1922
|
}
|
|
@@ -1765,7 +1927,8 @@ export class PortProxy {
|
|
|
1765
1927
|
console.log(
|
|
1766
1928
|
`Active connections: ${this.connectionRecords.size}. ` +
|
|
1767
1929
|
`Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` +
|
|
1768
|
-
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}
|
|
1930
|
+
`Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, NetworkProxy=${networkProxyConnections}, ` +
|
|
1931
|
+
`DomainSwitched=${domainSwitchedConnections}. ` +
|
|
1769
1932
|
`Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(
|
|
1770
1933
|
maxOutgoing
|
|
1771
1934
|
)}. ` +
|
|
@@ -1782,21 +1945,6 @@ export class PortProxy {
|
|
|
1782
1945
|
}
|
|
1783
1946
|
}
|
|
1784
1947
|
|
|
1785
|
-
/**
|
|
1786
|
-
* Add or replace NetworkProxy instances
|
|
1787
|
-
*/
|
|
1788
|
-
public setNetworkProxies(networkProxies: NetworkProxy[]): void {
|
|
1789
|
-
this.networkProxies = networkProxies;
|
|
1790
|
-
console.log(`Updated NetworkProxy instances: ${this.networkProxies.length} proxies configured`);
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
/**
|
|
1794
|
-
* Get a list of configured NetworkProxy instances
|
|
1795
|
-
*/
|
|
1796
|
-
public getNetworkProxies(): NetworkProxy[] {
|
|
1797
|
-
return this.networkProxies;
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
1948
|
/**
|
|
1801
1949
|
* Gracefully shut down the proxy
|
|
1802
1950
|
*/
|
|
@@ -1888,6 +2036,16 @@ export class PortProxy {
|
|
|
1888
2036
|
}
|
|
1889
2037
|
}
|
|
1890
2038
|
|
|
2039
|
+
// Stop NetworkProxy if it was started
|
|
2040
|
+
if (this.networkProxy) {
|
|
2041
|
+
try {
|
|
2042
|
+
await this.networkProxy.stop();
|
|
2043
|
+
console.log('NetworkProxy stopped successfully');
|
|
2044
|
+
} catch (err) {
|
|
2045
|
+
console.log(`Error stopping NetworkProxy: ${err}`);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1891
2049
|
// Clear all tracking maps
|
|
1892
2050
|
this.connectionRecords.clear();
|
|
1893
2051
|
this.domainTargetIndices.clear();
|