Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
794e1292e5 | |||
ee79f9ab7c | |||
107bc3b50b | |||
97982976c8 |
14
changelog.md
14
changelog.md
@ -1,5 +1,19 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-03-15 - 4.1.3 - fix(connectionhandler)
|
||||
Improve handling of TLS ClientHello messages when allowSessionTicket is disabled and no SNI is provided by sending a warning alert (unrecognized_name, code 0x70) with a proper callback and delay to ensure the alert is transmitted before closing the connection.
|
||||
|
||||
- Replace the fatal alert (0x02/0x40) with a warning alert (0x01/0x70) to notify clients to send SNI.
|
||||
- Use socket.write callback to wait 100ms after sending the alert before terminating the connection.
|
||||
- Remove the previous short (50ms) delay in favor of a more reliable delay mechanism before cleanup.
|
||||
|
||||
## 2025-03-15 - 4.1.2 - fix(connectionhandler)
|
||||
Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed.
|
||||
|
||||
- Added logic to transmit a fatal TLS alert (Handshake Failure) before closing the connection when no SNI is present with allowSessionTicket=false.
|
||||
- Introduced a slight 50ms delay after sending the alert to ensure the client receives the alert properly.
|
||||
- Applied these changes both for the initial ClientHello and when handling subsequent TLS data.
|
||||
|
||||
## 2025-03-15 - 4.1.1 - fix(tls)
|
||||
Enforce strict SNI handling in TLS connections by terminating ClientHello messages lacking SNI when session tickets are disallowed and removing legacy session cache code.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@push.rocks/smartproxy",
|
||||
"version": "4.1.1",
|
||||
"version": "4.1.3",
|
||||
"private": false,
|
||||
"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.",
|
||||
"main": "dist_ts/index.js",
|
||||
|
@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartproxy',
|
||||
version: '4.1.1',
|
||||
version: '4.1.3',
|
||||
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.'
|
||||
}
|
||||
|
@ -174,16 +174,50 @@ export class ConnectionHandler {
|
||||
|
||||
// If allowSessionTicket is false and we can't determine SNI, terminate the connection
|
||||
if (!serverName) {
|
||||
// Always block when allowSessionTicket is false and there's no SNI
|
||||
console.log(
|
||||
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
|
||||
`Terminating connection to force new TLS handshake with SNI.`
|
||||
);
|
||||
|
||||
// Send a proper TLS alert before ending the connection
|
||||
// Using "unrecognized_name" (112) alert which is a warning level alert (1)
|
||||
// that encourages clients to retry with proper SNI
|
||||
const alertData = Buffer.from([
|
||||
0x15, // Alert record type
|
||||
0x03, 0x03, // TLS 1.2 version
|
||||
0x00, 0x02, // Length
|
||||
0x01, // Warning alert level (not fatal)
|
||||
0x70 // unrecognized_name alert (code 112)
|
||||
]);
|
||||
|
||||
try {
|
||||
socket.write(alertData, () => {
|
||||
// Only close the socket after we're sure the alert was sent
|
||||
// Give the alert time to be processed by the client
|
||||
setTimeout(() => {
|
||||
socket.end();
|
||||
|
||||
// Ensure complete cleanup happens a bit later
|
||||
setTimeout(() => {
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
} catch (err) {
|
||||
// If we can't send the alert, fall back to immediate termination
|
||||
socket.end();
|
||||
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||
}
|
||||
|
||||
if (record.incomingTerminationReason === null) {
|
||||
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
||||
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
|
||||
}
|
||||
socket.end();
|
||||
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -548,32 +582,51 @@ export class ConnectionHandler {
|
||||
this.tlsManager.isClientHello(chunk) &&
|
||||
!serverName) {
|
||||
|
||||
// Check if this is a session resumption
|
||||
const resumptionInfo = this.tlsManager.handleSessionResumption(
|
||||
chunk,
|
||||
connectionId,
|
||||
false // No SNI
|
||||
// Always block ClientHello without SNI when allowSessionTicket is false
|
||||
console.log(
|
||||
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
|
||||
`Terminating connection to force new TLS handshake with SNI.`
|
||||
);
|
||||
|
||||
if (resumptionInfo.shouldBlock) {
|
||||
console.log(
|
||||
`[${connectionId}] Session resumption without SNI detected and allowSessionTicket=false. ` +
|
||||
`Terminating connection to force new TLS handshake with SNI.`
|
||||
);
|
||||
if (record.incomingTerminationReason === null) {
|
||||
record.incomingTerminationReason = resumptionInfo.reason || 'session_ticket_blocked_no_sni';
|
||||
this.connectionManager.incrementTerminationStat(
|
||||
'incoming',
|
||||
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
|
||||
);
|
||||
}
|
||||
// Send a proper TLS alert before ending the connection
|
||||
// Using "unrecognized_name" (112) alert which is a warning level alert (1)
|
||||
// that encourages clients to retry with proper SNI
|
||||
const alertData = Buffer.from([
|
||||
0x15, // Alert record type
|
||||
0x03, 0x03, // TLS 1.2 version
|
||||
0x00, 0x02, // Length
|
||||
0x01, // Warning alert level (not fatal)
|
||||
0x70 // unrecognized_name alert (code 112)
|
||||
]);
|
||||
|
||||
try {
|
||||
socket.write(alertData, () => {
|
||||
// Only close the socket after we're sure the alert was sent
|
||||
// Give the alert time to be processed by the client
|
||||
setTimeout(() => {
|
||||
socket.end();
|
||||
|
||||
// Ensure complete cleanup happens a bit later
|
||||
setTimeout(() => {
|
||||
if (!socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||
}, 100);
|
||||
}, 100);
|
||||
});
|
||||
} catch (err) {
|
||||
// If we can't send the alert, fall back to immediate termination
|
||||
socket.end();
|
||||
this.connectionManager.cleanupConnection(
|
||||
record,
|
||||
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
|
||||
);
|
||||
return;
|
||||
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||
}
|
||||
|
||||
if (record.incomingTerminationReason === null) {
|
||||
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
||||
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user