@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.
|
|
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",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '4.
|
|
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
|
|
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
|
-
//
|
|
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(
|
|
590
|
+
const writeSuccessful = socket.write(serverNameUnknownAlertData);
|
|
572
591
|
socket.uncork();
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
//
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
}
|