better logging

This commit is contained in:
Juergen Kunz
2025-07-03 02:32:17 +00:00
parent 67aff4bb30
commit 5d011ba84c
18 changed files with 1604 additions and 410 deletions

View File

@ -17,6 +17,8 @@ import { WebSocketHandler } from './websocket-handler.js';
import { HttpRouter } from '../../routing/router/index.js';
import { cleanupSocket } from '../../core/utils/socket-utils.js';
import { FunctionCache } from './function-cache.js';
import { SecurityManager } from './security-manager.js';
import { connectionLogDeduplicator } from '../../core/utils/log-deduplicator.js';
/**
* HttpProxy provides a reverse proxy with TLS termination, WebSocket support,
@ -43,6 +45,7 @@ export class HttpProxy implements IMetricsTracker {
private router = new HttpRouter(); // Unified HTTP router
private routeManager: RouteManager;
private functionCache: FunctionCache;
private securityManager: SecurityManager;
// State tracking
public socketMap = new plugins.lik.ObjectMap<plugins.net.Socket>();
@ -113,6 +116,14 @@ export class HttpProxy implements IMetricsTracker {
maxCacheSize: this.options.functionCacheSize || 1000,
defaultTtl: this.options.functionCacheTtl || 5000
});
// Initialize security manager
this.securityManager = new SecurityManager(
this.logger,
[],
this.options.maxConnectionsPerIP || 100,
this.options.connectionRateLimitPerMinute || 300
);
// Initialize other components
this.certificateManager = new CertificateManager(this.options);
@ -269,14 +280,113 @@ export class HttpProxy implements IMetricsTracker {
*/
private setupConnectionTracking(): void {
this.httpsServer.on('connection', (connection: plugins.net.Socket) => {
// Check if max connections reached
let remoteIP = connection.remoteAddress || '';
const connectionId = Math.random().toString(36).substring(2, 15);
const isFromSmartProxy = this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1');
// For SmartProxy connections, wait for CLIENT_IP header
if (isFromSmartProxy) {
let headerBuffer = Buffer.alloc(0);
let headerParsed = false;
const parseHeader = (data: Buffer) => {
if (headerParsed) return data;
headerBuffer = Buffer.concat([headerBuffer, data]);
const headerStr = headerBuffer.toString();
const headerEnd = headerStr.indexOf('\r\n');
if (headerEnd !== -1) {
const header = headerStr.substring(0, headerEnd);
if (header.startsWith('CLIENT_IP:')) {
remoteIP = header.substring(10); // Extract IP after "CLIENT_IP:"
this.logger.debug(`Extracted client IP from SmartProxy: ${remoteIP}`);
}
headerParsed = true;
// Store the real IP on the connection
(connection as any)._realRemoteIP = remoteIP;
// Validate the real IP
const ipValidation = this.securityManager.validateIP(remoteIP);
if (!ipValidation.allowed) {
connectionLogDeduplicator.log(
'ip-rejected',
'warn',
`HttpProxy connection rejected (via SmartProxy)`,
{ remoteIP, reason: ipValidation.reason, component: 'http-proxy' },
remoteIP
);
connection.destroy();
return null;
}
// Track connection by real IP
this.securityManager.trackConnectionByIP(remoteIP, connectionId);
// Return remaining data after header
return headerBuffer.slice(headerEnd + 2);
}
return null;
};
// Override the first data handler to parse header
const originalEmit = connection.emit;
connection.emit = function(event: string, ...args: any[]) {
if (event === 'data' && !headerParsed) {
const remaining = parseHeader(args[0]);
if (remaining && remaining.length > 0) {
// Call original emit with remaining data
return originalEmit.apply(connection, ['data', remaining]);
} else if (headerParsed) {
// Header parsed but no remaining data
return true;
}
// Header not complete yet, suppress this data event
return true;
}
return originalEmit.apply(connection, [event, ...args]);
} as any;
} else {
// Direct connection - validate immediately
const ipValidation = this.securityManager.validateIP(remoteIP);
if (!ipValidation.allowed) {
connectionLogDeduplicator.log(
'ip-rejected',
'warn',
`HttpProxy connection rejected`,
{ remoteIP, reason: ipValidation.reason, component: 'http-proxy' },
remoteIP
);
connection.destroy();
return;
}
// Track connection by IP
this.securityManager.trackConnectionByIP(remoteIP, connectionId);
}
// Then check global max connections
if (this.socketMap.getArray().length >= this.options.maxConnections) {
this.logger.warn(`Max connections (${this.options.maxConnections}) reached, rejecting new connection`);
connectionLogDeduplicator.log(
'connection-rejected',
'warn',
'HttpProxy max connections reached',
{
reason: 'global-limit',
currentConnections: this.socketMap.getArray().length,
maxConnections: this.options.maxConnections,
component: 'http-proxy'
},
'http-proxy-global-limit'
);
connection.destroy();
return;
}
// Add connection to tracking
// Add connection to tracking with metadata
(connection as any)._connectionId = connectionId;
(connection as any)._remoteIP = remoteIP;
this.socketMap.add(connection);
this.connectedClients = this.socketMap.getArray().length;
@ -284,12 +394,12 @@ export class HttpProxy implements IMetricsTracker {
const localPort = connection.localPort || 0;
const remotePort = connection.remotePort || 0;
// If this connection is from a SmartProxy (usually indicated by it coming from localhost)
if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
// If this connection is from a SmartProxy
if (isFromSmartProxy) {
this.portProxyConnections++;
this.logger.debug(`New connection from SmartProxy (local: ${localPort}, remote: ${remotePort})`);
this.logger.debug(`New connection from SmartProxy for client ${remoteIP} (local: ${localPort}, remote: ${remotePort})`);
} else {
this.logger.debug(`New direct connection (local: ${localPort}, remote: ${remotePort})`);
this.logger.debug(`New direct connection from ${remoteIP} (local: ${localPort}, remote: ${remotePort})`);
}
// Setup connection cleanup handlers
@ -298,12 +408,19 @@ export class HttpProxy implements IMetricsTracker {
this.socketMap.remove(connection);
this.connectedClients = this.socketMap.getArray().length;
// Remove IP tracking
const connId = (connection as any)._connectionId;
const connIP = (connection as any)._realRemoteIP || (connection as any)._remoteIP;
if (connId && connIP) {
this.securityManager.removeConnectionByIP(connIP, connId);
}
// If this was a SmartProxy connection, decrement the counter
if (this.options.portProxyIntegration && connection.remoteAddress?.includes('127.0.0.1')) {
this.portProxyConnections--;
}
this.logger.debug(`Connection closed. ${this.connectedClients} connections remaining`);
this.logger.debug(`Connection closed from ${connIP || 'unknown'}. ${this.connectedClients} connections remaining`);
}
};
@ -480,6 +597,9 @@ export class HttpProxy implements IMetricsTracker {
// Certificate management cleanup is handled by SmartCertManager
// Flush any pending deduplicated logs
connectionLogDeduplicator.flushAll();
// Close the HTTPS server
return new Promise((resolve) => {
this.httpsServer.close(() => {