feat(proxy): Implement WrappedSocket class for PROXY protocol support and update connection handling

This commit is contained in:
Juergen Kunz
2025-06-05 17:57:24 +00:00
parent 2a75e7c490
commit 18d79ac7e1
7 changed files with 813 additions and 51 deletions

View File

@ -11,6 +11,7 @@ import { HttpProxyBridge } from './http-proxy-bridge.js';
import { TimeoutManager } from './timeout-manager.js';
import { SharedRouteManager as RouteManager } from '../../core/routing/route-manager.js';
import { cleanupSocket, createIndependentSocketHandlers, setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
/**
* Handles new connection processing and setup logic with support for route-based configuration
@ -81,39 +82,52 @@ export class RouteConnectionHandler {
const remoteIP = socket.remoteAddress || '';
const localPort = socket.localPort || 0;
// Always wrap the socket to prepare for potential PROXY protocol
const wrappedSocket = new WrappedSocket(socket);
// If this is from a trusted proxy, log it
if (this.settings.proxyIPs?.includes(remoteIP)) {
logger.log('debug', `Connection from trusted proxy ${remoteIP}, PROXY protocol parsing will be enabled`, {
remoteIP,
component: 'route-handler'
});
}
// Validate IP against rate limits and connection limits
const ipValidation = this.securityManager.validateIP(remoteIP);
// Note: For wrapped sockets, this will use the underlying socket IP until PROXY protocol is parsed
const ipValidation = this.securityManager.validateIP(wrappedSocket.remoteAddress || '');
if (!ipValidation.allowed) {
logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' });
cleanupSocket(socket, `rejected-${ipValidation.reason}`, { immediate: true });
logger.log('warn', `Connection rejected`, { remoteIP: wrappedSocket.remoteAddress, reason: ipValidation.reason, component: 'route-handler' });
cleanupSocket(wrappedSocket.socket, `rejected-${ipValidation.reason}`, { immediate: true });
return;
}
// Create a new connection record
const record = this.connectionManager.createConnection(socket);
// Create a new connection record with the wrapped socket
const record = this.connectionManager.createConnection(wrappedSocket);
if (!record) {
// Connection was rejected due to limit - socket already destroyed by connection manager
return;
}
const connectionId = record.id;
// Apply socket optimizations
socket.setNoDelay(this.settings.noDelay);
// Apply socket optimizations (apply to underlying socket)
const underlyingSocket = wrappedSocket.socket;
underlyingSocket.setNoDelay(this.settings.noDelay);
// Apply keep-alive settings if enabled
if (this.settings.keepAlive) {
socket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
underlyingSocket.setKeepAlive(true, this.settings.keepAliveInitialDelay);
record.hasKeepAlive = true;
// Apply enhanced TCP keep-alive options if enabled
if (this.settings.enableKeepAliveProbes) {
try {
// These are platform-specific and may not be available
if ('setKeepAliveProbes' in socket) {
(socket as any).setKeepAliveProbes(10);
if ('setKeepAliveProbes' in underlyingSocket) {
(underlyingSocket as any).setKeepAliveProbes(10);
}
if ('setKeepAliveInterval' in socket) {
(socket as any).setKeepAliveInterval(1000);
if ('setKeepAliveInterval' in underlyingSocket) {
(underlyingSocket as any).setKeepAliveInterval(1000);
}
} catch (err) {
// Ignore errors - these are optional enhancements
@ -151,13 +165,13 @@ export class RouteConnectionHandler {
}
// Handle the connection - wait for initial data to determine if it's TLS
this.handleInitialData(socket, record);
this.handleInitialData(wrappedSocket, record);
}
/**
* Handle initial data from a connection to determine routing
*/
private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
private handleInitialData(socket: plugins.net.Socket | WrappedSocket, record: IConnectionRecord): void {
const connectionId = record.id;
const localPort = record.localPort;
let initialDataReceived = false;
@ -177,9 +191,11 @@ export class RouteConnectionHandler {
// If no routes require TLS handling and it's not port 443, route immediately
if (!needsTlsHandling && localPort !== 443) {
// Extract underlying socket for socket-utils functions
const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
// Set up proper socket handlers for immediate routing
setupSocketHandlers(
socket,
underlyingSocket,
(reason) => {
// Only cleanup if connection hasn't been fully established
// Check if outgoing connection exists and is connected
@ -206,7 +222,7 @@ export class RouteConnectionHandler {
);
// Route immediately for non-TLS connections
this.routeConnection(socket, record, '', undefined);
this.routeConnection(underlyingSocket, record, '', undefined);
return;
}
@ -363,7 +379,8 @@ export class RouteConnectionHandler {
}
// Find the appropriate route for this connection
this.routeConnection(socket, record, serverName, chunk);
const underlyingSocket = socket instanceof WrappedSocket ? socket.socket : socket;
this.routeConnection(underlyingSocket, record, serverName, chunk);
});
}