fix(PortProxy): Fix connection timeout and IP validation handling for PortProxy
This commit is contained in:
parent
8459e4013c
commit
296e1fcdc7
@ -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
|
||||
|
||||
|
@ -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.'
|
||||
}
|
||||
|
@ -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('');
|
||||
}
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user