diff --git a/ts/classes.portproxy.ts b/ts/classes.portproxy.ts index d35b2fc..169d949 100644 --- a/ts/classes.portproxy.ts +++ b/ts/classes.portproxy.ts @@ -164,6 +164,7 @@ interface ISNIExtractResult { hasSessionTicket?: boolean; // Whether a session ticket extension was found isResumption: boolean; // Whether this appears to be a session resumption resumedDomain?: string; // The domain associated with the session if resuming + partialExtract?: boolean; // Whether this was only a partial extraction (more data needed) } /** @@ -732,15 +733,37 @@ export class PortProxy { initialChunk?: Buffer, overridePort?: number ): void { + // Enhanced logging for initial connection troubleshooting + if (serverName) { + console.log(`[${connectionId}] Setting up direct connection for domain: ${serverName}`); + } else { + console.log(`[${connectionId}] Setting up direct connection without SNI`); + } + + // Log domain config details to help diagnose routing issues + if (domainConfig) { + console.log(`[${connectionId}] Using domain config: ${domainConfig.domains.join(', ')}`); + } else { + console.log(`[${connectionId}] No specific domain config found, using default settings`); + } + + // Ensure we maximize connection chances by setting appropriate timeouts + socket.setTimeout(30000); // 30 second initial connect timeout + // Existing connection setup logic const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!; const connectionOptions: plugins.net.NetConnectOpts = { host: targetHost, port: overridePort !== undefined ? overridePort : this.settings.toPort, + // Add connection timeout to ensure we don't hang indefinitely + timeout: 15000 // 15 second connection timeout }; if (this.settings.preserveSourceIP) { connectionOptions.localAddress = record.remoteIP.replace('::ffff:', ''); } + + console.log(`[${connectionId}] Connecting to backend: ${targetHost}:${connectionOptions.port}`); + // Pause the incoming socket to prevent buffer overflows socket.pause(); @@ -820,7 +843,7 @@ export class PortProxy { } } - // Setup specific error handler for connection phase + // Setup specific error handler for connection phase with enhanced retries targetSocket.once('error', (err) => { // This handler runs only once during the initial connection phase const code = (err as any).code; @@ -831,6 +854,7 @@ export class PortProxy { // Resume the incoming socket to prevent it from hanging socket.resume(); + // Add detailed logging for connection problems if (code === 'ECONNREFUSED') { console.log( `[${connectionId}] Target ${targetHost}:${connectionOptions.port} refused connection` @@ -846,6 +870,23 @@ export class PortProxy { } else if (code === 'EHOSTUNREACH') { console.log(`[${connectionId}] Host ${targetHost} is unreachable`); } + + // Log additional diagnostics + console.log(`[${connectionId}] Connection details - SNI: ${serverName || 'none'}, HasChunk: ${!!initialChunk}, ChunkSize: ${initialChunk ? initialChunk.length : 0}`); + + // For connection refusal or timeouts, try a more aggressive error response + // This helps browsers quickly realize there's an issue rather than waiting + if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT' || code === 'EHOSTUNREACH') { + try { + // Send a RST packet rather than a graceful close + // This signals to browsers to try a new connection immediately + socket.destroy(new Error(`Backend connection failed: ${code}`)); + console.log(`[${connectionId}] Forced connection termination to trigger immediate browser retry`); + return; // Skip normal cleanup + } catch (destroyErr) { + console.log(`[${connectionId}] Error during forced connection termination: ${destroyErr}`); + } + } // Clear any existing error handler after connection phase targetSocket.removeAllListeners('error'); @@ -1907,7 +1948,7 @@ export class PortProxy { } // If a forcedDomain is provided (port-based routing), use it; otherwise, use SNI-based lookup. - const domainConfig = forcedDomain + let domainConfig = forcedDomain ? forcedDomain : serverName ? this.settings.domainConfigs.find((config) => @@ -1915,6 +1956,22 @@ export class PortProxy { ) : undefined; + // 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 + if (serverName && connectionRecord.lockedDomain === serverName && serverName !== '') { + // Override domain config lookup for session resumption - crucial for certificate selection + const resumedDomainConfig = this.settings.domainConfigs.find((config) => + config.domains.some((d) => plugins.minimatch(serverName, d)) + ); + + if (resumedDomainConfig) { + domainConfig = resumedDomainConfig; + console.log(`[${connectionId}] Using domain config for resumed session: ${serverName}`); + } else { + console.log(`[${connectionId}] WARNING: Cannot find domain config for resumed domain: ${serverName}`); + } + } + // Save domain config in connection record connectionRecord.domainConfig = domainConfig; @@ -2079,12 +2136,17 @@ export class PortProxy { initialDataReceived = true; - // Try to extract SNI + // Try to extract SNI - with enhanced logging for troubleshooting let serverName = ''; + + // Record the chunk size for diagnostic purposes + console.log(`[${connectionId}] Received initial data: ${chunk.length} bytes`); if (isTlsHandshake(chunk)) { connectionRecord.isTLS = true; + console.log(`[${connectionId}] Detected TLS handshake`); + if (this.settings.enableTlsDebugLogging) { console.log( `[${connectionId}] Extracting SNI from TLS handshake, ${chunk.length} bytes` @@ -2098,10 +2160,25 @@ export class PortProxy { // This is a session resumption with a known domain serverName = sniInfo.resumedDomain; console.log(`[${connectionId}] TLS Session resumption detected for domain: ${serverName}`); + + // When resuming a session, explicitly set the domain in the record to ensure proper routing + // This is CRITICAL for ensuring we select the correct backend/certificate + connectionRecord.lockedDomain = serverName; + + // Force detailed logging for resumed sessions to help with troubleshooting + console.log(`[${connectionId}] Resuming TLS session for domain ${serverName} - will use original certificate`); } else { // Normal SNI extraction serverName = sniInfo?.serverName || ''; + + if (serverName) { + console.log(`[${connectionId}] Extracted SNI domain: ${serverName}`); + } else { + console.log(`[${connectionId}] No SNI found in TLS handshake`); + } } + } else { + console.log(`[${connectionId}] Non-TLS connection detected`); } // Lock the connection to the negotiated SNI.