Compare commits
4 Commits
Author | SHA1 | Date | |
---|---|---|---|
fe60f88746 | |||
252a987344 | |||
677d30563f | |||
9aa747b5d4 |
14
changelog.md
14
changelog.md
@ -1,5 +1,19 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 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)
|
## 2025-03-14 - 4.0.0 - BREAKING CHANGE(core)
|
||||||
refactor: reorganize internal module structure to use 'classes.pp.*' modules
|
refactor: reorganize internal module structure to use 'classes.pp.*' modules
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "4.0.0",
|
"version": "4.1.1",
|
||||||
"private": false,
|
"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.",
|
"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",
|
"main": "dist_ts/index.js",
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '4.0.0',
|
version: '4.1.1',
|
||||||
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
@ -172,35 +172,46 @@ export class ConnectionHandler {
|
|||||||
// Extract SNI for domain-specific NetworkProxy handling
|
// Extract SNI for domain-specific NetworkProxy handling
|
||||||
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
|
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
|
||||||
|
|
||||||
if (serverName) {
|
// If allowSessionTicket is false and we can't determine SNI, terminate the connection
|
||||||
// If we got an SNI, check for domain-specific NetworkProxy settings
|
if (!serverName) {
|
||||||
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
|
console.log(
|
||||||
|
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
|
||||||
// Save domain config and SNI in connection record
|
`Terminating connection to force new TLS handshake with SNI.`
|
||||||
record.domainConfig = domainConfig;
|
);
|
||||||
record.lockedDomain = serverName;
|
if (record.incomingTerminationReason === null) {
|
||||||
|
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
||||||
// Use domain-specific NetworkProxy port if configured
|
this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, '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 +542,39 @@ export class ConnectionHandler {
|
|||||||
|
|
||||||
// Extract SNI
|
// Extract SNI
|
||||||
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
|
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) {
|
||||||
|
|
||||||
|
// Check if this is a session resumption
|
||||||
|
const resumptionInfo = this.tlsManager.handleSessionResumption(
|
||||||
|
chunk,
|
||||||
|
connectionId,
|
||||||
|
false // No 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'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(
|
||||||
|
record,
|
||||||
|
resumptionInfo.reason || 'session_ticket_blocked_no_sni'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the connection to the negotiated SNI.
|
// Lock the connection to the negotiated SNI.
|
||||||
|
@ -22,27 +22,6 @@ export class SniHandler {
|
|||||||
private static fragmentedBuffers: Map<string, Buffer> = new Map();
|
private static fragmentedBuffers: Map<string, Buffer> = new Map();
|
||||||
private static fragmentTimeout: number = 1000; // ms to wait for fragments before cleanup
|
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)
|
* Checks if a buffer contains a TLS handshake message (record type 22)
|
||||||
* @param buffer - The buffer to check
|
* @param buffer - The buffer to check
|
||||||
@ -1174,7 +1153,7 @@ export class SniHandler {
|
|||||||
*
|
*
|
||||||
* The method uses connection tracking to handle fragmented ClientHello
|
* The method uses connection tracking to handle fragmented ClientHello
|
||||||
* messages and various TLS 1.3 behaviors, including Chrome's connection
|
* 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 buffer - The buffer containing TLS data
|
||||||
* @param connectionInfo - Connection metadata (IPs and ports)
|
* @param connectionInfo - Connection metadata (IPs and ports)
|
||||||
@ -1217,7 +1196,7 @@ export class SniHandler {
|
|||||||
|
|
||||||
// Handle application data with cached SNI (for connection racing)
|
// Handle application data with cached SNI (for connection racing)
|
||||||
if (this.isTlsApplicationData(buffer)) {
|
if (this.isTlsApplicationData(buffer)) {
|
||||||
// First check if explicit cachedSni was provided
|
// If explicit cachedSni was provided, use it
|
||||||
if (cachedSni) {
|
if (cachedSni) {
|
||||||
log(`Using provided cached SNI for application data: ${cachedSni}`);
|
log(`Using provided cached SNI for application data: ${cachedSni}`);
|
||||||
return cachedSni;
|
return cachedSni;
|
||||||
|
Reference in New Issue
Block a user