feat(proxy): Implement WrappedSocket class for PROXY protocol support and update connection handling
This commit is contained in:
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user