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

@ -5,6 +5,7 @@ import { TimeoutManager } from './timeout-manager.js';
import { logger } from '../../core/utils/logger.js';
import { LifecycleComponent } from '../../core/utils/lifecycle-component.js';
import { cleanupSocket } from '../../core/utils/socket-utils.js';
import { WrappedSocket } from '../../core/models/wrapped-socket.js';
/**
* Manages connection lifecycle, tracking, and cleanup with performance optimizations
@ -53,8 +54,9 @@ export class ConnectionManager extends LifecycleComponent {
/**
* Create and track a new connection
* Accepts either a regular net.Socket or a WrappedSocket for transparent PROXY protocol support
*/
public createConnection(socket: plugins.net.Socket): IConnectionRecord | null {
public createConnection(socket: plugins.net.Socket | WrappedSocket): IConnectionRecord | null {
// Enforce connection limit
if (this.connectionRecords.size >= this.maxConnections) {
logger.log('warn', `Connection limit reached (${this.maxConnections}). Rejecting new connection.`, {
@ -282,22 +284,26 @@ export class ConnectionManager extends LifecycleComponent {
const cleanupPromises: Promise<void>[] = [];
if (record.incoming) {
// Extract underlying socket if it's a WrappedSocket
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
if (!record.incoming.writable || record.incoming.destroyed) {
// Socket is not active, clean up immediately
cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { immediate: true }));
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { immediate: true }));
} else {
// Socket is still active, allow graceful cleanup
cleanupPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
cleanupPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming`, { allowDrain: true, gracePeriod: 5000 }));
}
}
if (record.outgoing) {
// Extract underlying socket if it's a WrappedSocket
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
if (!record.outgoing.writable || record.outgoing.destroyed) {
// Socket is not active, clean up immediately
cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { immediate: true }));
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { immediate: true }));
} else {
// Socket is still active, allow graceful cleanup
cleanupPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
cleanupPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing`, { allowDrain: true, gracePeriod: 5000 }));
}
}
@ -570,11 +576,13 @@ export class ConnectionManager extends LifecycleComponent {
const shutdownPromises: Promise<void>[] = [];
if (record.incoming) {
shutdownPromises.push(cleanupSocket(record.incoming, `${record.id}-incoming-shutdown`, { immediate: true }));
const incomingSocket = record.incoming instanceof WrappedSocket ? record.incoming.socket : record.incoming;
shutdownPromises.push(cleanupSocket(incomingSocket, `${record.id}-incoming-shutdown`, { immediate: true }));
}
if (record.outgoing) {
shutdownPromises.push(cleanupSocket(record.outgoing, `${record.id}-outgoing-shutdown`, { immediate: true }));
const outgoingSocket = record.outgoing instanceof WrappedSocket ? record.outgoing.socket : record.outgoing;
shutdownPromises.push(cleanupSocket(outgoingSocket, `${record.id}-outgoing-shutdown`, { immediate: true }));
}
// Don't wait for shutdown cleanup in this batch processing