@push.rocks/smartproxy 3.41.6 → 3.41.8
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.js +112 -84
- package/dist_ts/classes.snihandler.js +58 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +314 -231
- package/ts/classes.snihandler.ts +69 -0
package/ts/classes.portproxy.ts
CHANGED
|
@@ -11,7 +11,7 @@ export interface IDomainConfig {
|
|
|
11
11
|
portRanges?: Array<{ from: number; to: number }>; // Optional port ranges
|
|
12
12
|
// Allow domain-specific timeout override
|
|
13
13
|
connectionTimeout?: number; // Connection timeout override (ms)
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
// NetworkProxy integration options for this specific domain
|
|
16
16
|
useNetworkProxy?: boolean; // Whether to use NetworkProxy for this domain
|
|
17
17
|
networkProxyPort?: number; // Override default NetworkProxy port for this domain
|
|
@@ -65,17 +65,17 @@ export interface IPortProxySettings extends plugins.tls.TlsOptions {
|
|
|
65
65
|
// NetworkProxy integration
|
|
66
66
|
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
|
67
67
|
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
// ACME certificate management options
|
|
70
70
|
acme?: {
|
|
71
|
-
enabled?: boolean;
|
|
72
|
-
port?: number;
|
|
73
|
-
contactEmail?: string;
|
|
74
|
-
useProduction?: boolean;
|
|
75
|
-
renewThresholdDays?: number;
|
|
76
|
-
autoRenew?: boolean;
|
|
77
|
-
certificateStore?: string;
|
|
78
|
-
skipConfiguredCerts?: boolean;
|
|
71
|
+
enabled?: boolean; // Whether to enable automatic certificate management
|
|
72
|
+
port?: number; // Port to listen on for ACME challenges (default: 80)
|
|
73
|
+
contactEmail?: string; // Email for Let's Encrypt account
|
|
74
|
+
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
|
|
75
|
+
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
|
|
76
|
+
autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
|
|
77
|
+
certificateStore?: string; // Directory to store certificates (default: ./certs)
|
|
78
|
+
skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -216,7 +216,7 @@ export class PortProxy {
|
|
|
216
216
|
targetIP: settingsArg.targetIP || 'localhost',
|
|
217
217
|
|
|
218
218
|
// Timeout settings with reasonable defaults
|
|
219
|
-
initialDataTimeout: settingsArg.initialDataTimeout ||
|
|
219
|
+
initialDataTimeout: settingsArg.initialDataTimeout || 120000, // 120 seconds for initial handshake
|
|
220
220
|
socketTimeout: ensureSafeTimeout(settingsArg.socketTimeout || 3600000), // 1 hour socket timeout
|
|
221
221
|
inactivityCheckInterval: settingsArg.inactivityCheckInterval || 60000, // 60 seconds interval
|
|
222
222
|
maxConnectionLifetime: ensureSafeTimeout(settingsArg.maxConnectionLifetime || 86400000), // 24 hours default
|
|
@@ -232,13 +232,13 @@ export class PortProxy {
|
|
|
232
232
|
|
|
233
233
|
// Feature flags
|
|
234
234
|
disableInactivityCheck: settingsArg.disableInactivityCheck || false,
|
|
235
|
-
enableKeepAliveProbes:
|
|
236
|
-
|
|
235
|
+
enableKeepAliveProbes:
|
|
236
|
+
settingsArg.enableKeepAliveProbes !== undefined ? settingsArg.enableKeepAliveProbes : true,
|
|
237
237
|
enableDetailedLogging: settingsArg.enableDetailedLogging || false,
|
|
238
238
|
enableTlsDebugLogging: settingsArg.enableTlsDebugLogging || false,
|
|
239
239
|
enableRandomizedTimeouts: settingsArg.enableRandomizedTimeouts || false,
|
|
240
|
-
allowSessionTicket:
|
|
241
|
-
|
|
240
|
+
allowSessionTicket:
|
|
241
|
+
settingsArg.allowSessionTicket !== undefined ? settingsArg.allowSessionTicket : true,
|
|
242
242
|
|
|
243
243
|
// Rate limiting defaults
|
|
244
244
|
maxConnectionsPerIP: settingsArg.maxConnectionsPerIP || 100,
|
|
@@ -248,10 +248,10 @@ export class PortProxy {
|
|
|
248
248
|
keepAliveTreatment: settingsArg.keepAliveTreatment || 'extended',
|
|
249
249
|
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
|
250
250
|
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
// NetworkProxy settings
|
|
253
253
|
networkProxyPort: settingsArg.networkProxyPort || 8443, // Default NetworkProxy port
|
|
254
|
-
|
|
254
|
+
|
|
255
255
|
// ACME certificate settings with reasonable defaults
|
|
256
256
|
acme: settingsArg.acme || {
|
|
257
257
|
enabled: false,
|
|
@@ -261,8 +261,8 @@ export class PortProxy {
|
|
|
261
261
|
renewThresholdDays: 30,
|
|
262
262
|
autoRenew: true,
|
|
263
263
|
certificateStore: './certs',
|
|
264
|
-
skipConfiguredCerts: false
|
|
265
|
-
}
|
|
264
|
+
skipConfiguredCerts: false,
|
|
265
|
+
},
|
|
266
266
|
};
|
|
267
267
|
|
|
268
268
|
// Initialize NetworkProxy if enabled
|
|
@@ -280,23 +280,23 @@ export class PortProxy {
|
|
|
280
280
|
const networkProxyOptions: any = {
|
|
281
281
|
port: this.settings.networkProxyPort!,
|
|
282
282
|
portProxyIntegration: true,
|
|
283
|
-
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info'
|
|
283
|
+
logLevel: this.settings.enableDetailedLogging ? 'debug' : 'info',
|
|
284
284
|
};
|
|
285
|
-
|
|
285
|
+
|
|
286
286
|
// Add ACME settings if configured
|
|
287
287
|
if (this.settings.acme) {
|
|
288
288
|
networkProxyOptions.acme = { ...this.settings.acme };
|
|
289
289
|
}
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
this.networkProxy = new NetworkProxy(networkProxyOptions);
|
|
292
|
-
|
|
292
|
+
|
|
293
293
|
console.log(`Initialized NetworkProxy on port ${this.settings.networkProxyPort}`);
|
|
294
|
-
|
|
294
|
+
|
|
295
295
|
// Convert and apply domain configurations to NetworkProxy
|
|
296
296
|
await this.syncDomainConfigsToNetworkProxy();
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
|
-
|
|
299
|
+
|
|
300
300
|
/**
|
|
301
301
|
* Updates the domain configurations for the proxy
|
|
302
302
|
* @param newDomainConfigs The new domain configurations
|
|
@@ -304,47 +304,47 @@ export class PortProxy {
|
|
|
304
304
|
public async updateDomainConfigs(newDomainConfigs: IDomainConfig[]): Promise<void> {
|
|
305
305
|
console.log(`Updating domain configurations (${newDomainConfigs.length} configs)`);
|
|
306
306
|
this.settings.domainConfigs = newDomainConfigs;
|
|
307
|
-
|
|
307
|
+
|
|
308
308
|
// If NetworkProxy is initialized, resync the configurations
|
|
309
309
|
if (this.networkProxy) {
|
|
310
310
|
await this.syncDomainConfigsToNetworkProxy();
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
-
|
|
313
|
+
|
|
314
314
|
/**
|
|
315
315
|
* Updates the ACME certificate settings
|
|
316
316
|
* @param acmeSettings New ACME settings
|
|
317
317
|
*/
|
|
318
318
|
public async updateAcmeSettings(acmeSettings: IPortProxySettings['acme']): Promise<void> {
|
|
319
319
|
console.log('Updating ACME certificate settings');
|
|
320
|
-
|
|
320
|
+
|
|
321
321
|
// Update settings
|
|
322
322
|
this.settings.acme = {
|
|
323
323
|
...this.settings.acme,
|
|
324
|
-
...acmeSettings
|
|
324
|
+
...acmeSettings,
|
|
325
325
|
};
|
|
326
|
-
|
|
326
|
+
|
|
327
327
|
// If NetworkProxy is initialized, update its ACME settings
|
|
328
328
|
if (this.networkProxy) {
|
|
329
329
|
try {
|
|
330
330
|
// Recreate NetworkProxy with new settings if ACME enabled state has changed
|
|
331
331
|
if (this.settings.acme.enabled !== acmeSettings.enabled) {
|
|
332
332
|
console.log(`ACME enabled state changed to: ${acmeSettings.enabled}`);
|
|
333
|
-
|
|
333
|
+
|
|
334
334
|
// Stop the current NetworkProxy
|
|
335
335
|
await this.networkProxy.stop();
|
|
336
336
|
this.networkProxy = null;
|
|
337
|
-
|
|
337
|
+
|
|
338
338
|
// Reinitialize with new settings
|
|
339
339
|
await this.initializeNetworkProxy();
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
// Use start() to make sure ACME gets initialized if newly enabled
|
|
342
342
|
await this.networkProxy.start();
|
|
343
343
|
} else {
|
|
344
344
|
// Update existing NetworkProxy with new settings
|
|
345
345
|
// Note: Some settings may require a restart to take effect
|
|
346
346
|
console.log('Updating ACME settings in NetworkProxy');
|
|
347
|
-
|
|
347
|
+
|
|
348
348
|
// For certificate renewals, we might want to trigger checks with the new settings
|
|
349
349
|
if (acmeSettings.renewThresholdDays) {
|
|
350
350
|
console.log(`Setting new renewal threshold to ${acmeSettings.renewThresholdDays} days`);
|
|
@@ -359,7 +359,7 @@ export class PortProxy {
|
|
|
359
359
|
}
|
|
360
360
|
}
|
|
361
361
|
}
|
|
362
|
-
|
|
362
|
+
|
|
363
363
|
/**
|
|
364
364
|
* Synchronizes PortProxy domain configurations to NetworkProxy
|
|
365
365
|
* This allows domains configured in PortProxy to be used by NetworkProxy
|
|
@@ -369,60 +369,67 @@ export class PortProxy {
|
|
|
369
369
|
console.log('Cannot sync configurations - NetworkProxy not initialized');
|
|
370
370
|
return;
|
|
371
371
|
}
|
|
372
|
-
|
|
372
|
+
|
|
373
373
|
try {
|
|
374
374
|
// Get SSL certificates from assets
|
|
375
375
|
// Import fs directly since it's not in plugins
|
|
376
376
|
const fs = await import('fs');
|
|
377
|
-
|
|
377
|
+
|
|
378
378
|
let certPair;
|
|
379
379
|
try {
|
|
380
380
|
certPair = {
|
|
381
381
|
key: fs.readFileSync('assets/certs/key.pem', 'utf8'),
|
|
382
|
-
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8')
|
|
382
|
+
cert: fs.readFileSync('assets/certs/cert.pem', 'utf8'),
|
|
383
383
|
};
|
|
384
384
|
} catch (certError) {
|
|
385
385
|
console.log(`Warning: Could not read default certificates: ${certError}`);
|
|
386
|
-
console.log(
|
|
387
|
-
|
|
386
|
+
console.log(
|
|
387
|
+
'Using empty certificate placeholders - ACME will generate proper certificates if enabled'
|
|
388
|
+
);
|
|
389
|
+
|
|
388
390
|
// Use empty placeholders - NetworkProxy will use its internal defaults
|
|
389
391
|
// or ACME will generate proper ones if enabled
|
|
390
392
|
certPair = {
|
|
391
393
|
key: '',
|
|
392
|
-
cert: ''
|
|
394
|
+
cert: '',
|
|
393
395
|
};
|
|
394
396
|
}
|
|
395
|
-
|
|
397
|
+
|
|
396
398
|
// Convert domain configs to NetworkProxy configs
|
|
397
399
|
const proxyConfigs = this.networkProxy.convertPortProxyConfigs(
|
|
398
400
|
this.settings.domainConfigs,
|
|
399
401
|
certPair
|
|
400
402
|
);
|
|
401
|
-
|
|
403
|
+
|
|
402
404
|
// Log ACME-eligible domains if ACME is enabled
|
|
403
405
|
if (this.settings.acme?.enabled) {
|
|
404
406
|
const acmeEligibleDomains = proxyConfigs
|
|
405
|
-
.filter(config => !config.hostName.includes('*')) // Exclude wildcards
|
|
406
|
-
.map(config => config.hostName);
|
|
407
|
-
|
|
407
|
+
.filter((config) => !config.hostName.includes('*')) // Exclude wildcards
|
|
408
|
+
.map((config) => config.hostName);
|
|
409
|
+
|
|
408
410
|
if (acmeEligibleDomains.length > 0) {
|
|
409
411
|
console.log(`Domains eligible for ACME certificates: ${acmeEligibleDomains.join(', ')}`);
|
|
410
412
|
} else {
|
|
411
413
|
console.log('No domains eligible for ACME certificates found in configuration');
|
|
412
414
|
}
|
|
413
415
|
}
|
|
414
|
-
|
|
416
|
+
|
|
415
417
|
// Update NetworkProxy with the converted configs
|
|
416
|
-
this.networkProxy
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
418
|
+
this.networkProxy
|
|
419
|
+
.updateProxyConfigs(proxyConfigs)
|
|
420
|
+
.then(() => {
|
|
421
|
+
console.log(
|
|
422
|
+
`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`
|
|
423
|
+
);
|
|
424
|
+
})
|
|
425
|
+
.catch((err) => {
|
|
426
|
+
console.log(`Error synchronizing configurations: ${err.message}`);
|
|
427
|
+
});
|
|
421
428
|
} catch (err) {
|
|
422
429
|
console.log(`Failed to sync configurations: ${err}`);
|
|
423
430
|
}
|
|
424
431
|
}
|
|
425
|
-
|
|
432
|
+
|
|
426
433
|
/**
|
|
427
434
|
* Requests a certificate for a specific domain
|
|
428
435
|
* @param domain The domain to request a certificate for
|
|
@@ -433,12 +440,12 @@ export class PortProxy {
|
|
|
433
440
|
console.log('Cannot request certificate - NetworkProxy not initialized');
|
|
434
441
|
return false;
|
|
435
442
|
}
|
|
436
|
-
|
|
443
|
+
|
|
437
444
|
if (!this.settings.acme?.enabled) {
|
|
438
445
|
console.log('Cannot request certificate - ACME is not enabled');
|
|
439
446
|
return false;
|
|
440
447
|
}
|
|
441
|
-
|
|
448
|
+
|
|
442
449
|
try {
|
|
443
450
|
const result = await this.networkProxy.requestCertificate(domain);
|
|
444
451
|
if (result) {
|
|
@@ -546,9 +553,7 @@ export class PortProxy {
|
|
|
546
553
|
proxySocket.on('data', () => this.updateActivity(record));
|
|
547
554
|
|
|
548
555
|
if (this.settings.enableDetailedLogging) {
|
|
549
|
-
console.log(
|
|
550
|
-
`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`
|
|
551
|
-
);
|
|
556
|
+
console.log(`[${connectionId}] TLS connection successfully forwarded to NetworkProxy`);
|
|
552
557
|
}
|
|
553
558
|
});
|
|
554
559
|
}
|
|
@@ -582,11 +587,11 @@ export class PortProxy {
|
|
|
582
587
|
let queueSize = 0;
|
|
583
588
|
let processingQueue = false;
|
|
584
589
|
let drainPending = false;
|
|
585
|
-
|
|
590
|
+
|
|
586
591
|
// Flag to track if we've switched to the final piping mechanism
|
|
587
592
|
// Once this is true, we no longer buffer data in dataQueue
|
|
588
593
|
let pipingEstablished = false;
|
|
589
|
-
|
|
594
|
+
|
|
590
595
|
// Pause the incoming socket to prevent buffer overflows
|
|
591
596
|
// This ensures we control the flow of data until piping is set up
|
|
592
597
|
socket.pause();
|
|
@@ -594,22 +599,22 @@ export class PortProxy {
|
|
|
594
599
|
// Function to safely process the data queue without losing events
|
|
595
600
|
const processDataQueue = () => {
|
|
596
601
|
if (processingQueue || dataQueue.length === 0 || pipingEstablished) return;
|
|
597
|
-
|
|
602
|
+
|
|
598
603
|
processingQueue = true;
|
|
599
|
-
|
|
604
|
+
|
|
600
605
|
try {
|
|
601
606
|
// Process all queued chunks with the current active handler
|
|
602
607
|
while (dataQueue.length > 0) {
|
|
603
608
|
const chunk = dataQueue.shift()!;
|
|
604
609
|
queueSize -= chunk.length;
|
|
605
|
-
|
|
610
|
+
|
|
606
611
|
// Once piping is established, we shouldn't get here,
|
|
607
612
|
// but just in case, pass to the outgoing socket directly
|
|
608
613
|
if (pipingEstablished && record.outgoing) {
|
|
609
614
|
record.outgoing.write(chunk);
|
|
610
615
|
continue;
|
|
611
616
|
}
|
|
612
|
-
|
|
617
|
+
|
|
613
618
|
// Track bytes received
|
|
614
619
|
record.bytesReceived += chunk.length;
|
|
615
620
|
|
|
@@ -643,7 +648,7 @@ export class PortProxy {
|
|
|
643
648
|
}
|
|
644
649
|
} finally {
|
|
645
650
|
processingQueue = false;
|
|
646
|
-
|
|
651
|
+
|
|
647
652
|
// If there's a pending drain and we've processed everything,
|
|
648
653
|
// signal we're ready for more data if we haven't established piping yet
|
|
649
654
|
if (drainPending && dataQueue.length === 0 && !pipingEstablished) {
|
|
@@ -657,17 +662,17 @@ export class PortProxy {
|
|
|
657
662
|
const safeDataHandler = (chunk: Buffer) => {
|
|
658
663
|
// If piping is already established, just let the pipe handle it
|
|
659
664
|
if (pipingEstablished) return;
|
|
660
|
-
|
|
665
|
+
|
|
661
666
|
// Add to our queue for orderly processing
|
|
662
667
|
dataQueue.push(Buffer.from(chunk)); // Make a copy to be safe
|
|
663
668
|
queueSize += chunk.length;
|
|
664
|
-
|
|
669
|
+
|
|
665
670
|
// If queue is getting large, pause socket until we catch up
|
|
666
671
|
if (this.settings.maxPendingDataSize && queueSize > this.settings.maxPendingDataSize * 0.8) {
|
|
667
672
|
socket.pause();
|
|
668
673
|
drainPending = true;
|
|
669
674
|
}
|
|
670
|
-
|
|
675
|
+
|
|
671
676
|
// Process the queue
|
|
672
677
|
processDataQueue();
|
|
673
678
|
};
|
|
@@ -849,77 +854,75 @@ export class PortProxy {
|
|
|
849
854
|
// Process any remaining data in the queue before switching to piping
|
|
850
855
|
processDataQueue();
|
|
851
856
|
|
|
852
|
-
//
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
pipingEstablished = true;
|
|
856
|
-
|
|
857
|
-
// Setup piping in both directions
|
|
858
|
-
socket.pipe(targetSocket);
|
|
859
|
-
targetSocket.pipe(socket);
|
|
860
|
-
|
|
861
|
-
// Resume the socket to ensure data flows
|
|
862
|
-
socket.resume();
|
|
863
|
-
|
|
864
|
-
// Process any data that might be queued in the interim
|
|
865
|
-
if (dataQueue.length > 0) {
|
|
866
|
-
// Write any remaining queued data directly to the target socket
|
|
867
|
-
for (const chunk of dataQueue) {
|
|
868
|
-
targetSocket.write(chunk);
|
|
869
|
-
}
|
|
870
|
-
// Clear the queue
|
|
871
|
-
dataQueue.length = 0;
|
|
872
|
-
queueSize = 0;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
if (this.settings.enableDetailedLogging) {
|
|
876
|
-
console.log(
|
|
877
|
-
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
878
|
-
`${
|
|
879
|
-
serverName
|
|
880
|
-
? ` (SNI: ${serverName})`
|
|
881
|
-
: domainConfig
|
|
882
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
883
|
-
: ''
|
|
884
|
-
}` +
|
|
885
|
-
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
886
|
-
record.hasKeepAlive ? 'Yes' : 'No'
|
|
887
|
-
}`
|
|
888
|
-
);
|
|
889
|
-
} else {
|
|
890
|
-
console.log(
|
|
891
|
-
`Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
892
|
-
`${
|
|
893
|
-
serverName
|
|
894
|
-
? ` (SNI: ${serverName})`
|
|
895
|
-
: domainConfig
|
|
896
|
-
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
897
|
-
: ''
|
|
898
|
-
}`
|
|
899
|
-
);
|
|
900
|
-
}
|
|
901
|
-
};
|
|
902
|
-
|
|
857
|
+
// Set up piping immediately - don't delay this crucial step
|
|
858
|
+
pipingEstablished = true;
|
|
859
|
+
|
|
903
860
|
// Flush all pending data to target
|
|
904
861
|
if (record.pendingData.length > 0) {
|
|
905
862
|
const combinedData = Buffer.concat(record.pendingData);
|
|
863
|
+
|
|
864
|
+
if (this.settings.enableDetailedLogging) {
|
|
865
|
+
console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Write pending data immediately
|
|
906
869
|
targetSocket.write(combinedData, (err) => {
|
|
907
870
|
if (err) {
|
|
908
871
|
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
|
|
909
872
|
return this.initiateCleanupOnce(record, 'write_error');
|
|
910
873
|
}
|
|
911
|
-
|
|
912
|
-
// Establish piping now that we've flushed the buffered data
|
|
913
|
-
setupPiping();
|
|
914
874
|
});
|
|
875
|
+
|
|
876
|
+
// Clear the buffer now that we've processed it
|
|
877
|
+
record.pendingData = [];
|
|
878
|
+
record.pendingDataSize = 0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Setup piping in both directions without any delays
|
|
882
|
+
socket.pipe(targetSocket);
|
|
883
|
+
targetSocket.pipe(socket);
|
|
884
|
+
|
|
885
|
+
// Resume the socket to ensure data flows - CRITICAL!
|
|
886
|
+
socket.resume();
|
|
887
|
+
|
|
888
|
+
// Process any data that might be queued in the interim
|
|
889
|
+
if (dataQueue.length > 0) {
|
|
890
|
+
// Write any remaining queued data directly to the target socket
|
|
891
|
+
for (const chunk of dataQueue) {
|
|
892
|
+
targetSocket.write(chunk);
|
|
893
|
+
}
|
|
894
|
+
// Clear the queue
|
|
895
|
+
dataQueue.length = 0;
|
|
896
|
+
queueSize = 0;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
if (this.settings.enableDetailedLogging) {
|
|
900
|
+
console.log(
|
|
901
|
+
`[${connectionId}] Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
902
|
+
`${
|
|
903
|
+
serverName
|
|
904
|
+
? ` (SNI: ${serverName})`
|
|
905
|
+
: domainConfig
|
|
906
|
+
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
907
|
+
: ''
|
|
908
|
+
}` +
|
|
909
|
+
` TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${
|
|
910
|
+
record.hasKeepAlive ? 'Yes' : 'No'
|
|
911
|
+
}`
|
|
912
|
+
);
|
|
915
913
|
} else {
|
|
916
|
-
|
|
917
|
-
|
|
914
|
+
console.log(
|
|
915
|
+
`Connection established: ${record.remoteIP} -> ${targetHost}:${connectionOptions.port}` +
|
|
916
|
+
`${
|
|
917
|
+
serverName
|
|
918
|
+
? ` (SNI: ${serverName})`
|
|
919
|
+
: domainConfig
|
|
920
|
+
? ` (Port-based for domain: ${domainConfig.domains.join(', ')})`
|
|
921
|
+
: ''
|
|
922
|
+
}`
|
|
923
|
+
);
|
|
918
924
|
}
|
|
919
925
|
|
|
920
|
-
// Clear the buffer now that we've processed it
|
|
921
|
-
record.pendingData = [];
|
|
922
|
-
record.pendingDataSize = 0;
|
|
923
926
|
|
|
924
927
|
// Add the renegotiation handler for SNI validation with strict domain enforcement
|
|
925
928
|
// This will be called after we've established piping
|
|
@@ -935,30 +938,36 @@ export class PortProxy {
|
|
|
935
938
|
sourceIp: record.remoteIP,
|
|
936
939
|
sourcePort: record.incoming.remotePort || 0,
|
|
937
940
|
destIp: record.incoming.localAddress || '',
|
|
938
|
-
destPort: record.incoming.localPort || 0
|
|
941
|
+
destPort: record.incoming.localPort || 0,
|
|
939
942
|
};
|
|
940
|
-
|
|
943
|
+
|
|
941
944
|
// Check for session tickets if allowSessionTicket is disabled
|
|
942
945
|
if (this.settings.allowSessionTicket === false) {
|
|
943
946
|
// Analyze for session resumption attempt (session ticket or PSK)
|
|
944
|
-
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
945
|
-
|
|
947
|
+
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
948
|
+
renegChunk,
|
|
949
|
+
this.settings.enableTlsDebugLogging
|
|
950
|
+
);
|
|
951
|
+
|
|
946
952
|
if (resumptionInfo.isResumption) {
|
|
947
953
|
// Always log resumption attempt for easier debugging
|
|
948
954
|
// Try to extract SNI for logging
|
|
949
|
-
const extractedSNI = SniHandler.extractSNI(
|
|
955
|
+
const extractedSNI = SniHandler.extractSNI(
|
|
956
|
+
renegChunk,
|
|
957
|
+
this.settings.enableTlsDebugLogging
|
|
958
|
+
);
|
|
950
959
|
console.log(
|
|
951
960
|
`[${connectionId}] Session resumption detected in renegotiation. ` +
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
961
|
+
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
|
|
962
|
+
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
963
|
+
`allowSessionTicket: ${this.settings.allowSessionTicket}`
|
|
955
964
|
);
|
|
956
|
-
|
|
965
|
+
|
|
957
966
|
// Block if there's session resumption without SNI
|
|
958
967
|
if (!resumptionInfo.hasSNI) {
|
|
959
968
|
console.log(
|
|
960
969
|
`[${connectionId}] Session resumption detected in renegotiation without SNI and allowSessionTicket=false. ` +
|
|
961
|
-
|
|
970
|
+
`Terminating connection to force new TLS handshake.`
|
|
962
971
|
);
|
|
963
972
|
this.initiateCleanupOnce(record, 'session_ticket_blocked');
|
|
964
973
|
return;
|
|
@@ -966,14 +975,18 @@ export class PortProxy {
|
|
|
966
975
|
if (this.settings.enableDetailedLogging) {
|
|
967
976
|
console.log(
|
|
968
977
|
`[${connectionId}] Session resumption with SNI detected in renegotiation. ` +
|
|
969
|
-
|
|
978
|
+
`Allowing connection since SNI is present.`
|
|
970
979
|
);
|
|
971
980
|
}
|
|
972
981
|
}
|
|
973
982
|
}
|
|
974
983
|
}
|
|
975
|
-
|
|
976
|
-
const newSNI = SniHandler.extractSNIWithResumptionSupport(
|
|
984
|
+
|
|
985
|
+
const newSNI = SniHandler.extractSNIWithResumptionSupport(
|
|
986
|
+
renegChunk,
|
|
987
|
+
connInfo,
|
|
988
|
+
this.settings.enableTlsDebugLogging
|
|
989
|
+
);
|
|
977
990
|
|
|
978
991
|
// Skip if no SNI was found
|
|
979
992
|
if (!newSNI) return;
|
|
@@ -983,7 +996,7 @@ export class PortProxy {
|
|
|
983
996
|
// Log and terminate the connection for any SNI change
|
|
984
997
|
console.log(
|
|
985
998
|
`[${connectionId}] Renegotiation with different SNI: ${record.lockedDomain} -> ${newSNI}. ` +
|
|
986
|
-
|
|
999
|
+
`Terminating connection - SNI domain switching is not allowed.`
|
|
987
1000
|
);
|
|
988
1001
|
this.initiateCleanupOnce(record, 'sni_mismatch');
|
|
989
1002
|
} else if (this.settings.enableDetailedLogging) {
|
|
@@ -1005,11 +1018,15 @@ export class PortProxy {
|
|
|
1005
1018
|
// The renegotiation handler is added when piping is established
|
|
1006
1019
|
// Making it part of setupPiping ensures proper sequencing of event handlers
|
|
1007
1020
|
socket.on('data', renegotiationHandler);
|
|
1008
|
-
|
|
1021
|
+
|
|
1009
1022
|
if (this.settings.enableDetailedLogging) {
|
|
1010
|
-
console.log(
|
|
1023
|
+
console.log(
|
|
1024
|
+
`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
|
|
1025
|
+
);
|
|
1011
1026
|
if (this.settings.allowSessionTicket === false) {
|
|
1012
|
-
console.log(
|
|
1027
|
+
console.log(
|
|
1028
|
+
`[${connectionId}] Session ticket usage is disabled. Connection will be reset on reconnection attempts.`
|
|
1029
|
+
);
|
|
1013
1030
|
}
|
|
1014
1031
|
}
|
|
1015
1032
|
}
|
|
@@ -1176,7 +1193,7 @@ export class PortProxy {
|
|
|
1176
1193
|
try {
|
|
1177
1194
|
// Remove our safe data handler
|
|
1178
1195
|
record.incoming.removeAllListeners('data');
|
|
1179
|
-
|
|
1196
|
+
|
|
1180
1197
|
// Reset the handler references
|
|
1181
1198
|
record.renegotiationHandler = undefined;
|
|
1182
1199
|
} catch (err) {
|
|
@@ -1402,7 +1419,11 @@ export class PortProxy {
|
|
|
1402
1419
|
}
|
|
1403
1420
|
|
|
1404
1421
|
// Initialize NetworkProxy if needed (useNetworkProxy is set but networkProxy isn't initialized)
|
|
1405
|
-
if (
|
|
1422
|
+
if (
|
|
1423
|
+
this.settings.useNetworkProxy &&
|
|
1424
|
+
this.settings.useNetworkProxy.length > 0 &&
|
|
1425
|
+
!this.networkProxy
|
|
1426
|
+
) {
|
|
1406
1427
|
await this.initializeNetworkProxy();
|
|
1407
1428
|
}
|
|
1408
1429
|
|
|
@@ -1410,12 +1431,16 @@ export class PortProxy {
|
|
|
1410
1431
|
if (this.networkProxy) {
|
|
1411
1432
|
await this.networkProxy.start();
|
|
1412
1433
|
console.log(`NetworkProxy started on port ${this.settings.networkProxyPort}`);
|
|
1413
|
-
|
|
1434
|
+
|
|
1414
1435
|
// Log ACME status
|
|
1415
1436
|
if (this.settings.acme?.enabled) {
|
|
1416
|
-
console.log(
|
|
1437
|
+
console.log(
|
|
1438
|
+
`ACME certificate management is enabled (${
|
|
1439
|
+
this.settings.acme.useProduction ? 'Production' : 'Staging'
|
|
1440
|
+
} mode)`
|
|
1441
|
+
);
|
|
1417
1442
|
console.log(`ACME HTTP challenge server on port ${this.settings.acme.port}`);
|
|
1418
|
-
|
|
1443
|
+
|
|
1419
1444
|
// Register domains for ACME certificates if enabled
|
|
1420
1445
|
if (this.networkProxy.options.acme?.enabled) {
|
|
1421
1446
|
console.log('Registering domains with ACME certificate manager...');
|
|
@@ -1489,7 +1514,7 @@ export class PortProxy {
|
|
|
1489
1514
|
|
|
1490
1515
|
// Initialize browser connection tracking
|
|
1491
1516
|
isBrowserConnection: false,
|
|
1492
|
-
domainSwitches: 0,
|
|
1517
|
+
domainSwitches: 0,
|
|
1493
1518
|
};
|
|
1494
1519
|
|
|
1495
1520
|
// Apply keep-alive settings if enabled
|
|
@@ -1536,9 +1561,9 @@ export class PortProxy {
|
|
|
1536
1561
|
|
|
1537
1562
|
// Check if this connection should be forwarded directly to NetworkProxy
|
|
1538
1563
|
// First check port-based forwarding settings
|
|
1539
|
-
let shouldUseNetworkProxy =
|
|
1540
|
-
|
|
1541
|
-
|
|
1564
|
+
let shouldUseNetworkProxy =
|
|
1565
|
+
this.settings.useNetworkProxy && this.settings.useNetworkProxy.includes(localPort);
|
|
1566
|
+
|
|
1542
1567
|
// We'll look for domain-specific settings after SNI extraction
|
|
1543
1568
|
|
|
1544
1569
|
if (shouldUseNetworkProxy) {
|
|
@@ -1548,15 +1573,20 @@ export class PortProxy {
|
|
|
1548
1573
|
// Set an initial timeout for handshake data
|
|
1549
1574
|
let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
|
|
1550
1575
|
if (!initialDataReceived) {
|
|
1551
|
-
console.log(
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1576
|
+
console.log(`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP}`);
|
|
1577
|
+
|
|
1578
|
+
// Add a grace period instead of immediate termination
|
|
1579
|
+
setTimeout(() => {
|
|
1580
|
+
if (!initialDataReceived) {
|
|
1581
|
+
console.log(`[${connectionId}] Final initial data timeout after grace period`);
|
|
1582
|
+
if (connectionRecord.incomingTerminationReason === null) {
|
|
1583
|
+
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
1584
|
+
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
1585
|
+
}
|
|
1586
|
+
socket.end();
|
|
1587
|
+
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
1588
|
+
}
|
|
1589
|
+
}, 30000); // 30 second grace period
|
|
1560
1590
|
}
|
|
1561
1591
|
}, this.settings.initialDataTimeout!);
|
|
1562
1592
|
|
|
@@ -1577,13 +1607,13 @@ export class PortProxy {
|
|
|
1577
1607
|
|
|
1578
1608
|
initialDataReceived = true;
|
|
1579
1609
|
connectionRecord.hasReceivedInitialData = true;
|
|
1580
|
-
|
|
1610
|
+
|
|
1581
1611
|
// Block non-TLS connections on port 443
|
|
1582
1612
|
// Always enforce TLS on standard HTTPS port
|
|
1583
1613
|
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
|
|
1584
1614
|
console.log(
|
|
1585
1615
|
`[${connectionId}] Non-TLS connection detected on port 443. ` +
|
|
1586
|
-
|
|
1616
|
+
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
|
|
1587
1617
|
);
|
|
1588
1618
|
if (connectionRecord.incomingTerminationReason === null) {
|
|
1589
1619
|
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
|
|
@@ -1597,29 +1627,35 @@ export class PortProxy {
|
|
|
1597
1627
|
// Check if this looks like a TLS handshake
|
|
1598
1628
|
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1599
1629
|
connectionRecord.isTLS = true;
|
|
1600
|
-
|
|
1630
|
+
|
|
1601
1631
|
// Check for TLS ClientHello with either no SNI or session tickets
|
|
1602
1632
|
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
|
|
1603
1633
|
// Extract SNI first
|
|
1604
|
-
const extractedSNI = SniHandler.extractSNI(
|
|
1634
|
+
const extractedSNI = SniHandler.extractSNI(
|
|
1635
|
+
chunk,
|
|
1636
|
+
this.settings.enableTlsDebugLogging
|
|
1637
|
+
);
|
|
1605
1638
|
const hasSNI = !!extractedSNI;
|
|
1606
|
-
|
|
1639
|
+
|
|
1607
1640
|
// Analyze for session resumption attempt
|
|
1608
|
-
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
1609
|
-
|
|
1641
|
+
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
1642
|
+
chunk,
|
|
1643
|
+
this.settings.enableTlsDebugLogging
|
|
1644
|
+
);
|
|
1645
|
+
|
|
1610
1646
|
// Always log for debugging purposes
|
|
1611
1647
|
console.log(
|
|
1612
1648
|
`[${connectionId}] TLS ClientHello detected with allowSessionTicket=false. ` +
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1649
|
+
`Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` +
|
|
1650
|
+
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
1651
|
+
`Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}`
|
|
1616
1652
|
);
|
|
1617
|
-
|
|
1653
|
+
|
|
1618
1654
|
// Block if this is a connection with session resumption but no SNI
|
|
1619
1655
|
if (resumptionInfo.isResumption && !hasSNI) {
|
|
1620
1656
|
console.log(
|
|
1621
1657
|
`[${connectionId}] Session resumption detected in initial ClientHello without SNI and allowSessionTicket=false. ` +
|
|
1622
|
-
|
|
1658
|
+
`Terminating connection to force new TLS handshake.`
|
|
1623
1659
|
);
|
|
1624
1660
|
if (connectionRecord.incomingTerminationReason === null) {
|
|
1625
1661
|
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
|
|
@@ -1629,13 +1665,13 @@ export class PortProxy {
|
|
|
1629
1665
|
this.cleanupConnection(connectionRecord, 'session_ticket_blocked');
|
|
1630
1666
|
return;
|
|
1631
1667
|
}
|
|
1632
|
-
|
|
1668
|
+
|
|
1633
1669
|
// Also block if this is a TLS connection without SNI when allowSessionTicket is false
|
|
1634
1670
|
// This forces clients to send SNI which helps with routing
|
|
1635
1671
|
if (!hasSNI && localPort === 443) {
|
|
1636
1672
|
console.log(
|
|
1637
1673
|
`[${connectionId}] TLS ClientHello detected on port 443 without SNI and allowSessionTicket=false. ` +
|
|
1638
|
-
|
|
1674
|
+
`Terminating connection to force proper SNI in handshake.`
|
|
1639
1675
|
);
|
|
1640
1676
|
if (connectionRecord.incomingTerminationReason === null) {
|
|
1641
1677
|
connectionRecord.incomingTerminationReason = 'no_sni_blocked';
|
|
@@ -1646,57 +1682,70 @@ export class PortProxy {
|
|
|
1646
1682
|
return;
|
|
1647
1683
|
}
|
|
1648
1684
|
}
|
|
1649
|
-
|
|
1685
|
+
|
|
1650
1686
|
// Try to extract SNI for domain-specific NetworkProxy handling
|
|
1651
1687
|
const connInfo = {
|
|
1652
1688
|
sourceIp: remoteIP,
|
|
1653
1689
|
sourcePort: socket.remotePort || 0,
|
|
1654
1690
|
destIp: socket.localAddress || '',
|
|
1655
|
-
destPort: socket.localPort || 0
|
|
1691
|
+
destPort: socket.localPort || 0,
|
|
1656
1692
|
};
|
|
1657
|
-
|
|
1693
|
+
|
|
1658
1694
|
// Extract SNI to check for domain-specific NetworkProxy settings
|
|
1659
1695
|
const serverName = SniHandler.processTlsPacket(
|
|
1660
|
-
chunk,
|
|
1696
|
+
chunk,
|
|
1661
1697
|
connInfo,
|
|
1662
1698
|
this.settings.enableTlsDebugLogging
|
|
1663
1699
|
);
|
|
1664
|
-
|
|
1700
|
+
|
|
1665
1701
|
if (serverName) {
|
|
1666
1702
|
// If we got an SNI, check for domain-specific NetworkProxy settings
|
|
1667
1703
|
const domainConfig = this.settings.domainConfigs.find((config) =>
|
|
1668
1704
|
config.domains.some((d) => plugins.minimatch(serverName, d))
|
|
1669
1705
|
);
|
|
1670
|
-
|
|
1706
|
+
|
|
1671
1707
|
// Save domain config and SNI in connection record
|
|
1672
1708
|
connectionRecord.domainConfig = domainConfig;
|
|
1673
1709
|
connectionRecord.lockedDomain = serverName;
|
|
1674
|
-
|
|
1710
|
+
|
|
1675
1711
|
// Use domain-specific NetworkProxy port if configured
|
|
1676
1712
|
if (domainConfig?.useNetworkProxy) {
|
|
1677
|
-
const networkProxyPort =
|
|
1678
|
-
|
|
1713
|
+
const networkProxyPort =
|
|
1714
|
+
domainConfig.networkProxyPort || this.settings.networkProxyPort;
|
|
1715
|
+
|
|
1679
1716
|
if (this.settings.enableDetailedLogging) {
|
|
1680
1717
|
console.log(
|
|
1681
1718
|
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
|
|
1682
1719
|
);
|
|
1683
1720
|
}
|
|
1684
|
-
|
|
1721
|
+
|
|
1685
1722
|
// Forward to NetworkProxy with domain-specific port
|
|
1686
|
-
this.forwardToNetworkProxy(
|
|
1723
|
+
this.forwardToNetworkProxy(
|
|
1724
|
+
connectionId,
|
|
1725
|
+
socket,
|
|
1726
|
+
connectionRecord,
|
|
1727
|
+
chunk,
|
|
1728
|
+
networkProxyPort
|
|
1729
|
+
);
|
|
1687
1730
|
return;
|
|
1688
1731
|
}
|
|
1689
1732
|
}
|
|
1690
|
-
|
|
1733
|
+
|
|
1691
1734
|
// Forward directly to NetworkProxy without domain-specific settings
|
|
1692
1735
|
this.forwardToNetworkProxy(connectionId, socket, connectionRecord, chunk);
|
|
1693
1736
|
} else {
|
|
1694
1737
|
// If not TLS, use normal direct connection
|
|
1695
1738
|
console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${localPort}`);
|
|
1696
|
-
this.setupDirectConnection(
|
|
1739
|
+
this.setupDirectConnection(
|
|
1740
|
+
connectionId,
|
|
1741
|
+
socket,
|
|
1742
|
+
connectionRecord,
|
|
1743
|
+
undefined,
|
|
1744
|
+
undefined,
|
|
1745
|
+
chunk
|
|
1746
|
+
);
|
|
1697
1747
|
}
|
|
1698
1748
|
});
|
|
1699
|
-
|
|
1700
1749
|
} else {
|
|
1701
1750
|
// For non-NetworkProxy ports, proceed with normal processing
|
|
1702
1751
|
|
|
@@ -1718,15 +1767,20 @@ export class PortProxy {
|
|
|
1718
1767
|
if (this.settings.sniEnabled) {
|
|
1719
1768
|
initialTimeout = setTimeout(() => {
|
|
1720
1769
|
if (!initialDataReceived) {
|
|
1721
|
-
console.log(
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1770
|
+
console.log(`[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP}`);
|
|
1771
|
+
|
|
1772
|
+
// Add a grace period instead of immediate termination
|
|
1773
|
+
setTimeout(() => {
|
|
1774
|
+
if (!initialDataReceived) {
|
|
1775
|
+
console.log(`[${connectionId}] Final initial data timeout after grace period`);
|
|
1776
|
+
if (connectionRecord.incomingTerminationReason === null) {
|
|
1777
|
+
connectionRecord.incomingTerminationReason = 'initial_timeout';
|
|
1778
|
+
this.incrementTerminationStat('incoming', 'initial_timeout');
|
|
1779
|
+
}
|
|
1780
|
+
socket.end();
|
|
1781
|
+
this.cleanupConnection(connectionRecord, 'initial_timeout');
|
|
1782
|
+
}
|
|
1783
|
+
}, 30000); // 30 second grace period
|
|
1730
1784
|
}
|
|
1731
1785
|
}, this.settings.initialDataTimeout!);
|
|
1732
1786
|
|
|
@@ -1760,9 +1814,9 @@ export class PortProxy {
|
|
|
1760
1814
|
sourceIp: remoteIP,
|
|
1761
1815
|
sourcePort: socket.remotePort || 0,
|
|
1762
1816
|
destIp: socket.localAddress || '',
|
|
1763
|
-
destPort: socket.localPort || 0
|
|
1817
|
+
destPort: socket.localPort || 0,
|
|
1764
1818
|
};
|
|
1765
|
-
|
|
1819
|
+
|
|
1766
1820
|
SniHandler.extractSNIWithResumptionSupport(chunk, debugConnInfo, true);
|
|
1767
1821
|
}
|
|
1768
1822
|
}
|
|
@@ -1814,7 +1868,7 @@ export class PortProxy {
|
|
|
1814
1868
|
|
|
1815
1869
|
// Save domain config in connection record
|
|
1816
1870
|
connectionRecord.domainConfig = domainConfig;
|
|
1817
|
-
|
|
1871
|
+
|
|
1818
1872
|
// Check if this domain should use NetworkProxy (domain-specific setting)
|
|
1819
1873
|
if (domainConfig?.useNetworkProxy && this.networkProxy) {
|
|
1820
1874
|
if (this.settings.enableDetailedLogging) {
|
|
@@ -1822,15 +1876,16 @@ export class PortProxy {
|
|
|
1822
1876
|
`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`
|
|
1823
1877
|
);
|
|
1824
1878
|
}
|
|
1825
|
-
|
|
1826
|
-
const networkProxyPort =
|
|
1827
|
-
|
|
1879
|
+
|
|
1880
|
+
const networkProxyPort =
|
|
1881
|
+
domainConfig.networkProxyPort || this.settings.networkProxyPort;
|
|
1882
|
+
|
|
1828
1883
|
if (initialChunk && connectionRecord.isTLS) {
|
|
1829
1884
|
// For TLS connections with initial chunk, forward to NetworkProxy
|
|
1830
1885
|
this.forwardToNetworkProxy(
|
|
1831
|
-
connectionId,
|
|
1832
|
-
socket,
|
|
1833
|
-
connectionRecord,
|
|
1886
|
+
connectionId,
|
|
1887
|
+
socket,
|
|
1888
|
+
connectionRecord,
|
|
1834
1889
|
initialChunk,
|
|
1835
1890
|
networkProxyPort // Pass the domain-specific NetworkProxy port if configured
|
|
1836
1891
|
);
|
|
@@ -1861,7 +1916,10 @@ export class PortProxy {
|
|
|
1861
1916
|
)}`
|
|
1862
1917
|
);
|
|
1863
1918
|
}
|
|
1864
|
-
} else if (
|
|
1919
|
+
} else if (
|
|
1920
|
+
this.settings.defaultAllowedIPs &&
|
|
1921
|
+
this.settings.defaultAllowedIPs.length > 0
|
|
1922
|
+
) {
|
|
1865
1923
|
if (
|
|
1866
1924
|
!isGlobIPAllowed(
|
|
1867
1925
|
remoteIP,
|
|
@@ -1974,19 +2032,37 @@ export class PortProxy {
|
|
|
1974
2032
|
initialDataReceived = false;
|
|
1975
2033
|
|
|
1976
2034
|
socket.once('data', (chunk: Buffer) => {
|
|
2035
|
+
// Clear timeout immediately
|
|
1977
2036
|
if (initialTimeout) {
|
|
1978
2037
|
clearTimeout(initialTimeout);
|
|
1979
2038
|
initialTimeout = null;
|
|
1980
2039
|
}
|
|
1981
|
-
|
|
2040
|
+
|
|
1982
2041
|
initialDataReceived = true;
|
|
1983
2042
|
|
|
2043
|
+
// Add debugging ONLY if detailed logging is enabled - avoid heavy processing
|
|
2044
|
+
if (this.settings.enableTlsDebugLogging && SniHandler.isClientHello(chunk)) {
|
|
2045
|
+
// Move heavy debug logging to a separate async task to not block the flow
|
|
2046
|
+
setImmediate(() => {
|
|
2047
|
+
try {
|
|
2048
|
+
const resumptionInfo = SniHandler.hasSessionResumption(chunk, true);
|
|
2049
|
+
const standardSNI = SniHandler.extractSNI(chunk, true);
|
|
2050
|
+
const pskSNI = SniHandler.extractSNIFromPSKExtension(chunk, true);
|
|
2051
|
+
|
|
2052
|
+
console.log(`[${connectionId}] ClientHello details: isResumption=${resumptionInfo.isResumption}, hasSNI=${resumptionInfo.hasSNI}`);
|
|
2053
|
+
console.log(`[${connectionId}] SNI extraction results: standardSNI=${standardSNI || 'none'}, pskSNI=${pskSNI || 'none'}`);
|
|
2054
|
+
} catch (err) {
|
|
2055
|
+
console.log(`[${connectionId}] Error in debug logging: ${err}`);
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1984
2060
|
// Block non-TLS connections on port 443
|
|
1985
2061
|
// Always enforce TLS on standard HTTPS port
|
|
1986
2062
|
if (!SniHandler.isTlsHandshake(chunk) && localPort === 443) {
|
|
1987
2063
|
console.log(
|
|
1988
2064
|
`[${connectionId}] Non-TLS connection detected on port 443 in SNI handler. ` +
|
|
1989
|
-
|
|
2065
|
+
`Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
|
|
1990
2066
|
);
|
|
1991
2067
|
if (connectionRecord.incomingTerminationReason === null) {
|
|
1992
2068
|
connectionRecord.incomingTerminationReason = 'non_tls_blocked';
|
|
@@ -2008,28 +2084,34 @@ export class PortProxy {
|
|
|
2008
2084
|
`[${connectionId}] Extracting SNI from TLS handshake, ${chunk.length} bytes`
|
|
2009
2085
|
);
|
|
2010
2086
|
}
|
|
2011
|
-
|
|
2087
|
+
|
|
2012
2088
|
// Check for session tickets if allowSessionTicket is disabled
|
|
2013
2089
|
if (this.settings.allowSessionTicket === false && SniHandler.isClientHello(chunk)) {
|
|
2014
2090
|
// Analyze for session resumption attempt
|
|
2015
|
-
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
2016
|
-
|
|
2091
|
+
const resumptionInfo = SniHandler.hasSessionResumption(
|
|
2092
|
+
chunk,
|
|
2093
|
+
this.settings.enableTlsDebugLogging
|
|
2094
|
+
);
|
|
2095
|
+
|
|
2017
2096
|
if (resumptionInfo.isResumption) {
|
|
2018
2097
|
// Always log resumption attempt for easier debugging
|
|
2019
2098
|
// Try to extract SNI for logging
|
|
2020
|
-
const extractedSNI = SniHandler.extractSNI(
|
|
2099
|
+
const extractedSNI = SniHandler.extractSNI(
|
|
2100
|
+
chunk,
|
|
2101
|
+
this.settings.enableTlsDebugLogging
|
|
2102
|
+
);
|
|
2021
2103
|
console.log(
|
|
2022
2104
|
`[${connectionId}] Session resumption detected in SNI handler. ` +
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2105
|
+
`Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
|
|
2106
|
+
`SNI value: ${extractedSNI || 'None'}, ` +
|
|
2107
|
+
`allowSessionTicket: ${this.settings.allowSessionTicket}`
|
|
2026
2108
|
);
|
|
2027
|
-
|
|
2109
|
+
|
|
2028
2110
|
// Block if there's session resumption without SNI
|
|
2029
2111
|
if (!resumptionInfo.hasSNI) {
|
|
2030
2112
|
console.log(
|
|
2031
2113
|
`[${connectionId}] Session resumption detected in SNI handler without SNI and allowSessionTicket=false. ` +
|
|
2032
|
-
|
|
2114
|
+
`Terminating connection to force new TLS handshake.`
|
|
2033
2115
|
);
|
|
2034
2116
|
if (connectionRecord.incomingTerminationReason === null) {
|
|
2035
2117
|
connectionRecord.incomingTerminationReason = 'session_ticket_blocked';
|
|
@@ -2042,7 +2124,7 @@ export class PortProxy {
|
|
|
2042
2124
|
if (this.settings.enableDetailedLogging) {
|
|
2043
2125
|
console.log(
|
|
2044
2126
|
`[${connectionId}] Session resumption with SNI detected in SNI handler. ` +
|
|
2045
|
-
|
|
2127
|
+
`Allowing connection since SNI is present.`
|
|
2046
2128
|
);
|
|
2047
2129
|
}
|
|
2048
2130
|
}
|
|
@@ -2054,16 +2136,17 @@ export class PortProxy {
|
|
|
2054
2136
|
sourceIp: remoteIP,
|
|
2055
2137
|
sourcePort: socket.remotePort || 0,
|
|
2056
2138
|
destIp: socket.localAddress || '',
|
|
2057
|
-
destPort: socket.localPort || 0
|
|
2139
|
+
destPort: socket.localPort || 0,
|
|
2058
2140
|
};
|
|
2059
|
-
|
|
2141
|
+
|
|
2060
2142
|
// Use the new processTlsPacket method for comprehensive handling
|
|
2061
|
-
serverName =
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2143
|
+
serverName =
|
|
2144
|
+
SniHandler.processTlsPacket(
|
|
2145
|
+
chunk,
|
|
2146
|
+
connInfo,
|
|
2147
|
+
this.settings.enableTlsDebugLogging,
|
|
2148
|
+
connectionRecord.lockedDomain // Pass any previously negotiated domain as a hint
|
|
2149
|
+
) || '';
|
|
2067
2150
|
}
|
|
2068
2151
|
|
|
2069
2152
|
// Lock the connection to the negotiated SNI.
|
|
@@ -2392,7 +2475,7 @@ export class PortProxy {
|
|
|
2392
2475
|
console.log('Stopping NetworkProxy...');
|
|
2393
2476
|
await this.networkProxy.stop();
|
|
2394
2477
|
console.log('NetworkProxy stopped successfully');
|
|
2395
|
-
|
|
2478
|
+
|
|
2396
2479
|
// Log ACME shutdown if it was enabled
|
|
2397
2480
|
if (this.settings.acme?.enabled) {
|
|
2398
2481
|
console.log('ACME certificate manager stopped');
|
|
@@ -2417,4 +2500,4 @@ export class PortProxy {
|
|
|
2417
2500
|
|
|
2418
2501
|
console.log('PortProxy shutdown complete.');
|
|
2419
2502
|
}
|
|
2420
|
-
}
|
|
2503
|
+
}
|