@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.
@@ -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; // 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
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 || 60000, // 60 seconds for initial handshake
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: settingsArg.enableKeepAliveProbes !== undefined
236
- ? settingsArg.enableKeepAliveProbes : true,
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: settingsArg.allowSessionTicket !== undefined
241
- ? settingsArg.allowSessionTicket : true,
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('Using empty certificate placeholders - ACME will generate proper certificates if enabled');
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.updateProxyConfigs(proxyConfigs).then(() => {
417
- console.log(`Successfully synchronized ${proxyConfigs.length} domain configurations to NetworkProxy`);
418
- }).catch(err => {
419
- console.log(`Error synchronizing configurations: ${err.message}`);
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
- // Setup function to establish piping - we'll use this after flushing data
853
- const setupPiping = () => {
854
- // Mark that we're switching to piping mode
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
- // No pending data, just establish piping immediately
917
- setupPiping();
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(renegChunk, this.settings.enableTlsDebugLogging);
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(renegChunk, this.settings.enableTlsDebugLogging);
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
- `Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
953
- `SNI value: ${extractedSNI || 'None'}, ` +
954
- `allowSessionTicket: ${this.settings.allowSessionTicket}`
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
- `Terminating connection to force new TLS handshake.`
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
- `Allowing connection since SNI is present.`
978
+ `Allowing connection since SNI is present.`
970
979
  );
971
980
  }
972
981
  }
973
982
  }
974
983
  }
975
-
976
- const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, connInfo, this.settings.enableTlsDebugLogging);
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
- `Terminating connection - SNI domain switching is not allowed.`
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(`[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`);
1023
+ console.log(
1024
+ `[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}`
1025
+ );
1011
1026
  if (this.settings.allowSessionTicket === false) {
1012
- console.log(`[${connectionId}] Session ticket usage is disabled. Connection will be reset on reconnection attempts.`);
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 (this.settings.useNetworkProxy && this.settings.useNetworkProxy.length > 0 && !this.networkProxy) {
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(`ACME certificate management is enabled (${this.settings.acme.useProduction ? 'Production' : 'Staging'} mode)`);
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 = this.settings.useNetworkProxy &&
1540
- this.settings.useNetworkProxy.includes(localPort);
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
- `[${connectionId}] Initial data timeout (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP} on port ${localPort}`
1553
- );
1554
- if (connectionRecord.incomingTerminationReason === null) {
1555
- connectionRecord.incomingTerminationReason = 'initial_timeout';
1556
- this.incrementTerminationStat('incoming', 'initial_timeout');
1557
- }
1558
- socket.end();
1559
- this.cleanupConnection(connectionRecord, 'initial_timeout');
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
- `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
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(chunk, this.settings.enableTlsDebugLogging);
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(chunk, this.settings.enableTlsDebugLogging);
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
- `Has SNI: ${hasSNI ? 'Yes' : 'No'}, ` +
1614
- `SNI value: ${extractedSNI || 'None'}, ` +
1615
- `Has session resumption: ${resumptionInfo.isResumption ? 'Yes' : 'No'}`
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
- `Terminating connection to force new TLS handshake.`
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
- `Terminating connection to force proper SNI in handshake.`
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 = domainConfig.networkProxyPort || this.settings.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(connectionId, socket, connectionRecord, chunk, networkProxyPort);
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(connectionId, socket, connectionRecord, undefined, undefined, chunk);
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
- `[${connectionId}] Initial data timeout (${this.settings.initialDataTimeout}ms) for connection from ${remoteIP} on port ${localPort}`
1723
- );
1724
- if (connectionRecord.incomingTerminationReason === null) {
1725
- connectionRecord.incomingTerminationReason = 'initial_timeout';
1726
- this.incrementTerminationStat('incoming', 'initial_timeout');
1727
- }
1728
- socket.end();
1729
- this.cleanupConnection(connectionRecord, 'initial_timeout');
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 = domainConfig.networkProxyPort || this.settings.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 (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
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
- `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
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(chunk, this.settings.enableTlsDebugLogging);
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(chunk, this.settings.enableTlsDebugLogging);
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
- `Has SNI: ${resumptionInfo.hasSNI ? 'Yes' : 'No'}, ` +
2024
- `SNI value: ${extractedSNI || 'None'}, ` +
2025
- `allowSessionTicket: ${this.settings.allowSessionTicket}`
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
- `Terminating connection to force new TLS handshake.`
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
- `Allowing connection since SNI is present.`
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 = SniHandler.processTlsPacket(
2062
- chunk,
2063
- connInfo,
2064
- this.settings.enableTlsDebugLogging,
2065
- connectionRecord.lockedDomain // Pass any previously negotiated domain as a hint
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
+ }