@push.rocks/smartproxy 4.1.16 → 4.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "4.1.16",
3
+ "version": "4.2.0",
4
4
  "private": false,
5
5
  "description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
6
6
  "main": "dist_ts/index.js",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '4.1.16',
6
+ version: '4.2.0',
7
7
  description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
8
8
  }
@@ -11,7 +11,6 @@ import { TlsManager } from './classes.pp.tlsmanager.js';
11
11
  import { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
12
12
  import { TimeoutManager } from './classes.pp.timeoutmanager.js';
13
13
  import { PortRangeManager } from './classes.pp.portrangemanager.js';
14
- import { TlsAlert } from './classes.pp.tlsalert.js';
15
14
 
16
15
  /**
17
16
  * Handles new connection processing and setup logic
@@ -561,125 +560,64 @@ export class ConnectionHandler {
561
560
  // Block ClientHello without SNI when allowSessionTicket is false
562
561
  console.log(
563
562
  `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
564
- `Sending unrecognized_name alert to encourage immediate retry with SNI on same connection.`
563
+ `Sending warning unrecognized_name alert to encourage immediate retry with SNI.`
565
564
  );
566
565
 
566
+ // Set the termination reason first
567
+ if (record.incomingTerminationReason === null) {
568
+ record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
569
+ this.connectionManager.incrementTerminationStat(
570
+ 'incoming',
571
+ 'session_ticket_blocked_no_sni'
572
+ );
573
+ }
574
+
575
+ // Create a warning-level alert for unrecognized_name
576
+ // This encourages Chrome to retry immediately with SNI
577
+ const serverNameUnknownAlertData = Buffer.from([
578
+ 0x15, // Alert record type
579
+ 0x03,
580
+ 0x03, // TLS 1.2 version
581
+ 0x00,
582
+ 0x02, // Length
583
+ 0x01, // Warning alert level (not fatal)
584
+ 0x70, // unrecognized_name alert (code 112)
585
+ ]);
586
+
567
587
  try {
568
- // Send the alert but do NOT end the connection
569
- // Using our new TlsAlert class for better alert management
588
+ // Use cork/uncork to ensure the alert is sent as a single packet
570
589
  socket.cork();
571
- socket.write(TlsAlert.alerts.unrecognizedName);
590
+ const writeSuccessful = socket.write(serverNameUnknownAlertData);
572
591
  socket.uncork();
573
-
574
- console.log(
575
- `[${connectionId}] Alert sent, waiting for new ClientHello on same connection...`
576
- );
577
-
578
- // Remove existing data listener and wait for a new ClientHello
579
- socket.removeAllListeners('data');
580
-
581
- // Set up a new data handler to capture the next message
582
- socket.once('data', (retryChunk) => {
583
- // Cancel the fallback timeout as we received data
584
- if (record.alertFallbackTimeout) {
585
- clearTimeout(record.alertFallbackTimeout);
586
- record.alertFallbackTimeout = null;
587
- }
588
-
589
- // Check if this is a new ClientHello
590
- if (this.tlsManager.isClientHello(retryChunk)) {
591
- console.log(`[${connectionId}] Received new ClientHello after alert`);
592
-
593
- // Extract SNI from the new ClientHello
594
- const newServerName = this.tlsManager.extractSNI(retryChunk, connInfo) || '';
595
-
596
- if (newServerName) {
597
- console.log(`[${connectionId}] New ClientHello contains SNI: ${newServerName}`);
598
-
599
- // Update the record with the new SNI
600
- record.lockedDomain = newServerName;
601
-
602
- // Continue with normal connection setup using the new chunk with SNI
603
- setupConnection(newServerName, retryChunk);
604
- } else {
605
- console.log(
606
- `[${connectionId}] New ClientHello still missing SNI, closing connection`
607
- );
608
-
609
- // If still no SNI after retry, now we can close the connection
610
- if (record.incomingTerminationReason === null) {
611
- record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
612
- this.connectionManager.incrementTerminationStat(
613
- 'incoming',
614
- 'session_ticket_blocked_no_sni'
615
- );
616
- }
617
-
618
- // Send a close_notify alert before ending the connection
619
- TlsAlert.sendCloseNotify(socket)
620
- .catch((err) => {
621
- console.log(`[${connectionId}] Error sending close_notify: ${err.message}`);
622
- })
623
- .finally(() => {
624
- // Clean up even if sending the alert fails
625
- this.connectionManager.cleanupConnection(
626
- record,
627
- 'session_ticket_blocked_no_sni'
628
- );
629
- });
630
- }
631
- } else {
632
- console.log(
633
- `[${connectionId}] Received non-ClientHello data after alert, closing connection`
634
- );
635
-
636
- // If we got something other than a ClientHello, close the connection
637
- if (record.incomingTerminationReason === null) {
638
- record.incomingTerminationReason = 'invalid_protocol';
639
- this.connectionManager.incrementTerminationStat('incoming', 'invalid_protocol');
640
- }
641
-
642
- // Send a protocol_version alert before ending the connection
643
- TlsAlert.send(socket, TlsAlert.LEVEL_FATAL, TlsAlert.PROTOCOL_VERSION, true)
644
- .catch((err) => {
645
- console.log(
646
- `[${connectionId}] Error sending protocol_version alert: ${err.message}`
647
- );
648
- })
649
- .finally(() => {
650
- // Clean up even if sending the alert fails
651
- this.connectionManager.cleanupConnection(record, 'invalid_protocol');
652
- });
653
- }
654
- });
655
-
656
- // Set a fallback timeout in case the client doesn't respond
657
- const fallbackTimeout = setTimeout(() => {
658
- console.log(`[${connectionId}] No response after alert, closing connection`);
659
-
660
- if (record.incomingTerminationReason === null) {
661
- record.incomingTerminationReason = 'alert_timeout';
662
- this.connectionManager.incrementTerminationStat('incoming', 'alert_timeout');
663
- }
664
-
665
- // Send a close_notify alert before ending the connection
666
- TlsAlert.sendCloseNotify(socket)
667
- .catch((err) => {
668
- console.log(`[${connectionId}] Error sending close_notify: ${err.message}`);
669
- })
670
- .finally(() => {
671
- // Clean up even if sending the alert fails
672
- this.connectionManager.cleanupConnection(record, 'alert_timeout');
673
- });
674
- }, 10000); // 10 second timeout
675
-
676
- // Make sure the timeout doesn't keep the process alive
677
- if (fallbackTimeout.unref) {
678
- fallbackTimeout.unref();
592
+
593
+ // Function to handle the clean socket termination - but more gradually
594
+ const finishConnection = () => {
595
+ // Give Chrome more time to process the alert before closing
596
+ // We won't call destroy() at all - just end() and let the socket close naturally
597
+
598
+ // Log the cleanup but wait for natural closure
599
+ setTimeout(() => {
600
+ socket.end();
601
+ this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
602
+ }, 1000); // Longer delay to let socket cleanup happen naturally
603
+ };
604
+
605
+ if (writeSuccessful) {
606
+ // Wait longer before ending connection to ensure alert is processed by client
607
+ setTimeout(finishConnection, 200); // Increased from 50ms to 200ms
608
+ } else {
609
+ // If the kernel buffer was full, wait for the drain event
610
+ socket.once('drain', () => {
611
+ // Wait longer after drain as well
612
+ setTimeout(finishConnection, 200);
613
+ });
614
+
615
+ // Safety timeout is increased too
616
+ setTimeout(() => {
617
+ socket.removeAllListeners('drain');
618
+ finishConnection();
619
+ }, 400); // Increased from 250ms to 400ms
679
620
  }
680
-
681
- // Store the timeout in the record so it can be cleared during cleanup
682
- record.alertFallbackTimeout = fallbackTimeout;
683
621
  } catch (err) {
684
622
  // If we can't send the alert, fall back to immediate termination
685
623
  console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`);
@@ -687,7 +625,6 @@ export class ConnectionHandler {
687
625
  this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
688
626
  }
689
627
 
690
- // Return early to prevent the normal flow
691
628
  return;
692
629
  }
693
630
  }
@@ -173,6 +173,7 @@ export class TlsAlert {
173
173
  protocolVersion: TlsAlert.createFatal(TlsAlert.PROTOCOL_VERSION),
174
174
  insufficientSecurity: TlsAlert.createFatal(TlsAlert.INSUFFICIENT_SECURITY),
175
175
  internalError: TlsAlert.createFatal(TlsAlert.INTERNAL_ERROR),
176
+ unrecognizedNameFatal: TlsAlert.createFatal(TlsAlert.UNRECOGNIZED_NAME),
176
177
  };
177
178
 
178
179
  /**
@@ -215,4 +216,43 @@ export class TlsAlert {
215
216
  const level = fatal ? this.LEVEL_FATAL : this.LEVEL_WARNING;
216
217
  return this.send(socket, level, this.CERTIFICATE_EXPIRED, closeAfterSend, closeDelay);
217
218
  }
219
+
220
+ /**
221
+ * Send a sequence of alerts to force SNI from clients
222
+ * This combines multiple alerts to ensure maximum browser compatibility
223
+ *
224
+ * @param socket The socket to send the alerts to
225
+ * @returns Promise that resolves when all alerts have been sent
226
+ */
227
+ static async sendForceSniSequence(socket: net.Socket): Promise<void> {
228
+ try {
229
+ // Send unrecognized_name (warning)
230
+ socket.cork();
231
+ socket.write(this.alerts.unrecognizedName);
232
+ socket.uncork();
233
+
234
+ // Give the socket time to send the alert
235
+ return new Promise((resolve) => {
236
+ setTimeout(resolve, 50);
237
+ });
238
+ } catch (err) {
239
+ return Promise.reject(err);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Send a fatal level alert that immediately terminates the connection
245
+ *
246
+ * @param socket The socket to send the alert to
247
+ * @param description Alert description code
248
+ * @param closeDelay Milliseconds to wait before closing the connection (default: 100ms)
249
+ * @returns Promise that resolves when the alert has been sent and the connection closed
250
+ */
251
+ static async sendFatalAndClose(
252
+ socket: net.Socket,
253
+ description: number,
254
+ closeDelay: number = 100
255
+ ): Promise<void> {
256
+ return this.send(socket, this.LEVEL_FATAL, description, true, closeDelay);
257
+ }
218
258
  }