Compare commits

...

8 Commits

Author SHA1 Message Date
794e1292e5 4.1.3
Some checks failed
Default (tags) / security (push) Successful in 38s
Default (tags) / test (push) Failing after 1m0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-15 18:51:50 +00:00
ee79f9ab7c 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. 2025-03-15 18:51:50 +00:00
107bc3b50b 4.1.2
Some checks failed
Default (tags) / security (push) Successful in 30s
Default (tags) / test (push) Failing after 1m2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-15 17:16:18 +00:00
97982976c8 fix(connectionhandler): Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed. 2025-03-15 17:16:18 +00:00
fe60f88746 4.1.1
Some checks failed
Default (tags) / security (push) Failing after 12m44s
Default (tags) / test (push) Has been cancelled
Default (tags) / release (push) Has been cancelled
Default (tags) / metadata (push) Has been cancelled
2025-03-15 17:00:11 +00:00
252a987344 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. 2025-03-15 17:00:10 +00:00
677d30563f 4.1.0
Some checks failed
Default (tags) / security (push) Successful in 37s
Default (tags) / test (push) Failing after 59s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-03-14 11:34:53 +00:00
9aa747b5d4 feat(SniHandler): Enhance SNI extraction to support session caching and tab reactivation by adding session cache initialization, cleanup and helper methods. Update processTlsPacket to use cached SNI for session resumption and connection racing scenarios. 2025-03-14 11:34:52 +00:00
5 changed files with 157 additions and 53 deletions

View File

@ -1,5 +1,33 @@
# 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.
- In classes.pp.connectionhandler.ts, if allowSessionTicket is false and no SNI is extracted from a ClientHello, the connection is terminated to force a new handshake with SNI.
- In classes.pp.snihandler.ts, removed session cache and related cleanup functions used for tab reactivation, simplifying SNI extraction logic.
- Improved logging in TLS processing to aid in diagnosing handshake and session resumption issues.
## 2025-03-14 - 4.1.0 - feat(SniHandler)
Enhance SNI extraction to support session caching and tab reactivation by adding session cache initialization, cleanup and helper methods. Update processTlsPacket to use cached SNI for session resumption and connection racing scenarios.
- Introduce initSessionCacheCleanup, cleanupSessionCache, createClientKey, cacheSession, and getCachedSession methods to manage SNI information.
- Cache SNI based on client IP and client random to improve handling of fragmented ClientHello messages and tab reactivation.
- Update processTlsPacket to leverage cached SNI when standard extraction fails, reducing redundant extraction and enhancing connection racing behavior.
## 2025-03-14 - 4.0.0 - BREAKING CHANGE(core)
refactor: reorganize internal module structure to use 'classes.pp.*' modules

View File

@ -1,6 +1,6 @@
{
"name": "@push.rocks/smartproxy",
"version": "4.0.0",
"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",

View File

@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@push.rocks/smartproxy',
version: '4.0.0',
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.'
}

View File

@ -172,35 +172,80 @@ export class ConnectionHandler {
// Extract SNI for domain-specific NetworkProxy handling
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
if (serverName) {
// If we got an SNI, check for domain-specific NetworkProxy settings
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
// Save domain config and SNI in connection record
record.domainConfig = domainConfig;
record.lockedDomain = serverName;
// Use domain-specific NetworkProxy port if configured
if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
);
}
// Forward to NetworkProxy with domain-specific port
this.networkProxyBridge.forwardToNetworkProxy(
connectionId,
socket,
record,
chunk,
networkProxyPort,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
// 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');
}
return;
}
// Save domain config and SNI in connection record
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
record.domainConfig = domainConfig;
record.lockedDomain = serverName;
// Use domain-specific NetworkProxy port if configured
if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
);
}
// Forward to NetworkProxy with domain-specific port
this.networkProxyBridge.forwardToNetworkProxy(
connectionId,
socket,
record,
chunk,
networkProxyPort,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
}
}
@ -531,6 +576,58 @@ export class ConnectionHandler {
// Extract SNI
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
// If allowSessionTicket is false and this is a ClientHello with no SNI, terminate the connection
if (this.settings.allowSessionTicket === false &&
this.tlsManager.isClientHello(chunk) &&
!serverName) {
// 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.`
);
// 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');
}
return;
}
}
// Lock the connection to the negotiated SNI.

View File

@ -22,27 +22,6 @@ export class SniHandler {
private static fragmentedBuffers: Map<string, Buffer> = new Map();
private static fragmentTimeout: number = 1000; // ms to wait for fragments before cleanup
/**
* Extract the client random value from a ClientHello message
*
* @param buffer - The buffer containing the ClientHello
* @returns The 32-byte client random or undefined if extraction fails
*/
private static extractClientRandom(buffer: Buffer): Buffer | undefined {
try {
if (!this.isClientHello(buffer) || buffer.length < 46) {
return undefined;
}
// In a ClientHello message, the client random starts at position 11
// after record header (5 bytes), handshake type (1 byte),
// handshake length (3 bytes), and client version (2 bytes)
return buffer.slice(11, 11 + 32);
} catch (error) {
return undefined;
}
}
/**
* Checks if a buffer contains a TLS handshake message (record type 22)
* @param buffer - The buffer to check
@ -1174,7 +1153,7 @@ export class SniHandler {
*
* The method uses connection tracking to handle fragmented ClientHello
* messages and various TLS 1.3 behaviors, including Chrome's connection
* racing patterns.
* racing patterns and tab reactivation behaviors.
*
* @param buffer - The buffer containing TLS data
* @param connectionInfo - Connection metadata (IPs and ports)
@ -1217,7 +1196,7 @@ export class SniHandler {
// Handle application data with cached SNI (for connection racing)
if (this.isTlsApplicationData(buffer)) {
// First check if explicit cachedSni was provided
// If explicit cachedSni was provided, use it
if (cachedSni) {
log(`Using provided cached SNI for application data: ${cachedSni}`);
return cachedSni;