Compare commits

...

6 Commits

Author SHA1 Message Date
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 129 additions and 53 deletions

View File

@ -1,5 +1,26 @@
# Changelog
## 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.2",
"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.2',
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,70 @@ 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
// Don't even check for session resumption - be strict
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
// This helps browsers like Chrome properly recognize the error
const alertData = Buffer.from([
0x15, // Alert record type
0x03, 0x03, // TLS 1.2 version
0x00, 0x02, // Length
0x02, // Fatal alert level
0x40 // Handshake failure alert
]);
try {
socket.write(alertData);
} catch (err) {
// Ignore write errors, we're closing anyway
}
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
}
// Add a small delay before ending to allow alert to be sent
setTimeout(() => {
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 50);
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 +566,47 @@ 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
// Don't even check for session resumption - be strict
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
const alertData = Buffer.from([
0x15, // Alert record type
0x03, 0x03, // TLS 1.2 version
0x00, 0x02, // Length
0x02, // Fatal alert level
0x40 // Handshake failure alert
]);
try {
socket.write(alertData);
} catch (err) {
// Ignore write errors, we're closing anyway
}
if (record.incomingTerminationReason === null) {
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
}
// Add a small delay before ending to allow alert to be sent
setTimeout(() => {
socket.end();
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
}, 50);
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;