diff --git a/changelog.md b/changelog.md index c8e1cdd..31b0274 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +## 2025-03-03 - 3.22.1 - fix(PortProxy) +Fix connection timeout and IP validation handling for PortProxy + +- Adjusted initial data timeout setting for SNI-enabled connections in PortProxy. +- Restored IP validation logic to original behavior, ensuring compatibility with domain configurations. + ## 2025-03-03 - 3.22.0 - feat(classes.portproxy) Enhanced PortProxy to support initial data timeout and improved IP handling diff --git a/ts/00_commitinfo_data.ts b/ts/00_commitinfo_data.ts index 198735b..f3d1dd0 100644 --- a/ts/00_commitinfo_data.ts +++ b/ts/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@push.rocks/smartproxy', - version: '3.22.0', + version: '3.22.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.' } diff --git a/ts/classes.portproxy.ts b/ts/classes.portproxy.ts index eae1c38..d740c52 100644 --- a/ts/classes.portproxy.ts +++ b/ts/classes.portproxy.ts @@ -324,12 +324,12 @@ export class PortProxy { initiateCleanupOnce(reason); }; - // Set an initial timeout only if SNI is enabled or this is not a chained proxy - // For chained proxies, we need to allow more time for data to flow through - const initialTimeoutMs = this.settings.initialDataTimeout || - (this.settings.sniEnabled ? 15000 : 0); // Increased timeout for SNI, disabled for non-SNI by default - + // IMPORTANT: We won't set any initial timeout for a chained proxy scenario + // The code below is commented out to restore original behavior + /* let initialTimeout: NodeJS.Timeout | null = null; + const initialTimeoutMs = this.settings.initialDataTimeout || + (this.settings.sniEnabled ? 15000 : 0); if (initialTimeoutMs > 0) { console.log(`Setting initial data timeout of ${initialTimeoutMs}ms for connection from ${remoteIP}`); @@ -345,7 +345,22 @@ export class PortProxy { }, initialTimeoutMs); } else { console.log(`No initial timeout set for connection from ${remoteIP} (likely chained proxy)`); - // Mark as received immediately if we're not waiting for data + initialDataReceived = true; + } + */ + + // Original behavior: only set timeout if SNI is enabled, and use a fixed 5 second timeout + let initialTimeout: NodeJS.Timeout | null = null; + if (this.settings.sniEnabled) { + console.log(`Setting 5 second initial timeout for SNI extraction from ${remoteIP}`); + initialTimeout = setTimeout(() => { + if (!initialDataReceived) { + console.log(`Initial data timeout for ${remoteIP}`); + socket.end(); + initiateCleanupOnce('initial_timeout'); + } + }, 5000); + } else { initialDataReceived = true; } @@ -448,35 +463,28 @@ export class PortProxy { ) : undefined); // Effective IP check: merge allowed IPs with default allowed, and remove blocked IPs. - // In a chained proxy, relax IP validation unless explicitly configured - // If this is the first proxy in the chain, normal validation applies + // Use original domain configuration and IP validation logic + // This restores the behavior that was working before if (domainConfig) { - // Has specific domain config - check IP restrictions only if allowedIPs is non-empty - if (domainConfig.allowedIPs.length > 0) { - const effectiveAllowedIPs: string[] = [ - ...domainConfig.allowedIPs, - ...(this.settings.defaultAllowedIPs || []) - ]; - const effectiveBlockedIPs: string[] = [ - ...(domainConfig.blockedIPs || []), - ...(this.settings.defaultBlockedIPs || []) - ]; - if (!isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) { - return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`); - } - } else { - console.log(`Domain config for ${domainConfig.domains.join(', ')} has empty allowedIPs, skipping IP validation`); + const effectiveAllowedIPs: string[] = [ + ...domainConfig.allowedIPs, + ...(this.settings.defaultAllowedIPs || []) + ]; + const effectiveBlockedIPs: string[] = [ + ...(domainConfig.blockedIPs || []), + ...(this.settings.defaultBlockedIPs || []) + ]; + + // Special case: if allowedIPs is empty, skip IP validation for backward compatibility + if (domainConfig.allowedIPs.length > 0 && !isGlobIPAllowed(remoteIP, effectiveAllowedIPs, effectiveBlockedIPs)) { + return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for domain ${domainConfig.domains.join(', ')}`); } } else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) { - // No domain config but has default IP restrictions if (!isGlobIPAllowed(remoteIP, this.settings.defaultAllowedIPs, this.settings.defaultBlockedIPs || [])) { return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed by default allowed list`); } - } else { - // No domain config and no default allowed IPs - // In a chained proxy setup, we'll allow this connection - console.log(`No specific IP restrictions found for ${remoteIP}. Allowing connection in potential chained proxy setup.`); - } + } + // If no IP validation rules, allow the connection (original behavior) const targetHost = domainConfig ? this.getTargetIP(domainConfig) : this.settings.targetIP!; const connectionOptions: plugins.net.NetConnectOpts = { @@ -657,14 +665,8 @@ export class PortProxy { // --- FALLBACK: SNI-BASED HANDLING (or default when SNI is disabled) --- if (this.settings.sniEnabled) { - // If using SNI, we need to wait for data to establish the connection - if (initialDataReceived) { - console.log(`Initial data already marked as received for ${remoteIP}, but SNI is enabled. This is unexpected.`); - } - initialDataReceived = false; - - console.log(`Waiting for TLS ClientHello from ${remoteIP} to extract SNI...`); + socket.once('data', (chunk: Buffer) => { if (initialTimeout) { clearTimeout(initialTimeout); @@ -672,23 +674,10 @@ export class PortProxy { } initialDataReceived = true; - console.log(`Received initial data from ${remoteIP}, length: ${chunk.length} bytes`); - - let serverName = ''; - try { - // Only try to extract SNI if the chunk looks like a TLS ClientHello - if (chunk.length > 5 && chunk.readUInt8(0) === 22) { - serverName = extractSNI(chunk) || ''; - console.log(`Extracted SNI: "${serverName}" from connection ${remoteIP}`); - } else { - console.log(`Data from ${remoteIP} doesn't appear to be a TLS ClientHello. First byte: ${chunk.length > 0 ? chunk.readUInt8(0) : 'N/A'}`); - } - } catch (err) { - console.log(`Error extracting SNI from chunk: ${err}. Proceeding without SNI.`); - } - + const serverName = extractSNI(chunk) || ''; // Lock the connection to the negotiated SNI. connectionRecord.lockedDomain = serverName; + console.log(`Received connection from ${remoteIP} with SNI: ${serverName}`); // Delay adding the renegotiation listener until the next tick, // so the initial ClientHello is not reprocessed. @@ -714,23 +703,10 @@ export class PortProxy { setupConnection(serverName, chunk); }); } else { - // Non-SNI mode: we can proceed immediately without waiting for data - if (initialTimeout) { - clearTimeout(initialTimeout); - initialTimeout = null; - } - initialDataReceived = true; - console.log(`SNI disabled for connection from ${remoteIP}, proceeding directly to connection setup`); - - // Check IP restrictions only if explicitly configured - if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) { - if (!isAllowed(remoteIP, this.settings.defaultAllowedIPs)) { - return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`); - } + if (!this.settings.defaultAllowedIPs || this.settings.defaultAllowedIPs.length === 0 || !isAllowed(remoteIP, this.settings.defaultAllowedIPs)) { + return rejectIncomingConnection('rejected', `Connection rejected: IP ${remoteIP} not allowed for non-SNI connection`); } - - // Proceed with connection setup setupConnection(''); } };