fix(PortProxy): Improve TLS handshake buffering and enhance debug logging for SNI forwarding in PortProxy

This commit is contained in:
Philipp Kunz 2025-03-11 03:48:10 +00:00
parent 0ea0f02428
commit d6022c8f8a
3 changed files with 55 additions and 5 deletions

View File

@ -1,5 +1,13 @@
# Changelog # Changelog
## 2025-03-11 - 3.31.1 - fix(PortProxy)
Improve TLS handshake buffering and enhance debug logging for SNI forwarding in PortProxy
- Explicitly copy the initial TLS handshake data to prevent mutation before buffering
- Log buffered TLS handshake data with SNI information for better diagnostics
- Add detailed error logs on TLS connection failures, including server and domain config status
- Output additional debug messages during ClientHello forwarding to verify proper TLS handshake processing
## 2025-03-11 - 3.31.0 - feat(PortProxy) ## 2025-03-11 - 3.31.0 - feat(PortProxy)
Improve TLS handshake SNI extraction and add session resumption tracking in PortProxy Improve TLS handshake SNI extraction and add session resumption tracking in PortProxy

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartproxy', name: '@push.rocks/smartproxy',
version: '3.31.0', version: '3.31.1',
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.' description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, and dynamic routing with authentication options.'
} }

View File

@ -804,11 +804,22 @@ export class PortProxy {
// Add the temp handler to capture all incoming data during connection setup // Add the temp handler to capture all incoming data during connection setup
socket.on('data', tempDataHandler); socket.on('data', tempDataHandler);
// Add initial chunk to pending data if present // Add initial chunk to pending data if present - this is critical for SNI forwarding
if (initialChunk) { if (initialChunk) {
record.bytesReceived += initialChunk.length; // Make explicit copy of the buffer to ensure it doesn't get modified
record.pendingData.push(Buffer.from(initialChunk)); const initialDataCopy = Buffer.from(initialChunk);
record.pendingDataSize = initialChunk.length; record.bytesReceived += initialDataCopy.length;
record.pendingData.push(initialDataCopy);
record.pendingDataSize = initialDataCopy.length;
// Log TLS handshake for debug purposes
if (isTlsHandshake(initialChunk)) {
record.isTLS = true;
console.log(`[${connectionId}] Buffered TLS handshake data: ${initialDataCopy.length} bytes, SNI: ${serverName || 'none'}`);
}
} else if (record.isTLS) {
// This shouldn't happen, but log a warning if we have a TLS connection with no initial data
console.log(`[${connectionId}] WARNING: TLS connection without initial handshake data`);
} }
// Create the target socket but don't set up piping immediately // Create the target socket but don't set up piping immediately
@ -874,6 +885,11 @@ export class PortProxy {
// Log additional diagnostics // Log additional diagnostics
console.log(`[${connectionId}] Connection details - SNI: ${serverName || 'none'}, HasChunk: ${!!initialChunk}, ChunkSize: ${initialChunk ? initialChunk.length : 0}`); console.log(`[${connectionId}] Connection details - SNI: ${serverName || 'none'}, HasChunk: ${!!initialChunk}, ChunkSize: ${initialChunk ? initialChunk.length : 0}`);
// For TLS connections, provide even more detailed diagnostics
if (record.isTLS) {
console.log(`[${connectionId}] TLS connection failure details - TLS detected: ${record.isTLS}, Server: ${targetHost}:${connectionOptions.port}, Domain config: ${domainConfig ? 'Present' : 'Missing'}`);
}
// For connection refusal or timeouts, try a more aggressive error response // For connection refusal or timeouts, try a more aggressive error response
// This helps browsers quickly realize there's an issue rather than waiting // This helps browsers quickly realize there's an issue rather than waiting
if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT' || code === 'EHOSTUNREACH') { if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT' || code === 'EHOSTUNREACH') {
@ -999,12 +1015,29 @@ export class PortProxy {
// Flush all pending data to target // Flush all pending data to target
if (record.pendingData.length > 0) { if (record.pendingData.length > 0) {
const combinedData = Buffer.concat(record.pendingData); const combinedData = Buffer.concat(record.pendingData);
// Add critical debugging for SNI forwarding issues
if (record.isTLS && this.settings.enableTlsDebugLogging) {
console.log(`[${connectionId}] Forwarding TLS handshake data: ${combinedData.length} bytes, SNI: ${serverName || 'none'}`);
// Additional check to verify we're forwarding the ClientHello properly
if (combinedData[0] === 22) { // TLS handshake
console.log(`[${connectionId}] Initial data is a TLS handshake record`);
}
}
// Write the combined data to the target
targetSocket.write(combinedData, (err) => { targetSocket.write(combinedData, (err) => {
if (err) { if (err) {
console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`); console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`);
return this.initiateCleanupOnce(record, 'write_error'); return this.initiateCleanupOnce(record, 'write_error');
} }
if (record.isTLS) {
// Log successful forwarding of initial TLS data
console.log(`[${connectionId}] Successfully forwarded initial TLS data to backend`);
}
// Set up the renegotiation listener *before* piping if this is a TLS connection with SNI // Set up the renegotiation listener *before* piping if this is a TLS connection with SNI
if (serverName && record.isTLS) { if (serverName && record.isTLS) {
// This listener handles TLS renegotiation detection // This listener handles TLS renegotiation detection
@ -1956,6 +1989,15 @@ export class PortProxy {
) )
: undefined; : undefined;
// Enhanced logging to diagnose domain config selection issues
if (serverName && !domainConfig) {
console.log(`[${connectionId}] WARNING: No domain config found for SNI: ${serverName}`);
console.log(`[${connectionId}] Available domains:`,
this.settings.domainConfigs.map(config => config.domains.join(',')).join(' | '));
} else if (serverName && domainConfig) {
console.log(`[${connectionId}] Found domain config for SNI: ${serverName} -> ${domainConfig.domains.join(',')}`);
}
// For session resumption, ensure we use the domain config matching the resumed domain // For session resumption, ensure we use the domain config matching the resumed domain
// The resumed domain will be in serverName if this is a session resumption // The resumed domain will be in serverName if this is a session resumption
if (serverName && connectionRecord.lockedDomain === serverName && serverName !== '') { if (serverName && connectionRecord.lockedDomain === serverName && serverName !== '') {