Add tests for connect-disconnect and error handling in SmartProxy
This commit is contained in:
@ -176,8 +176,33 @@ export class RouteConnectionHandler {
|
||||
|
||||
// If no routes require TLS handling and it's not port 443, route immediately
|
||||
if (!needsTlsHandling && localPort !== 443) {
|
||||
// Set up error handler
|
||||
socket.on('error', this.connectionManager.handleError('incoming', record));
|
||||
// Set up proper socket handlers for immediate routing
|
||||
setupSocketHandlers(
|
||||
socket,
|
||||
(reason) => {
|
||||
// Only cleanup if connection hasn't been fully established
|
||||
// Check if outgoing connection exists and is connected
|
||||
if (!record.outgoing || record.outgoing.readyState !== 'open') {
|
||||
logger.log('debug', `Connection ${connectionId} closed during immediate routing: ${reason}`, {
|
||||
connectionId,
|
||||
remoteIP: record.remoteIP,
|
||||
reason,
|
||||
hasOutgoing: !!record.outgoing,
|
||||
outgoingState: record.outgoing?.readyState,
|
||||
component: 'route-handler'
|
||||
});
|
||||
|
||||
// If there's a pending outgoing connection, destroy it
|
||||
if (record.outgoing && !record.outgoing.destroyed) {
|
||||
record.outgoing.destroy();
|
||||
}
|
||||
|
||||
this.connectionManager.cleanupConnection(record, reason);
|
||||
}
|
||||
},
|
||||
undefined, // Use default timeout handler
|
||||
'immediate-route-client'
|
||||
);
|
||||
|
||||
// Route immediately for non-TLS connections
|
||||
this.routeConnection(socket, record, '', undefined);
|
||||
@ -221,6 +246,37 @@ export class RouteConnectionHandler {
|
||||
// Set up error handler
|
||||
socket.on('error', this.connectionManager.handleError('incoming', record));
|
||||
|
||||
// Add close/end handlers to catch immediate disconnections
|
||||
socket.once('close', () => {
|
||||
if (!initialDataReceived) {
|
||||
logger.log('warn', `Connection ${connectionId} closed before sending initial data`, {
|
||||
connectionId,
|
||||
remoteIP: record.remoteIP,
|
||||
component: 'route-handler'
|
||||
});
|
||||
if (initialTimeout) {
|
||||
clearTimeout(initialTimeout);
|
||||
initialTimeout = null;
|
||||
}
|
||||
this.connectionManager.cleanupConnection(record, 'closed_before_data');
|
||||
}
|
||||
});
|
||||
|
||||
socket.once('end', () => {
|
||||
if (!initialDataReceived) {
|
||||
logger.log('debug', `Connection ${connectionId} ended before sending initial data`, {
|
||||
connectionId,
|
||||
remoteIP: record.remoteIP,
|
||||
component: 'route-handler'
|
||||
});
|
||||
if (initialTimeout) {
|
||||
clearTimeout(initialTimeout);
|
||||
initialTimeout = null;
|
||||
}
|
||||
// Don't cleanup on 'end' - wait for 'close'
|
||||
}
|
||||
});
|
||||
|
||||
// First data handler to capture initial TLS handshake
|
||||
socket.once('data', (chunk: Buffer) => {
|
||||
// Clear the initial timeout since we've received data
|
||||
@ -927,107 +983,6 @@ export class RouteConnectionHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup improved error handling for the outgoing connection
|
||||
* @deprecated This method is no longer used - error handling is done in createSocketWithErrorHandler
|
||||
*/
|
||||
private setupOutgoingErrorHandler(
|
||||
connectionId: string,
|
||||
targetSocket: plugins.net.Socket,
|
||||
record: IConnectionRecord,
|
||||
socket: plugins.net.Socket,
|
||||
finalTargetHost: string,
|
||||
finalTargetPort: number
|
||||
): void {
|
||||
targetSocket.once('error', (err) => {
|
||||
// This handler runs only once during the initial connection phase
|
||||
const code = (err as any).code;
|
||||
logger.log('error',
|
||||
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${err.message} (${code})`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
targetPort: finalTargetPort,
|
||||
errorMessage: err.message,
|
||||
errorCode: code,
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
|
||||
// Resume the incoming socket to prevent it from hanging
|
||||
socket.resume();
|
||||
|
||||
// Log specific error types for easier debugging
|
||||
if (code === 'ECONNREFUSED') {
|
||||
logger.log('error',
|
||||
`Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
targetPort: finalTargetPort,
|
||||
recommendation: 'Check if the target service is running and listening on that port.',
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
} else if (code === 'ETIMEDOUT') {
|
||||
logger.log('error',
|
||||
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} timed out. Check network conditions, firewall rules, or if the target is too far away.`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
targetPort: finalTargetPort,
|
||||
recommendation: 'Check network conditions, firewall rules, or if the target is too far away.',
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
} else if (code === 'ECONNRESET') {
|
||||
logger.log('error',
|
||||
`Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} was reset. The target might have closed the connection abruptly.`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
targetPort: finalTargetPort,
|
||||
recommendation: 'The target might have closed the connection abruptly.',
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
} else if (code === 'EHOSTUNREACH') {
|
||||
logger.log('error',
|
||||
`Connection ${connectionId}: Host ${finalTargetHost} is unreachable. Check DNS settings, network routing, or firewall rules.`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
recommendation: 'Check DNS settings, network routing, or firewall rules.',
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
} else if (code === 'ENOTFOUND') {
|
||||
logger.log('error',
|
||||
`Connection ${connectionId}: DNS lookup failed for ${finalTargetHost}. Check your DNS settings or if the hostname is correct.`,
|
||||
{
|
||||
connectionId,
|
||||
targetHost: finalTargetHost,
|
||||
recommendation: 'Check your DNS settings or if the hostname is correct.',
|
||||
component: 'route-handler'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Clear any existing error handler after connection phase
|
||||
targetSocket.removeAllListeners('error');
|
||||
|
||||
// Re-add the normal error handler for established connections
|
||||
targetSocket.on('error', this.connectionManager.handleError('outgoing', record));
|
||||
|
||||
if (record.outgoingTerminationReason === null) {
|
||||
record.outgoingTerminationReason = 'connection_failed';
|
||||
this.connectionManager.incrementTerminationStat('outgoing', 'connection_failed');
|
||||
}
|
||||
|
||||
// Clean up the connection
|
||||
this.connectionManager.initiateCleanupOnce(record, `connection_failed_${code}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up a direct connection to the target
|
||||
@ -1090,6 +1045,16 @@ export class RouteConnectionHandler {
|
||||
host: finalTargetHost,
|
||||
onError: (error) => {
|
||||
// Connection failed - clean up everything immediately
|
||||
// Check if connection record is still valid (client might have disconnected)
|
||||
if (record.connectionClosed) {
|
||||
logger.log('debug', `Backend connection failed but client already disconnected for ${connectionId}`, {
|
||||
connectionId,
|
||||
errorCode: (error as any).code,
|
||||
component: 'route-handler'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('error',
|
||||
`Connection setup error for ${connectionId} to ${finalTargetHost}:${finalTargetPort}: ${error.message} (${(error as any).code})`,
|
||||
{
|
||||
@ -1117,10 +1082,12 @@ export class RouteConnectionHandler {
|
||||
}
|
||||
|
||||
// Resume the incoming socket to prevent it from hanging
|
||||
socket.resume();
|
||||
if (socket && !socket.destroyed) {
|
||||
socket.resume();
|
||||
}
|
||||
|
||||
// Clean up the incoming socket
|
||||
if (!socket.destroyed) {
|
||||
if (socket && !socket.destroyed) {
|
||||
socket.destroy();
|
||||
}
|
||||
|
||||
@ -1294,7 +1261,7 @@ export class RouteConnectionHandler {
|
||||
}
|
||||
});
|
||||
|
||||
// Only set up basic properties - everything else happens in onConnect
|
||||
// Set outgoing socket immediately so it can be cleaned up if client disconnects
|
||||
record.outgoing = targetSocket;
|
||||
record.outgoingStartTime = Date.now();
|
||||
|
||||
|
Reference in New Issue
Block a user