fix(logger): Replace raw console logging calls with structured logger usage across certificate management, connection handling, and route processing for improved observability.
This commit is contained in:
		| @@ -1,5 +1,13 @@ | |||||||
| # Changelog | # Changelog | ||||||
|  |  | ||||||
|  | ## 2025-05-19 - 19.3.11 - fix(logger) | ||||||
|  | Replace raw console logging calls with structured logger usage across certificate management, connection handling, and route processing for improved observability. | ||||||
|  |  | ||||||
|  | - Replaced console.log, console.warn, and console.error in SmartCertManager with logger.log for more consistent logging. | ||||||
|  | - Updated ConnectionManager and RouteConnectionHandler to log detailed connection events using a structured logger. | ||||||
|  | - Enhanced logging statements with contextual metadata such as connection IDs, remote IPs, target information, and component identifiers. | ||||||
|  | - Standardized log output across proxy modules to aid in debugging and monitoring. | ||||||
|  |  | ||||||
| ## 2025-05-19 - 19.3.10 - fix(certificate-manager, smart-proxy) | ## 2025-05-19 - 19.3.10 - fix(certificate-manager, smart-proxy) | ||||||
| Fix race condition in ACME certificate provisioning and refactor certificate manager initialization to defer provisioning until after port listeners are active | Fix race condition in ACME certificate provisioning and refactor certificate manager initialization to defer provisioning until after port listeners are active | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,6 @@ | |||||||
|  */ |  */ | ||||||
| export const commitinfo = { | export const commitinfo = { | ||||||
|   name: '@push.rocks/smartproxy', |   name: '@push.rocks/smartproxy', | ||||||
|   version: '19.3.10', |   version: '19.3.11', | ||||||
|   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' |   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import type { IRouteConfig, IRouteTls } from './models/route-types.js'; | |||||||
| import type { IAcmeOptions } from './models/interfaces.js'; | import type { IAcmeOptions } from './models/interfaces.js'; | ||||||
| import { CertStore } from './cert-store.js'; | import { CertStore } from './cert-store.js'; | ||||||
| import type { AcmeStateManager } from './acme-state-manager.js'; | import type { AcmeStateManager } from './acme-state-manager.js'; | ||||||
|  | import { logger } from '../../core/utils/logger.js'; | ||||||
|  |  | ||||||
| export interface ICertStatus { | export interface ICertStatus { | ||||||
|   domain: string; |   domain: string; | ||||||
| @@ -125,16 +126,16 @@ export class SmartCertManager { | |||||||
|        |        | ||||||
|       // Add challenge route once at initialization if not already active |       // Add challenge route once at initialization if not already active | ||||||
|       if (!this.challengeRouteActive) { |       if (!this.challengeRouteActive) { | ||||||
|         console.log('Adding ACME challenge route during initialization'); |         logger.log('info', 'Adding ACME challenge route during initialization', { component: 'certificate-manager' }); | ||||||
|         await this.addChallengeRoute(); |         await this.addChallengeRoute(); | ||||||
|       } else { |       } else { | ||||||
|         console.log('Challenge route already active from previous instance'); |         logger.log('info', 'Challenge route already active from previous instance', { component: 'certificate-manager' }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Skip automatic certificate provisioning during initialization |     // Skip automatic certificate provisioning during initialization | ||||||
|     // This will be called later after ports are listening |     // This will be called later after ports are listening | ||||||
|     console.log('Certificate manager initialized. Deferring certificate provisioning until after ports are listening.'); |     logger.log('info', 'Certificate manager initialized. Deferring certificate provisioning until after ports are listening.', { component: 'certificate-manager' }); | ||||||
|      |      | ||||||
|     // Start renewal timer |     // Start renewal timer | ||||||
|     this.startRenewalTimer(); |     this.startRenewalTimer(); | ||||||
| @@ -157,7 +158,7 @@ export class SmartCertManager { | |||||||
|         try { |         try { | ||||||
|           await this.provisionCertificate(route, true); // Allow concurrent since we're managing it here |           await this.provisionCertificate(route, true); // Allow concurrent since we're managing it here | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|           console.error(`Failed to provision certificate for route ${route.name}: ${error}`); |           logger.log('error', `Failed to provision certificate for route ${route.name}`, { routeName: route.name, error, component: 'certificate-manager' }); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } finally { |     } finally { | ||||||
| @@ -176,13 +177,13 @@ export class SmartCertManager { | |||||||
|      |      | ||||||
|     // Check if provisioning is already in progress (prevent concurrent provisioning) |     // Check if provisioning is already in progress (prevent concurrent provisioning) | ||||||
|     if (!allowConcurrent && this.isProvisioning) { |     if (!allowConcurrent && this.isProvisioning) { | ||||||
|       console.log(`Certificate provisioning already in progress, skipping ${route.name}`); |       logger.log('info', `Certificate provisioning already in progress, skipping ${route.name}`, { routeName: route.name, component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     const domains = this.extractDomainsFromRoute(route); |     const domains = this.extractDomainsFromRoute(route); | ||||||
|     if (domains.length === 0) { |     if (domains.length === 0) { | ||||||
|       console.warn(`Route ${route.name} has TLS termination but no domains`); |       logger.log('warn', `Route ${route.name} has TLS termination but no domains`, { routeName: route.name, component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -219,7 +220,7 @@ export class SmartCertManager { | |||||||
|     // Check if we already have a valid certificate |     // Check if we already have a valid certificate | ||||||
|     const existingCert = await this.certStore.getCertificate(routeName); |     const existingCert = await this.certStore.getCertificate(routeName); | ||||||
|     if (existingCert && this.isCertificateValid(existingCert)) { |     if (existingCert && this.isCertificateValid(existingCert)) { | ||||||
|       console.log(`Using existing valid certificate for ${primaryDomain}`); |       logger.log('info', `Using existing valid certificate for ${primaryDomain}`, { domain: primaryDomain, component: 'certificate-manager' }); | ||||||
|       await this.applyCertificate(primaryDomain, existingCert); |       await this.applyCertificate(primaryDomain, existingCert); | ||||||
|       this.updateCertStatus(routeName, 'valid', 'acme', existingCert); |       this.updateCertStatus(routeName, 'valid', 'acme', existingCert); | ||||||
|       return; |       return; | ||||||
| @@ -230,7 +231,7 @@ export class SmartCertManager { | |||||||
|                          this.globalAcmeDefaults?.renewThresholdDays ||  |                          this.globalAcmeDefaults?.renewThresholdDays ||  | ||||||
|                          30; |                          30; | ||||||
|      |      | ||||||
|     console.log(`Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`); |     logger.log('info', `Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`, { domains: domains.join(', '), renewThreshold, component: 'certificate-manager' }); | ||||||
|     this.updateCertStatus(routeName, 'pending', 'acme'); |     this.updateCertStatus(routeName, 'pending', 'acme'); | ||||||
|      |      | ||||||
|     try { |     try { | ||||||
| @@ -252,7 +253,7 @@ export class SmartCertManager { | |||||||
|                                     hasDnsChallenge; |                                     hasDnsChallenge; | ||||||
|        |        | ||||||
|       if (shouldIncludeWildcard) { |       if (shouldIncludeWildcard) { | ||||||
|         console.log(`Requesting wildcard certificate for ${primaryDomain} (DNS-01 available)`); |         logger.log('info', `Requesting wildcard certificate for ${primaryDomain} (DNS-01 available)`, { domain: primaryDomain, challengeType: 'DNS-01', component: 'certificate-manager' }); | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       // Use smartacme to get certificate with optional wildcard |       // Use smartacme to get certificate with optional wildcard | ||||||
| @@ -279,9 +280,9 @@ export class SmartCertManager { | |||||||
|       await this.applyCertificate(primaryDomain, certData); |       await this.applyCertificate(primaryDomain, certData); | ||||||
|       this.updateCertStatus(routeName, 'valid', 'acme', certData); |       this.updateCertStatus(routeName, 'valid', 'acme', certData); | ||||||
|        |        | ||||||
|       console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`); |       logger.log('info', `Successfully provisioned ACME certificate for ${primaryDomain}`, { domain: primaryDomain, component: 'certificate-manager' }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`); |       logger.log('error', `Failed to provision ACME certificate for ${primaryDomain}: ${error.message}`, { domain: primaryDomain, error: error.message, component: 'certificate-manager' }); | ||||||
|       this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message); |       this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message); | ||||||
|       throw error; |       throw error; | ||||||
|     } |     } | ||||||
| @@ -328,9 +329,9 @@ export class SmartCertManager { | |||||||
|       await this.applyCertificate(domain, certData); |       await this.applyCertificate(domain, certData); | ||||||
|       this.updateCertStatus(routeName, 'valid', 'static', certData); |       this.updateCertStatus(routeName, 'valid', 'static', certData); | ||||||
|        |        | ||||||
|       console.log(`Successfully loaded static certificate for ${domain}`); |       logger.log('info', `Successfully loaded static certificate for ${domain}`, { domain, component: 'certificate-manager' }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       console.error(`Failed to provision static certificate for ${domain}: ${error}`); |       logger.log('error', `Failed to provision static certificate for ${domain}: ${error.message}`, { domain, error: error.message, component: 'certificate-manager' }); | ||||||
|       this.updateCertStatus(routeName, 'error', 'static', undefined, error.message); |       this.updateCertStatus(routeName, 'error', 'static', undefined, error.message); | ||||||
|       throw error; |       throw error; | ||||||
|     } |     } | ||||||
| @@ -341,7 +342,7 @@ export class SmartCertManager { | |||||||
|    */ |    */ | ||||||
|   private async applyCertificate(domain: string, certData: ICertificateData): Promise<void> { |   private async applyCertificate(domain: string, certData: ICertificateData): Promise<void> { | ||||||
|     if (!this.httpProxy) { |     if (!this.httpProxy) { | ||||||
|       console.warn('HttpProxy not set, cannot apply certificate'); |       logger.log('warn', `HttpProxy not set, cannot apply certificate for domain ${domain}`, { domain, component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -398,13 +399,13 @@ export class SmartCertManager { | |||||||
|   private async addChallengeRoute(): Promise<void> { |   private async addChallengeRoute(): Promise<void> { | ||||||
|     // Check with state manager first |     // Check with state manager first | ||||||
|     if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) { |     if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) { | ||||||
|       console.log('Challenge route already active in global state, skipping'); |       logger.log('info', 'Challenge route already active in global state, skipping', { component: 'certificate-manager' }); | ||||||
|       this.challengeRouteActive = true; |       this.challengeRouteActive = true; | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     if (this.challengeRouteActive) { |     if (this.challengeRouteActive) { | ||||||
|       console.log('Challenge route already active locally, skipping'); |       logger.log('info', 'Challenge route already active locally, skipping', { component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -427,9 +428,9 @@ export class SmartCertManager { | |||||||
|         this.acmeStateManager.addChallengeRoute(challengeRoute); |         this.acmeStateManager.addChallengeRoute(challengeRoute); | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       console.log('ACME challenge route successfully added'); |       logger.log('info', 'ACME challenge route successfully added', { component: 'certificate-manager' }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       console.error('Failed to add challenge route:', error); |       logger.log('error', `Failed to add challenge route: ${error.message}`, { error: error.message, component: 'certificate-manager' }); | ||||||
|       if ((error as any).code === 'EADDRINUSE') { |       if ((error as any).code === 'EADDRINUSE') { | ||||||
|         throw new Error(`Port ${this.globalAcmeDefaults?.port || 80} is already in use for ACME challenges`); |         throw new Error(`Port ${this.globalAcmeDefaults?.port || 80} is already in use for ACME challenges`); | ||||||
|       } |       } | ||||||
| @@ -442,7 +443,7 @@ export class SmartCertManager { | |||||||
|    */ |    */ | ||||||
|   private async removeChallengeRoute(): Promise<void> { |   private async removeChallengeRoute(): Promise<void> { | ||||||
|     if (!this.challengeRouteActive) { |     if (!this.challengeRouteActive) { | ||||||
|       console.log('Challenge route not active, skipping removal'); |       logger.log('info', 'Challenge route not active, skipping removal', { component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -460,9 +461,9 @@ export class SmartCertManager { | |||||||
|         this.acmeStateManager.removeChallengeRoute('acme-challenge'); |         this.acmeStateManager.removeChallengeRoute('acme-challenge'); | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       console.log('ACME challenge route successfully removed'); |       logger.log('info', 'ACME challenge route successfully removed', { component: 'certificate-manager' }); | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       console.error('Failed to remove challenge route:', error); |       logger.log('error', `Failed to remove challenge route: ${error.message}`, { error: error.message, component: 'certificate-manager' }); | ||||||
|       // Reset the flag even on error to avoid getting stuck |       // Reset the flag even on error to avoid getting stuck | ||||||
|       this.challengeRouteActive = false; |       this.challengeRouteActive = false; | ||||||
|       throw error; |       throw error; | ||||||
| @@ -492,11 +493,11 @@ export class SmartCertManager { | |||||||
|         const cert = await this.certStore.getCertificate(routeName); |         const cert = await this.certStore.getCertificate(routeName); | ||||||
|          |          | ||||||
|         if (cert && !this.isCertificateValid(cert)) { |         if (cert && !this.isCertificateValid(cert)) { | ||||||
|           console.log(`Certificate for ${routeName} needs renewal`); |           logger.log('info', `Certificate for ${routeName} needs renewal`, { routeName, component: 'certificate-manager' }); | ||||||
|           try { |           try { | ||||||
|             await this.provisionCertificate(route); |             await this.provisionCertificate(route); | ||||||
|           } catch (error) { |           } catch (error) { | ||||||
|             console.error(`Failed to renew certificate for ${routeName}: ${error}`); |             logger.log('error', `Failed to renew certificate for ${routeName}: ${error.message}`, { routeName, error: error.message, component: 'certificate-manager' }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -621,7 +622,7 @@ export class SmartCertManager { | |||||||
|      |      | ||||||
|     // Always remove challenge route on shutdown |     // Always remove challenge route on shutdown | ||||||
|     if (this.challengeRoute) { |     if (this.challengeRoute) { | ||||||
|       console.log('Removing ACME challenge route during shutdown'); |       logger.log('info', 'Removing ACME challenge route during shutdown', { component: 'certificate-manager' }); | ||||||
|       await this.removeChallengeRoute(); |       await this.removeChallengeRoute(); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ import * as plugins from '../../plugins.js'; | |||||||
| import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js'; | import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js'; | ||||||
| import { SecurityManager } from './security-manager.js'; | import { SecurityManager } from './security-manager.js'; | ||||||
| import { TimeoutManager } from './timeout-manager.js'; | import { TimeoutManager } from './timeout-manager.js'; | ||||||
|  | import { logger } from '../../core/utils/logger.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Manages connection lifecycle, tracking, and cleanup |  * Manages connection lifecycle, tracking, and cleanup | ||||||
| @@ -97,7 +98,7 @@ export class ConnectionManager { | |||||||
|    */ |    */ | ||||||
|   public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void { |   public initiateCleanupOnce(record: IConnectionRecord, reason: string = 'normal'): void { | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log(`[${record.id}] Connection cleanup initiated for ${record.remoteIP} (${reason})`); |       logger.log('info', `Connection cleanup initiated`, { connectionId: record.id, remoteIP: record.remoteIP, reason, component: 'connection-manager' }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if ( |     if ( | ||||||
| @@ -139,7 +140,7 @@ export class ConnectionManager { | |||||||
|           // Reset the handler references |           // Reset the handler references | ||||||
|           record.renegotiationHandler = undefined; |           record.renegotiationHandler = undefined; | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           console.log(`[${record.id}] Error removing data handlers: ${err}`); |           logger.log('error', `Error removing data handlers for connection ${record.id}: ${err}`, { connectionId: record.id, error: err, component: 'connection-manager' }); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -160,16 +161,36 @@ export class ConnectionManager { | |||||||
|  |  | ||||||
|       // Log connection details |       // Log connection details | ||||||
|       if (this.settings.enableDetailedLogging) { |       if (this.settings.enableDetailedLogging) { | ||||||
|         console.log( |         logger.log('info',  | ||||||
|           `[${record.id}] Connection from ${record.remoteIP} on port ${record.localPort} terminated (${reason}).` + |           `Connection from ${record.remoteIP} on port ${record.localPort} terminated (${reason}). ` + | ||||||
|           `Duration: ${plugins.prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` + |           `Duration: ${plugins.prettyMs(duration)}, Bytes IN: ${bytesReceived}, OUT: ${bytesSent}, ` + | ||||||
|           `TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` + |           `TLS: ${record.isTLS ? 'Yes' : 'No'}, Keep-Alive: ${record.hasKeepAlive ? 'Yes' : 'No'}` + | ||||||
|           `${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` + |           `${record.usingNetworkProxy ? ', Using NetworkProxy' : ''}` + | ||||||
|             `${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}` |           `${record.domainSwitches ? `, Domain switches: ${record.domainSwitches}` : ''}`,  | ||||||
|  |           { | ||||||
|  |             connectionId: record.id, | ||||||
|  |             remoteIP: record.remoteIP, | ||||||
|  |             localPort: record.localPort, | ||||||
|  |             reason, | ||||||
|  |             duration: plugins.prettyMs(duration), | ||||||
|  |             bytes: { in: bytesReceived, out: bytesSent }, | ||||||
|  |             tls: record.isTLS, | ||||||
|  |             keepAlive: record.hasKeepAlive, | ||||||
|  |             usingNetworkProxy: record.usingNetworkProxy, | ||||||
|  |             domainSwitches: record.domainSwitches || 0, | ||||||
|  |             component: 'connection-manager' | ||||||
|  |           } | ||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         console.log( |         logger.log('info',  | ||||||
|           `[${record.id}] Connection from ${record.remoteIP} terminated (${reason}). Active connections: ${this.connectionRecords.size}` |           `Connection from ${record.remoteIP} terminated (${reason}). Active connections: ${this.connectionRecords.size}`,  | ||||||
|  |           { | ||||||
|  |             connectionId: record.id, | ||||||
|  |             remoteIP: record.remoteIP, | ||||||
|  |             reason, | ||||||
|  |             activeConnections: this.connectionRecords.size, | ||||||
|  |             component: 'connection-manager' | ||||||
|  |           } | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -189,7 +210,7 @@ export class ConnectionManager { | |||||||
|               socket.destroy(); |               socket.destroy(); | ||||||
|             } |             } | ||||||
|           } catch (err) { |           } catch (err) { | ||||||
|             console.log(`[${record.id}] Error destroying ${side} socket: ${err}`); |             logger.log('error', `Error destroying ${side} socket for connection ${record.id}: ${err}`, { connectionId: record.id, side, error: err, component: 'connection-manager' }); | ||||||
|           } |           } | ||||||
|         }, 1000); |         }, 1000); | ||||||
|  |  | ||||||
| @@ -199,13 +220,13 @@ export class ConnectionManager { | |||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|       console.log(`[${record.id}] Error closing ${side} socket: ${err}`); |       logger.log('error', `Error closing ${side} socket for connection ${record.id}: ${err}`, { connectionId: record.id, side, error: err, component: 'connection-manager' }); | ||||||
|       try { |       try { | ||||||
|         if (!socket.destroyed) { |         if (!socket.destroyed) { | ||||||
|           socket.destroy(); |           socket.destroy(); | ||||||
|         } |         } | ||||||
|       } catch (destroyErr) { |       } catch (destroyErr) { | ||||||
|         console.log(`[${record.id}] Error destroying ${side} socket: ${destroyErr}`); |         logger.log('error', `Error destroying ${side} socket for connection ${record.id}: ${destroyErr}`, { connectionId: record.id, side, error: destroyErr, component: 'connection-manager' }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -224,21 +245,36 @@ export class ConnectionManager { | |||||||
|  |  | ||||||
|       if (code === 'ECONNRESET') { |       if (code === 'ECONNRESET') { | ||||||
|         reason = 'econnreset'; |         reason = 'econnreset'; | ||||||
|         console.log( |         logger.log('warn', `ECONNRESET on ${side} connection from ${record.remoteIP}. Error: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, { | ||||||
|           `[${record.id}] ECONNRESET on ${side} side from ${record.remoteIP}: ${err.message}. ` + |           connectionId: record.id, | ||||||
|           `Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago` |           side, | ||||||
|         ); |           remoteIP: record.remoteIP, | ||||||
|  |           error: err.message, | ||||||
|  |           duration: plugins.prettyMs(connectionDuration), | ||||||
|  |           lastActivity: plugins.prettyMs(lastActivityAge), | ||||||
|  |           component: 'connection-manager' | ||||||
|  |         }); | ||||||
|       } else if (code === 'ETIMEDOUT') { |       } else if (code === 'ETIMEDOUT') { | ||||||
|         reason = 'etimedout'; |         reason = 'etimedout'; | ||||||
|         console.log( |         logger.log('warn', `ETIMEDOUT on ${side} connection from ${record.remoteIP}. Error: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, { | ||||||
|           `[${record.id}] ETIMEDOUT on ${side} side from ${record.remoteIP}: ${err.message}. ` + |           connectionId: record.id, | ||||||
|           `Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago` |           side, | ||||||
|         ); |           remoteIP: record.remoteIP, | ||||||
|  |           error: err.message, | ||||||
|  |           duration: plugins.prettyMs(connectionDuration), | ||||||
|  |           lastActivity: plugins.prettyMs(lastActivityAge), | ||||||
|  |           component: 'connection-manager' | ||||||
|  |         }); | ||||||
|       } else { |       } else { | ||||||
|         console.log( |         logger.log('error', `Error on ${side} connection from ${record.remoteIP}: ${err.message}. Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)}`, { | ||||||
|           `[${record.id}] Error on ${side} side from ${record.remoteIP}: ${err.message}. ` + |           connectionId: record.id, | ||||||
|           `Duration: ${plugins.prettyMs(connectionDuration)}, Last activity: ${plugins.prettyMs(lastActivityAge)} ago` |           side, | ||||||
|         ); |           remoteIP: record.remoteIP, | ||||||
|  |           error: err.message, | ||||||
|  |           duration: plugins.prettyMs(connectionDuration), | ||||||
|  |           lastActivity: plugins.prettyMs(lastActivityAge), | ||||||
|  |           component: 'connection-manager' | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (side === 'incoming' && record.incomingTerminationReason === null) { |       if (side === 'incoming' && record.incomingTerminationReason === null) { | ||||||
| @@ -259,7 +295,12 @@ export class ConnectionManager { | |||||||
|   public handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) { |   public handleClose(side: 'incoming' | 'outgoing', record: IConnectionRecord) { | ||||||
|     return () => { |     return () => { | ||||||
|       if (this.settings.enableDetailedLogging) { |       if (this.settings.enableDetailedLogging) { | ||||||
|         console.log(`[${record.id}] Connection closed on ${side} side from ${record.remoteIP}`); |         logger.log('info', `Connection closed on ${side} side`, { | ||||||
|  |           connectionId: record.id, | ||||||
|  |           side, | ||||||
|  |           remoteIP: record.remoteIP, | ||||||
|  |           component: 'connection-manager' | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       if (side === 'incoming' && record.incomingTerminationReason === null) { |       if (side === 'incoming' && record.incomingTerminationReason === null) { | ||||||
| @@ -321,11 +362,13 @@ export class ConnectionManager { | |||||||
|       if (inactivityTime > effectiveTimeout && !record.connectionClosed) { |       if (inactivityTime > effectiveTimeout && !record.connectionClosed) { | ||||||
|         // For keep-alive connections, issue a warning first |         // For keep-alive connections, issue a warning first | ||||||
|         if (record.hasKeepAlive && !record.inactivityWarningIssued) { |         if (record.hasKeepAlive && !record.inactivityWarningIssued) { | ||||||
|           console.log( |           logger.log('warn', `Keep-alive connection ${id} from ${record.remoteIP} inactive for ${plugins.prettyMs(inactivityTime)}. Will close in 10 minutes if no activity.`, { | ||||||
|             `[${id}] Warning: Keep-alive connection from ${record.remoteIP} inactive for ${ |             connectionId: id, | ||||||
|               plugins.prettyMs(inactivityTime) |             remoteIP: record.remoteIP, | ||||||
|             }. Will close in 10 minutes if no activity.` |             inactiveFor: plugins.prettyMs(inactivityTime), | ||||||
|           ); |             closureWarning: '10 minutes', | ||||||
|  |             component: 'connection-manager' | ||||||
|  |           }); | ||||||
|  |  | ||||||
|           // Set warning flag and add grace period |           // Set warning flag and add grace period | ||||||
|           record.inactivityWarningIssued = true; |           record.inactivityWarningIssued = true; | ||||||
| @@ -337,27 +380,30 @@ export class ConnectionManager { | |||||||
|               record.outgoing.write(Buffer.alloc(0)); |               record.outgoing.write(Buffer.alloc(0)); | ||||||
|  |  | ||||||
|               if (this.settings.enableDetailedLogging) { |               if (this.settings.enableDetailedLogging) { | ||||||
|                 console.log(`[${id}] Sent probe packet to test keep-alive connection`); |                 logger.log('info', `Sent probe packet to test keep-alive connection ${id}`, { connectionId: id, component: 'connection-manager' }); | ||||||
|               } |               } | ||||||
|             } catch (err) { |             } catch (err) { | ||||||
|               console.log(`[${id}] Error sending probe packet: ${err}`); |               logger.log('error', `Error sending probe packet to connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' }); | ||||||
|             } |             } | ||||||
|           } |           } | ||||||
|         } else { |         } else { | ||||||
|           // For non-keep-alive or after warning, close the connection |           // For non-keep-alive or after warning, close the connection | ||||||
|           console.log( |           logger.log('warn', `Closing inactive connection ${id} from ${record.remoteIP} (inactive for ${plugins.prettyMs(inactivityTime)}, keep-alive: ${record.hasKeepAlive ? 'Yes' : 'No'})`, { | ||||||
|             `[${id}] Inactivity check: No activity on connection from ${record.remoteIP} ` + |             connectionId: id, | ||||||
|             `for ${plugins.prettyMs(inactivityTime)}.` + |             remoteIP: record.remoteIP, | ||||||
|             (record.hasKeepAlive ? ' Despite keep-alive being enabled.' : '') |             inactiveFor: plugins.prettyMs(inactivityTime), | ||||||
|           ); |             hasKeepAlive: record.hasKeepAlive, | ||||||
|  |             component: 'connection-manager' | ||||||
|  |           }); | ||||||
|           this.cleanupConnection(record, 'inactivity'); |           this.cleanupConnection(record, 'inactivity'); | ||||||
|         } |         } | ||||||
|       } else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) { |       } else if (inactivityTime <= effectiveTimeout && record.inactivityWarningIssued) { | ||||||
|         // If activity detected after warning, clear the warning |         // If activity detected after warning, clear the warning | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `Connection ${id} activity detected after inactivity warning`, { | ||||||
|             `[${id}] Connection activity detected after inactivity warning, resetting warning` |             connectionId: id, | ||||||
|           ); |             component: 'connection-manager' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|         record.inactivityWarningIssued = false; |         record.inactivityWarningIssued = false; | ||||||
|       } |       } | ||||||
| @@ -369,11 +415,12 @@ export class ConnectionManager { | |||||||
|         !record.connectionClosed && |         !record.connectionClosed && | ||||||
|         now - record.outgoingClosedTime > 120000 |         now - record.outgoingClosedTime > 120000 | ||||||
|       ) { |       ) { | ||||||
|         console.log( |         logger.log('warn', `Parity check: Connection ${id} from ${record.remoteIP} has incoming socket still active ${plugins.prettyMs(now - record.outgoingClosedTime)} after outgoing socket closed`, { | ||||||
|           `[${id}] Parity check: Incoming socket for ${record.remoteIP} still active ${ |           connectionId: id, | ||||||
|             plugins.prettyMs(now - record.outgoingClosedTime) |           remoteIP: record.remoteIP, | ||||||
|           } after outgoing closed.` |           timeElapsed: plugins.prettyMs(now - record.outgoingClosedTime), | ||||||
|         ); |           component: 'connection-manager' | ||||||
|  |         }); | ||||||
|         this.cleanupConnection(record, 'parity_check'); |         this.cleanupConnection(record, 'parity_check'); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -406,7 +453,7 @@ export class ConnectionManager { | |||||||
|             record.outgoing.end(); |             record.outgoing.end(); | ||||||
|           } |           } | ||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           console.log(`Error during graceful connection end for ${id}: ${err}`); |           logger.log('error', `Error during graceful end of connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' }); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -433,7 +480,7 @@ export class ConnectionManager { | |||||||
|               } |               } | ||||||
|             } |             } | ||||||
|           } catch (err) { |           } catch (err) { | ||||||
|             console.log(`Error during forced connection destruction for ${id}: ${err}`); |             logger.log('error', `Error during forced destruction of connection ${id}: ${err}`, { connectionId: id, error: err, component: 'connection-manager' }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import * as plugins from '../../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
| import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js'; | import type { IConnectionRecord, ISmartProxyOptions } from './models/interfaces.js'; | ||||||
|  | import { logger } from '../../core/utils/logger.js'; | ||||||
| // Route checking functions have been removed | // Route checking functions have been removed | ||||||
| import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-types.js'; | import type { IRouteConfig, IRouteAction, IRouteContext } from './models/route-types.js'; | ||||||
| import { ConnectionManager } from './connection-manager.js'; | import { ConnectionManager } from './connection-manager.js'; | ||||||
| @@ -83,7 +84,7 @@ export class RouteConnectionHandler { | |||||||
|     // Validate IP against rate limits and connection limits |     // Validate IP against rate limits and connection limits | ||||||
|     const ipValidation = this.securityManager.validateIP(remoteIP); |     const ipValidation = this.securityManager.validateIP(remoteIP); | ||||||
|     if (!ipValidation.allowed) { |     if (!ipValidation.allowed) { | ||||||
|       console.log(`Connection rejected from ${remoteIP}: ${ipValidation.reason}`); |       logger.log('warn', `Connection rejected`, { remoteIP, reason: ipValidation.reason, component: 'route-handler' }); | ||||||
|       socket.end(); |       socket.end(); | ||||||
|       socket.destroy(); |       socket.destroy(); | ||||||
|       return; |       return; | ||||||
| @@ -114,21 +115,35 @@ export class RouteConnectionHandler { | |||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           // Ignore errors - these are optional enhancements |           // Ignore errors - these are optional enhancements | ||||||
|           if (this.settings.enableDetailedLogging) { |           if (this.settings.enableDetailedLogging) { | ||||||
|             console.log(`[${connectionId}] Enhanced TCP keep-alive settings not supported: ${err}`); |             logger.log('warn', `Enhanced TCP keep-alive settings not supported`, { connectionId, error: err, component: 'route-handler' }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log( |       logger.log('info',  | ||||||
|         `[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` + |         `New connection from ${remoteIP} on port ${localPort}. ` + | ||||||
|         `Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` + |         `Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` + | ||||||
|           `Active connections: ${this.connectionManager.getConnectionCount()}` |         `Active connections: ${this.connectionManager.getConnectionCount()}`,  | ||||||
|  |         { | ||||||
|  |           connectionId, | ||||||
|  |           remoteIP, | ||||||
|  |           localPort, | ||||||
|  |           keepAlive: record.hasKeepAlive ? 'Enabled' : 'Disabled', | ||||||
|  |           activeConnections: this.connectionManager.getConnectionCount(), | ||||||
|  |           component: 'route-handler' | ||||||
|  |         } | ||||||
|       ); |       ); | ||||||
|     } else { |     } else { | ||||||
|       console.log( |       logger.log('info',  | ||||||
|         `New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionManager.getConnectionCount()}` |         `New connection from ${remoteIP} on port ${localPort}. Active connections: ${this.connectionManager.getConnectionCount()}`,  | ||||||
|  |         { | ||||||
|  |           remoteIP, | ||||||
|  |           localPort, | ||||||
|  |           activeConnections: this.connectionManager.getConnectionCount(), | ||||||
|  |           component: 'route-handler' | ||||||
|  |         } | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -147,14 +162,20 @@ export class RouteConnectionHandler { | |||||||
|     // Set an initial timeout for handshake data |     // Set an initial timeout for handshake data | ||||||
|     let initialTimeout: NodeJS.Timeout | null = setTimeout(() => { |     let initialTimeout: NodeJS.Timeout | null = setTimeout(() => { | ||||||
|       if (!initialDataReceived) { |       if (!initialDataReceived) { | ||||||
|         console.log( |         logger.log('warn', `No initial data received from ${record.remoteIP} after ${this.settings.initialDataTimeout}ms for connection ${connectionId}`, { | ||||||
|           `[${connectionId}] Initial data warning (${this.settings.initialDataTimeout}ms) for connection from ${record.remoteIP}` |           connectionId, | ||||||
|         ); |           timeout: this.settings.initialDataTimeout, | ||||||
|  |           remoteIP: record.remoteIP, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|  |  | ||||||
|         // Add a grace period |         // Add a grace period | ||||||
|         setTimeout(() => { |         setTimeout(() => { | ||||||
|           if (!initialDataReceived) { |           if (!initialDataReceived) { | ||||||
|             console.log(`[${connectionId}] Final initial data timeout after grace period`); |             logger.log('warn', `Final initial data timeout after grace period for connection ${connectionId}`, { | ||||||
|  |               connectionId, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             if (record.incomingTerminationReason === null) { |             if (record.incomingTerminationReason === null) { | ||||||
|               record.incomingTerminationReason = 'initial_timeout'; |               record.incomingTerminationReason = 'initial_timeout'; | ||||||
|               this.connectionManager.incrementTerminationStat('incoming', 'initial_timeout'); |               this.connectionManager.incrementTerminationStat('incoming', 'initial_timeout'); | ||||||
| @@ -187,10 +208,11 @@ export class RouteConnectionHandler { | |||||||
|  |  | ||||||
|       // Block non-TLS connections on port 443 |       // Block non-TLS connections on port 443 | ||||||
|       if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) { |       if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) { | ||||||
|         console.log( |         logger.log('warn', `Non-TLS connection ${connectionId} detected on port 443. Terminating connection - only TLS traffic is allowed on standard HTTPS port.`, { | ||||||
|           `[${connectionId}] Non-TLS connection detected on port 443. ` + |           connectionId, | ||||||
|             `Terminating connection - only TLS traffic is allowed on standard HTTPS port.` |           message: 'Terminating connection - only TLS traffic is allowed on standard HTTPS port.', | ||||||
|         ); |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         if (record.incomingTerminationReason === null) { |         if (record.incomingTerminationReason === null) { | ||||||
|           record.incomingTerminationReason = 'non_tls_blocked'; |           record.incomingTerminationReason = 'non_tls_blocked'; | ||||||
|           this.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked'); |           this.connectionManager.incrementTerminationStat('incoming', 'non_tls_blocked'); | ||||||
| @@ -223,7 +245,10 @@ export class RouteConnectionHandler { | |||||||
|  |  | ||||||
|           // Check if we should reject connections without SNI |           // Check if we should reject connections without SNI | ||||||
|           if (!serverName && this.settings.allowSessionTicket === false) { |           if (!serverName && this.settings.allowSessionTicket === false) { | ||||||
|             console.log(`[${connectionId}] No SNI detected in TLS ClientHello; sending TLS alert.`); |             logger.log('warn', `No SNI detected in TLS ClientHello for connection ${connectionId}; sending TLS alert`, { | ||||||
|  |               connectionId, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             if (record.incomingTerminationReason === null) { |             if (record.incomingTerminationReason === null) { | ||||||
|               record.incomingTerminationReason = 'session_ticket_blocked_no_sni'; |               record.incomingTerminationReason = 'session_ticket_blocked_no_sni'; | ||||||
|               this.connectionManager.incrementTerminationStat( |               this.connectionManager.incrementTerminationStat( | ||||||
| @@ -245,7 +270,11 @@ export class RouteConnectionHandler { | |||||||
|           } |           } | ||||||
|  |  | ||||||
|           if (this.settings.enableDetailedLogging) { |           if (this.settings.enableDetailedLogging) { | ||||||
|             console.log(`[${connectionId}] TLS connection with SNI: ${serverName || '(empty)'}`); |             logger.log('info', `TLS connection with SNI`, { | ||||||
|  |               connectionId, | ||||||
|  |               serverName: serverName || '(empty)', | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -278,12 +307,18 @@ export class RouteConnectionHandler { | |||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     if (!routeMatch) { |     if (!routeMatch) { | ||||||
|       console.log( |       logger.log('warn', `No route found for ${serverName || 'connection'} on port ${localPort} (connection: ${connectionId})`, { | ||||||
|         `[${connectionId}] No route found for ${serverName || 'connection'} on port ${localPort}` |         connectionId, | ||||||
|       ); |         serverName: serverName || 'connection', | ||||||
|  |         localPort, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|  |  | ||||||
|       // No matching route, use default/fallback handling |       // No matching route, use default/fallback handling | ||||||
|       console.log(`[${connectionId}] Using default route handling for connection`); |       logger.log('info', `Using default route handling for connection ${connectionId}`, { | ||||||
|  |         connectionId, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|  |  | ||||||
|       // Check default security settings |       // Check default security settings | ||||||
|       const defaultSecuritySettings = this.settings.defaults?.security; |       const defaultSecuritySettings = this.settings.defaults?.security; | ||||||
| @@ -296,7 +331,11 @@ export class RouteConnectionHandler { | |||||||
|           ); |           ); | ||||||
|  |  | ||||||
|           if (!isAllowed) { |           if (!isAllowed) { | ||||||
|             console.log(`[${connectionId}] IP ${remoteIP} not in default allowed list`); |             logger.log('warn', `IP ${remoteIP} not in default allowed list for connection ${connectionId}`, { | ||||||
|  |               connectionId, | ||||||
|  |               remoteIP, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             socket.end(); |             socket.end(); | ||||||
|             this.connectionManager.cleanupConnection(record, 'ip_blocked'); |             this.connectionManager.cleanupConnection(record, 'ip_blocked'); | ||||||
|             return; |             return; | ||||||
| @@ -321,7 +360,10 @@ export class RouteConnectionHandler { | |||||||
|         ); |         ); | ||||||
|       } else { |       } else { | ||||||
|         // No default target available, terminate the connection |         // No default target available, terminate the connection | ||||||
|         console.log(`[${connectionId}] No default target configured. Closing connection.`); |         logger.log('warn', `No default target configured for connection ${connectionId}. Closing connection`, { | ||||||
|  |           connectionId, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         socket.end(); |         socket.end(); | ||||||
|         this.connectionManager.cleanupConnection(record, 'no_default_target'); |         this.connectionManager.cleanupConnection(record, 'no_default_target'); | ||||||
|         return; |         return; | ||||||
| @@ -332,11 +374,13 @@ export class RouteConnectionHandler { | |||||||
|     const route = routeMatch.route; |     const route = routeMatch.route; | ||||||
|  |  | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log( |       logger.log('info', `Route matched`, { | ||||||
|         `[${connectionId}] Route matched: "${route.name || 'unnamed'}" for ${ |         connectionId, | ||||||
|           serverName || 'connection' |         routeName: route.name || 'unnamed', | ||||||
|         } on port ${localPort}` |         serverName: serverName || 'connection', | ||||||
|       ); |         localPort, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -356,7 +400,11 @@ export class RouteConnectionHandler { | |||||||
|         return; |         return; | ||||||
|  |  | ||||||
|       default: |       default: | ||||||
|         console.log(`[${connectionId}] Unknown action type: ${(route.action as any).type}`); |         logger.log('error', `Unknown action type '${(route.action as any).type}' for connection ${connectionId}`, { | ||||||
|  |           connectionId, | ||||||
|  |           actionType: (route.action as any).type, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         socket.end(); |         socket.end(); | ||||||
|         this.connectionManager.cleanupConnection(record, 'unknown_action'); |         this.connectionManager.cleanupConnection(record, 'unknown_action'); | ||||||
|     } |     } | ||||||
| @@ -381,30 +429,36 @@ export class RouteConnectionHandler { | |||||||
|        |        | ||||||
|       // Log the connection for monitoring purposes |       // Log the connection for monitoring purposes | ||||||
|       if (this.settings.enableDetailedLogging) { |       if (this.settings.enableDetailedLogging) { | ||||||
|         console.log( |         logger.log('info', `NFTables forwarding (kernel-level)`, { | ||||||
|           `[${record.id}] NFTables forwarding (kernel-level): ` + |           connectionId: record.id, | ||||||
|             `${record.remoteIP}:${socket.remotePort} -> ${socket.localAddress}:${record.localPort}` + |           source: `${record.remoteIP}:${socket.remotePort}`, | ||||||
|             ` (Route: "${route.name || 'unnamed'}", Domain: ${record.lockedDomain || 'n/a'})` |           destination: `${socket.localAddress}:${record.localPort}`, | ||||||
|         ); |           routeName: route.name || 'unnamed', | ||||||
|  |           domain: record.lockedDomain || 'n/a', | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|       } else { |       } else { | ||||||
|         console.log( |         logger.log('info', `NFTables forwarding`, { | ||||||
|           `[${record.id}] NFTables forwarding: ${record.remoteIP} -> port ${ |           connectionId: record.id, | ||||||
|             record.localPort |           remoteIP: record.remoteIP, | ||||||
|           } (Route: "${route.name || 'unnamed'}")` |           localPort: record.localPort, | ||||||
|         ); |           routeName: route.name || 'unnamed', | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Additional NFTables-specific logging if configured |       // Additional NFTables-specific logging if configured | ||||||
|       if (action.nftables) { |       if (action.nftables) { | ||||||
|         const nftConfig = action.nftables; |         const nftConfig = action.nftables; | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `NFTables config`, { | ||||||
|             `[${record.id}] NFTables config: ` + |             connectionId: record.id, | ||||||
|               `protocol=${nftConfig.protocol || 'tcp'}, ` + |             protocol: nftConfig.protocol || 'tcp', | ||||||
|               `preserveSourceIP=${nftConfig.preserveSourceIP || false}, ` + |             preserveSourceIP: nftConfig.preserveSourceIP || false, | ||||||
|               `priority=${nftConfig.priority || 'default'}, ` + |             priority: nftConfig.priority || 'default', | ||||||
|               `maxRate=${nftConfig.maxRate || 'unlimited'}` |             maxRate: nftConfig.maxRate || 'unlimited', | ||||||
|           ); |             component: 'route-handler' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|        |        | ||||||
| @@ -419,7 +473,10 @@ export class RouteConnectionHandler { | |||||||
|  |  | ||||||
|     // We should have a target configuration for forwarding |     // We should have a target configuration for forwarding | ||||||
|     if (!action.target) { |     if (!action.target) { | ||||||
|       console.log(`[${connectionId}] Forward action missing target configuration`); |       logger.log('error', `Forward action missing target configuration for connection ${connectionId}`, { | ||||||
|  |         connectionId, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|       socket.end(); |       socket.end(); | ||||||
|       this.connectionManager.cleanupConnection(record, 'missing_target'); |       this.connectionManager.cleanupConnection(record, 'missing_target'); | ||||||
|       return; |       return; | ||||||
| @@ -447,14 +504,18 @@ export class RouteConnectionHandler { | |||||||
|       try { |       try { | ||||||
|         targetHost = action.target.host(routeContext); |         targetHost = action.target.host(routeContext); | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `Dynamic host resolved to ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost} for connection ${connectionId}`, { | ||||||
|             `[${connectionId}] Dynamic host resolved to: ${ |             connectionId, | ||||||
|               Array.isArray(targetHost) ? targetHost.join(', ') : targetHost |             targetHost: Array.isArray(targetHost) ? targetHost.join(', ') : targetHost, | ||||||
|             }` |             component: 'route-handler' | ||||||
|           ); |           }); | ||||||
|         } |         } | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         console.log(`[${connectionId}] Error in host mapping function: ${err}`); |         logger.log('error', `Error in host mapping function for connection ${connectionId}: ${err}`, { | ||||||
|  |           connectionId, | ||||||
|  |           error: err, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         socket.end(); |         socket.end(); | ||||||
|         this.connectionManager.cleanupConnection(record, 'host_mapping_error'); |         this.connectionManager.cleanupConnection(record, 'host_mapping_error'); | ||||||
|         return; |         return; | ||||||
| @@ -474,14 +535,21 @@ export class RouteConnectionHandler { | |||||||
|       try { |       try { | ||||||
|         targetPort = action.target.port(routeContext); |         targetPort = action.target.port(routeContext); | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `Dynamic port mapping from ${record.localPort} to ${targetPort} for connection ${connectionId}`, { | ||||||
|             `[${connectionId}] Dynamic port mapping: ${record.localPort} -> ${targetPort}` |             connectionId, | ||||||
|           ); |             sourcePort: record.localPort, | ||||||
|  |             targetPort, | ||||||
|  |             component: 'route-handler' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|         // Store the resolved target port in the context for potential future use |         // Store the resolved target port in the context for potential future use | ||||||
|         routeContext.targetPort = targetPort; |         routeContext.targetPort = targetPort; | ||||||
|       } catch (err) { |       } catch (err) { | ||||||
|         console.log(`[${connectionId}] Error in port mapping function: ${err}`); |         logger.log('error', `Error in port mapping function for connection ${connectionId}: ${err}`, { | ||||||
|  |           connectionId, | ||||||
|  |           error: err, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         socket.end(); |         socket.end(); | ||||||
|         this.connectionManager.cleanupConnection(record, 'port_mapping_error'); |         this.connectionManager.cleanupConnection(record, 'port_mapping_error'); | ||||||
|         return; |         return; | ||||||
| @@ -503,7 +571,12 @@ export class RouteConnectionHandler { | |||||||
|         case 'passthrough': |         case 'passthrough': | ||||||
|           // For TLS passthrough, just forward directly |           // For TLS passthrough, just forward directly | ||||||
|           if (this.settings.enableDetailedLogging) { |           if (this.settings.enableDetailedLogging) { | ||||||
|             console.log(`[${connectionId}] Using TLS passthrough to ${selectedHost}:${targetPort}`); |             logger.log('info', `Using TLS passthrough to ${selectedHost}:${targetPort} for connection ${connectionId}`, { | ||||||
|  |               connectionId, | ||||||
|  |               targetHost: selectedHost, | ||||||
|  |               targetPort, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|           } |           } | ||||||
|  |  | ||||||
|           return this.setupDirectConnection( |           return this.setupDirectConnection( | ||||||
| @@ -521,9 +594,11 @@ export class RouteConnectionHandler { | |||||||
|           // For TLS termination, use HttpProxy |           // For TLS termination, use HttpProxy | ||||||
|           if (this.httpProxyBridge.getHttpProxy()) { |           if (this.httpProxyBridge.getHttpProxy()) { | ||||||
|             if (this.settings.enableDetailedLogging) { |             if (this.settings.enableDetailedLogging) { | ||||||
|               console.log( |               logger.log('info', `Using HttpProxy for TLS termination to ${Array.isArray(action.target.host) ? action.target.host.join(', ') : action.target.host} for connection ${connectionId}`, { | ||||||
|                 `[${connectionId}] Using HttpProxy for TLS termination to ${action.target.host}` |                 connectionId, | ||||||
|               ); |                 targetHost: action.target.host, | ||||||
|  |                 component: 'route-handler' | ||||||
|  |               }); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // If we have an initial chunk with TLS data, start processing it |             // If we have an initial chunk with TLS data, start processing it | ||||||
| @@ -540,12 +615,18 @@ export class RouteConnectionHandler { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             // This shouldn't normally happen - we should have TLS data at this point |             // This shouldn't normally happen - we should have TLS data at this point | ||||||
|             console.log(`[${connectionId}] TLS termination route without TLS data`); |             logger.log('error', `TLS termination route without TLS data for connection ${connectionId}`, { | ||||||
|  |               connectionId, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             socket.end(); |             socket.end(); | ||||||
|             this.connectionManager.cleanupConnection(record, 'tls_error'); |             this.connectionManager.cleanupConnection(record, 'tls_error'); | ||||||
|             return; |             return; | ||||||
|           } else { |           } else { | ||||||
|             console.log(`[${connectionId}] HttpProxy not available for TLS termination`); |             logger.log('error', `HttpProxy not available for TLS termination for connection ${connectionId}`, { | ||||||
|  |               connectionId, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             socket.end(); |             socket.end(); | ||||||
|             this.connectionManager.cleanupConnection(record, 'no_http_proxy'); |             this.connectionManager.cleanupConnection(record, 'no_http_proxy'); | ||||||
|             return; |             return; | ||||||
| @@ -558,9 +639,11 @@ export class RouteConnectionHandler { | |||||||
|       if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) { |       if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) { | ||||||
|         // Forward non-TLS connections to HttpProxy if configured |         // Forward non-TLS connections to HttpProxy if configured | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `Using HttpProxy for non-TLS connection ${connectionId} on port ${record.localPort}`, { | ||||||
|             `[${connectionId}] Using HttpProxy for non-TLS connection on port ${record.localPort}` |             connectionId, | ||||||
|           ); |             port: record.localPort, | ||||||
|  |             component: 'route-handler' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         this.httpProxyBridge.forwardToHttpProxy( |         this.httpProxyBridge.forwardToHttpProxy( | ||||||
| @@ -575,9 +658,12 @@ export class RouteConnectionHandler { | |||||||
|       } else { |       } else { | ||||||
|         // Basic forwarding |         // Basic forwarding | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `Using basic forwarding to ${Array.isArray(action.target.host) ? action.target.host.join(', ') : action.target.host}:${action.target.port} for connection ${connectionId}`, { | ||||||
|             `[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}` |             connectionId, | ||||||
|           ); |             targetHost: action.target.host, | ||||||
|  |             targetPort: action.target.port, | ||||||
|  |             component: 'route-handler' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Get the appropriate host value |         // Get the appropriate host value | ||||||
| @@ -633,7 +719,10 @@ export class RouteConnectionHandler { | |||||||
|   ): void { |   ): void { | ||||||
|     // For TLS connections, we can't do redirects at the TCP level |     // For TLS connections, we can't do redirects at the TCP level | ||||||
|     if (record.isTLS) { |     if (record.isTLS) { | ||||||
|       console.log(`[${record.id}] Cannot redirect TLS connection at TCP level`); |       logger.log('warn', `Cannot redirect TLS connection ${record.id} at TCP level`, { | ||||||
|  |         connectionId: record.id, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|       socket.end(); |       socket.end(); | ||||||
|       this.connectionManager.cleanupConnection(record, 'tls_redirect_error'); |       this.connectionManager.cleanupConnection(record, 'tls_redirect_error'); | ||||||
|       return; |       return; | ||||||
| @@ -658,9 +747,11 @@ export class RouteConnectionHandler { | |||||||
|     const connectionId = record.id; |     const connectionId = record.id; | ||||||
|  |  | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log( |       logger.log('info', `Blocking connection ${connectionId} based on route '${route.name || 'unnamed'}'`, { | ||||||
|         `[${connectionId}] Blocking connection based on route "${route.name || 'unnamed'}"` |         connectionId, | ||||||
|       ); |         routeName: route.name || 'unnamed', | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Simply close the connection |     // Simply close the connection | ||||||
| @@ -699,8 +790,16 @@ export class RouteConnectionHandler { | |||||||
|     targetSocket.once('error', (err) => { |     targetSocket.once('error', (err) => { | ||||||
|       // This handler runs only once during the initial connection phase |       // This handler runs only once during the initial connection phase | ||||||
|       const code = (err as any).code; |       const code = (err as any).code; | ||||||
|       console.log( |       logger.log('error',  | ||||||
|         `[${connectionId}] Connection setup error to ${finalTargetHost}:${finalTargetPort}: ${err.message} (${code})` |         `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 |       // Resume the incoming socket to prevent it from hanging | ||||||
| @@ -708,29 +807,57 @@ export class RouteConnectionHandler { | |||||||
|  |  | ||||||
|       // Log specific error types for easier debugging |       // Log specific error types for easier debugging | ||||||
|       if (code === 'ECONNREFUSED') { |       if (code === 'ECONNREFUSED') { | ||||||
|         console.log( |         logger.log('error',  | ||||||
|           `[${connectionId}] Target ${finalTargetHost}:${finalTargetPort} refused connection. ` + |           `Connection ${connectionId}: Target ${finalTargetHost}:${finalTargetPort} refused connection. Check if the target service is running and listening on that port.`,  | ||||||
|           `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') { |       } else if (code === 'ETIMEDOUT') { | ||||||
|         console.log( |         logger.log('error',  | ||||||
|           `[${connectionId}] Connection to ${finalTargetHost}:${finalTargetPort} timed out. ` + |           `Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} timed out. Check network conditions, firewall rules, or if the target is too far away.`,  | ||||||
|           `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') { |       } else if (code === 'ECONNRESET') { | ||||||
|         console.log( |         logger.log('error',  | ||||||
|           `[${connectionId}] Connection to ${finalTargetHost}:${finalTargetPort} was reset. ` + |           `Connection ${connectionId} to ${finalTargetHost}:${finalTargetPort} was reset. The target might have closed the connection abruptly.`,  | ||||||
|           `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') { |       } else if (code === 'EHOSTUNREACH') { | ||||||
|         console.log( |         logger.log('error',  | ||||||
|           `[${connectionId}] Host ${finalTargetHost} is unreachable. ` + |           `Connection ${connectionId}: Host ${finalTargetHost} is unreachable. Check DNS settings, network routing, or firewall rules.`,  | ||||||
|           `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') { |       } else if (code === 'ENOTFOUND') { | ||||||
|         console.log( |         logger.log('error',  | ||||||
|           `[${connectionId}] DNS lookup failed for ${finalTargetHost}. ` + |           `Connection ${connectionId}: DNS lookup failed for ${finalTargetHost}. Check your DNS settings or if the hostname is correct.`,  | ||||||
|           `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' | ||||||
|  |           } | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -779,9 +906,12 @@ export class RouteConnectionHandler { | |||||||
|     record.targetPort = finalTargetPort; |     record.targetPort = finalTargetPort; | ||||||
|  |  | ||||||
|     if (this.settings.enableDetailedLogging) { |     if (this.settings.enableDetailedLogging) { | ||||||
|       console.log( |       logger.log('info', `Setting up direct connection ${connectionId} to ${finalTargetHost}:${finalTargetPort}`, { | ||||||
|         `[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}` |         connectionId, | ||||||
|       ); |         targetHost: finalTargetHost, | ||||||
|  |         targetPort: finalTargetPort, | ||||||
|  |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Setup connection options |     // Setup connection options | ||||||
| @@ -826,9 +956,11 @@ export class RouteConnectionHandler { | |||||||
|         } catch (err) { |         } catch (err) { | ||||||
|           // Ignore errors - these are optional enhancements |           // Ignore errors - these are optional enhancements | ||||||
|           if (this.settings.enableDetailedLogging) { |           if (this.settings.enableDetailedLogging) { | ||||||
|             console.log( |             logger.log('warn', `Enhanced TCP keep-alive not supported for outgoing socket on connection ${connectionId}: ${err}`, { | ||||||
|               `[${connectionId}] Enhanced TCP keep-alive not supported for outgoing socket: ${err}` |               connectionId, | ||||||
|             ); |               error: err, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -848,22 +980,23 @@ export class RouteConnectionHandler { | |||||||
|     socket.on('timeout', () => { |     socket.on('timeout', () => { | ||||||
|       // For keep-alive connections, just log a warning instead of closing |       // For keep-alive connections, just log a warning instead of closing | ||||||
|       if (record.hasKeepAlive) { |       if (record.hasKeepAlive) { | ||||||
|         console.log( |         logger.log('warn', `Timeout event on incoming keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`, { | ||||||
|           `[${connectionId}] Timeout event on incoming keep-alive connection from ${ |           connectionId, | ||||||
|             record.remoteIP |           remoteIP: record.remoteIP, | ||||||
|           } after ${plugins.prettyMs( |           timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000), | ||||||
|             this.settings.socketTimeout || 3600000 |           status: 'Connection preserved', | ||||||
|           )}. Connection preserved.` |           component: 'route-handler' | ||||||
|         ); |         }); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // For non-keep-alive connections, proceed with normal cleanup |       // For non-keep-alive connections, proceed with normal cleanup | ||||||
|       console.log( |       logger.log('warn', `Timeout on incoming side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`, { | ||||||
|         `[${connectionId}] Timeout on incoming side from ${ |         connectionId, | ||||||
|           record.remoteIP |         remoteIP: record.remoteIP, | ||||||
|         } after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}` |         timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000), | ||||||
|       ); |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|       if (record.incomingTerminationReason === null) { |       if (record.incomingTerminationReason === null) { | ||||||
|         record.incomingTerminationReason = 'timeout'; |         record.incomingTerminationReason = 'timeout'; | ||||||
|         this.connectionManager.incrementTerminationStat('incoming', 'timeout'); |         this.connectionManager.incrementTerminationStat('incoming', 'timeout'); | ||||||
| @@ -874,22 +1007,23 @@ export class RouteConnectionHandler { | |||||||
|     targetSocket.on('timeout', () => { |     targetSocket.on('timeout', () => { | ||||||
|       // For keep-alive connections, just log a warning instead of closing |       // For keep-alive connections, just log a warning instead of closing | ||||||
|       if (record.hasKeepAlive) { |       if (record.hasKeepAlive) { | ||||||
|         console.log( |         logger.log('warn', `Timeout event on outgoing keep-alive connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}. Connection preserved.`, { | ||||||
|           `[${connectionId}] Timeout event on outgoing keep-alive connection from ${ |           connectionId, | ||||||
|             record.remoteIP |           remoteIP: record.remoteIP, | ||||||
|           } after ${plugins.prettyMs( |           timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000), | ||||||
|             this.settings.socketTimeout || 3600000 |           status: 'Connection preserved', | ||||||
|           )}. Connection preserved.` |           component: 'route-handler' | ||||||
|         ); |         }); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // For non-keep-alive connections, proceed with normal cleanup |       // For non-keep-alive connections, proceed with normal cleanup | ||||||
|       console.log( |       logger.log('warn', `Timeout on outgoing side for connection ${connectionId} from ${record.remoteIP} after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}`, { | ||||||
|         `[${connectionId}] Timeout on outgoing side from ${ |         connectionId, | ||||||
|           record.remoteIP |         remoteIP: record.remoteIP, | ||||||
|         } after ${plugins.prettyMs(this.settings.socketTimeout || 3600000)}` |         timeout: plugins.prettyMs(this.settings.socketTimeout || 3600000), | ||||||
|       ); |         component: 'route-handler' | ||||||
|  |       }); | ||||||
|       if (record.outgoingTerminationReason === null) { |       if (record.outgoingTerminationReason === null) { | ||||||
|         record.outgoingTerminationReason = 'timeout'; |         record.outgoingTerminationReason = 'timeout'; | ||||||
|         this.connectionManager.incrementTerminationStat('outgoing', 'timeout'); |         this.connectionManager.incrementTerminationStat('outgoing', 'timeout'); | ||||||
| @@ -909,9 +1043,12 @@ export class RouteConnectionHandler { | |||||||
|     // Wait for the outgoing connection to be ready before setting up piping |     // Wait for the outgoing connection to be ready before setting up piping | ||||||
|     targetSocket.once('connect', () => { |     targetSocket.once('connect', () => { | ||||||
|       if (this.settings.enableDetailedLogging) { |       if (this.settings.enableDetailedLogging) { | ||||||
|         console.log( |         logger.log('info', `Connection ${connectionId} established to target ${finalTargetHost}:${finalTargetPort}`, { | ||||||
|           `[${connectionId}] Connection established to target: ${finalTargetHost}:${finalTargetPort}` |           connectionId, | ||||||
|         ); |           targetHost: finalTargetHost, | ||||||
|  |           targetPort: finalTargetPort, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Clear the initial connection error handler |       // Clear the initial connection error handler | ||||||
| @@ -933,7 +1070,11 @@ export class RouteConnectionHandler { | |||||||
|         // Write pending data immediately |         // Write pending data immediately | ||||||
|         targetSocket.write(combinedData, (err) => { |         targetSocket.write(combinedData, (err) => { | ||||||
|           if (err) { |           if (err) { | ||||||
|             console.log(`[${connectionId}] Error writing pending data to target: ${err.message}`); |             logger.log('error', `Error writing pending data to target for connection ${connectionId}: ${err.message}`, { | ||||||
|  |               connectionId, | ||||||
|  |               error: err.message, | ||||||
|  |               component: 'route-handler' | ||||||
|  |             }); | ||||||
|             return this.connectionManager.initiateCleanupOnce(record, 'write_error'); |             return this.connectionManager.initiateCleanupOnce(record, 'write_error'); | ||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
| @@ -954,15 +1095,17 @@ export class RouteConnectionHandler { | |||||||
|       }); |       }); | ||||||
|  |  | ||||||
|       // Log successful connection |       // Log successful connection | ||||||
|       console.log( |       logger.log('info',  | ||||||
|         `Connection established: ${record.remoteIP} -> ${finalTargetHost}:${finalTargetPort}` + |         `Connection established: ${record.remoteIP} -> ${finalTargetHost}:${finalTargetPort}` + | ||||||
|           `${ |         `${serverName ? ` (SNI: ${serverName})` : record.lockedDomain ? ` (Domain: ${record.lockedDomain})` : ''}`,  | ||||||
|             serverName |         { | ||||||
|               ? ` (SNI: ${serverName})` |           remoteIP: record.remoteIP, | ||||||
|               : record.lockedDomain |           targetHost: finalTargetHost, | ||||||
|               ? ` (Domain: ${record.lockedDomain})` |           targetPort: finalTargetPort, | ||||||
|               : '' |           sni: serverName || undefined, | ||||||
|           }` |           domain: !serverName && record.lockedDomain ? record.lockedDomain : undefined, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         } | ||||||
|       ); |       ); | ||||||
|  |  | ||||||
|       // Add TLS renegotiation handler if needed |       // Add TLS renegotiation handler if needed | ||||||
| @@ -990,17 +1133,21 @@ export class RouteConnectionHandler { | |||||||
|         socket.on('data', renegotiationHandler); |         socket.on('data', renegotiationHandler); | ||||||
|  |  | ||||||
|         if (this.settings.enableDetailedLogging) { |         if (this.settings.enableDetailedLogging) { | ||||||
|           console.log( |           logger.log('info', `TLS renegotiation handler installed for connection ${connectionId} with SNI ${serverName}`, { | ||||||
|             `[${connectionId}] TLS renegotiation handler installed for SNI domain: ${serverName}` |             connectionId, | ||||||
|           ); |             serverName, | ||||||
|  |             component: 'route-handler' | ||||||
|  |           }); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       // Set connection timeout |       // Set connection timeout | ||||||
|       record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => { |       record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => { | ||||||
|         console.log( |         logger.log('warn', `Connection ${connectionId} from ${record.remoteIP} exceeded max lifetime, forcing cleanup`, { | ||||||
|           `[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.` |           connectionId, | ||||||
|         ); |           remoteIP: record.remoteIP, | ||||||
|  |           component: 'route-handler' | ||||||
|  |         }); | ||||||
|         this.connectionManager.initiateCleanupOnce(record, reason); |         this.connectionManager.initiateCleanupOnce(record, reason); | ||||||
|       }); |       }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| import * as plugins from '../../plugins.js'; | import * as plugins from '../../plugins.js'; | ||||||
|  | import { logger } from '../../core/utils/logger.js'; | ||||||
|  |  | ||||||
| // Importing required components | // Importing required components | ||||||
| import { ConnectionManager } from './connection-manager.js'; | import { ConnectionManager } from './connection-manager.js'; | ||||||
| @@ -239,7 +240,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|     ); |     ); | ||||||
|      |      | ||||||
|     if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) { |     if (autoRoutes.length === 0 && !this.hasStaticCertRoutes()) { | ||||||
|       console.log('No routes require certificate management'); |       logger.log('info', 'No routes require certificate management', { component: 'certificate-manager' }); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -256,7 +257,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|         useProduction: this.settings.acme.useProduction || false, |         useProduction: this.settings.acme.useProduction || false, | ||||||
|         port: this.settings.acme.port || 80 |         port: this.settings.acme.port || 80 | ||||||
|       }; |       }; | ||||||
|       console.log(`Using top-level ACME configuration with email: ${acmeOptions.email}`); |       logger.log('info', `Using top-level ACME configuration with email: ${acmeOptions.email}`, { component: 'certificate-manager' }); | ||||||
|     } else if (autoRoutes.length > 0) { |     } else if (autoRoutes.length > 0) { | ||||||
|       // Check for route-level ACME config |       // Check for route-level ACME config | ||||||
|       const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email); |       const routeWithAcme = autoRoutes.find(r => r.action.tls?.acme?.email); | ||||||
| @@ -267,7 +268,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|           useProduction: routeAcme.useProduction || false, |           useProduction: routeAcme.useProduction || false, | ||||||
|           port: routeAcme.challengePort || 80 |           port: routeAcme.challengePort || 80 | ||||||
|         }; |         }; | ||||||
|         console.log(`Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`); |         logger.log('info', `Using route-level ACME configuration from route '${routeWithAcme.name}' with email: ${acmeOptions.email}`, { component: 'certificate-manager' }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|      |      | ||||||
| @@ -305,7 +306,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|   public async start() { |   public async start() { | ||||||
|     // Don't start if already shutting down |     // Don't start if already shutting down | ||||||
|     if (this.isShuttingDown) { |     if (this.isShuttingDown) { | ||||||
|       console.log("Cannot start SmartProxy while it's shutting down"); |       logger.log('warn', "Cannot start SmartProxy while it's in the shutdown process"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -332,9 +333,9 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|     const allWarnings = [...configWarnings, ...acmeWarnings]; |     const allWarnings = [...configWarnings, ...acmeWarnings]; | ||||||
|      |      | ||||||
|     if (allWarnings.length > 0) { |     if (allWarnings.length > 0) { | ||||||
|       console.log("Configuration warnings:"); |       logger.log('warn', `${allWarnings.length} configuration warnings found`, { count: allWarnings.length }); | ||||||
|       for (const warning of allWarnings) { |       for (const warning of allWarnings) { | ||||||
|         console.log(` - ${warning}`); |         logger.log('warn', `${warning}`); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -353,7 +354,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|      |      | ||||||
|     // Now that ports are listening, provision any required certificates |     // Now that ports are listening, provision any required certificates | ||||||
|     if (this.certManager) { |     if (this.certManager) { | ||||||
|       console.log('Starting certificate provisioning now that ports are ready'); |       logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' }); | ||||||
|       await this.certManager.provisionAllCertificates(); |       await this.certManager.provisionAllCertificates(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -411,16 +412,26 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|       const terminationStats = this.connectionManager.getTerminationStats(); |       const terminationStats = this.connectionManager.getTerminationStats(); | ||||||
|  |  | ||||||
|       // Log detailed stats |       // Log detailed stats | ||||||
|       console.log( |       logger.log('info', 'Connection statistics', { | ||||||
|         `Active connections: ${connectionRecords.size}. ` + |         activeConnections: connectionRecords.size, | ||||||
|         `Types: TLS=${tlsConnections} (Completed=${completedTlsHandshakes}, Pending=${pendingTlsHandshakes}), ` + |         tls: { | ||||||
|         `Non-TLS=${nonTlsConnections}, KeepAlive=${keepAliveConnections}, HttpProxy=${httpProxyConnections}. ` + |           total: tlsConnections, | ||||||
|         `Longest running: IN=${plugins.prettyMs(maxIncoming)}, OUT=${plugins.prettyMs(maxOutgoing)}. ` + |           completed: completedTlsHandshakes, | ||||||
|         `Termination stats: ${JSON.stringify({ |           pending: pendingTlsHandshakes | ||||||
|           IN: terminationStats.incoming, |         }, | ||||||
|           OUT: terminationStats.outgoing, |         nonTls: nonTlsConnections, | ||||||
|         })}` |         keepAlive: keepAliveConnections, | ||||||
|       ); |         httpProxy: httpProxyConnections, | ||||||
|  |         longestRunning: { | ||||||
|  |           incoming: plugins.prettyMs(maxIncoming), | ||||||
|  |           outgoing: plugins.prettyMs(maxOutgoing) | ||||||
|  |         }, | ||||||
|  |         terminationStats: { | ||||||
|  |           incoming: terminationStats.incoming, | ||||||
|  |           outgoing: terminationStats.outgoing | ||||||
|  |         }, | ||||||
|  |         component: 'connection-manager' | ||||||
|  |       }); | ||||||
|     }, this.settings.inactivityCheckInterval || 60000); |     }, this.settings.inactivityCheckInterval || 60000); | ||||||
|  |  | ||||||
|     // Make sure the interval doesn't keep the process alive |     // Make sure the interval doesn't keep the process alive | ||||||
| @@ -439,19 +450,19 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|    * Stop the proxy server |    * Stop the proxy server | ||||||
|    */ |    */ | ||||||
|   public async stop() { |   public async stop() { | ||||||
|     console.log('SmartProxy shutting down...'); |     logger.log('info', 'SmartProxy shutting down...'); | ||||||
|     this.isShuttingDown = true; |     this.isShuttingDown = true; | ||||||
|     this.portManager.setShuttingDown(true); |     this.portManager.setShuttingDown(true); | ||||||
|      |      | ||||||
|     // Stop certificate manager |     // Stop certificate manager | ||||||
|     if (this.certManager) { |     if (this.certManager) { | ||||||
|       await this.certManager.stop(); |       await this.certManager.stop(); | ||||||
|       console.log('Certificate manager stopped'); |       logger.log('info', 'Certificate manager stopped'); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Stop NFTablesManager |     // Stop NFTablesManager | ||||||
|     await this.nftablesManager.stop(); |     await this.nftablesManager.stop(); | ||||||
|     console.log('NFTablesManager stopped'); |     logger.log('info', 'NFTablesManager stopped'); | ||||||
|  |  | ||||||
|     // Stop the connection logger |     // Stop the connection logger | ||||||
|     if (this.connectionLogger) { |     if (this.connectionLogger) { | ||||||
| @@ -461,7 +472,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|  |  | ||||||
|     // Stop all port listeners |     // Stop all port listeners | ||||||
|     await this.portManager.closeAll(); |     await this.portManager.closeAll(); | ||||||
|     console.log('All servers closed. Cleaning up active connections...'); |     logger.log('info', 'All servers closed. Cleaning up active connections...'); | ||||||
|  |  | ||||||
|     // Clean up all active connections |     // Clean up all active connections | ||||||
|     this.connectionManager.clearConnections(); |     this.connectionManager.clearConnections(); | ||||||
| @@ -472,7 +483,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|     // Clear ACME state manager |     // Clear ACME state manager | ||||||
|     this.acmeStateManager.clear(); |     this.acmeStateManager.clear(); | ||||||
|  |  | ||||||
|     console.log('SmartProxy shutdown complete.'); |     logger.log('info', 'SmartProxy shutdown complete.'); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
| @@ -481,7 +492,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|    * Note: This legacy method has been removed. Use updateRoutes instead. |    * Note: This legacy method has been removed. Use updateRoutes instead. | ||||||
|    */ |    */ | ||||||
|   public async updateDomainConfigs(): Promise<void> { |   public async updateDomainConfigs(): Promise<void> { | ||||||
|     console.warn('Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.'); |     logger.log('warn', 'Method updateDomainConfigs() is deprecated. Use updateRoutes() instead.'); | ||||||
|     throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead'); |     throw new Error('updateDomainConfigs() is deprecated - use updateRoutes() instead'); | ||||||
|   } |   } | ||||||
|    |    | ||||||
| @@ -497,7 +508,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|       const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge'); |       const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge'); | ||||||
|        |        | ||||||
|       if (!challengeRouteExists) { |       if (!challengeRouteExists) { | ||||||
|         console.log('Challenge route successfully removed from routes'); |         logger.log('info', 'Challenge route successfully removed from routes'); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|        |        | ||||||
| @@ -505,7 +516,9 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|       await plugins.smartdelay.delayFor(retryDelay); |       await plugins.smartdelay.delayFor(retryDelay); | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     throw new Error('Failed to verify challenge route removal after ' + maxRetries + ' attempts'); |     const error = `Failed to verify challenge route removal after ${maxRetries} attempts`; | ||||||
|  |     logger.log('error', error); | ||||||
|  |     throw new Error(error); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
| @@ -533,7 +546,7 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|    */ |    */ | ||||||
|   public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> { |   public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> { | ||||||
|     return this.routeUpdateLock.runExclusive(async () => { |     return this.routeUpdateLock.runExclusive(async () => { | ||||||
|       console.log(`Updating routes (${newRoutes.length} routes)`); |       logger.log('info', `Updating routes (${newRoutes.length} routes)`, { routeCount: newRoutes.length, component: 'route-manager' }); | ||||||
|  |  | ||||||
|       // Get existing routes that use NFTables |       // Get existing routes that use NFTables | ||||||
|       const oldNfTablesRoutes = this.settings.routes.filter( |       const oldNfTablesRoutes = this.settings.routes.filter( | ||||||
| @@ -658,14 +671,14 @@ export class SmartProxy extends plugins.EventEmitter { | |||||||
|      |      | ||||||
|     // Check for wildcard domains (they can't get ACME certs) |     // Check for wildcard domains (they can't get ACME certs) | ||||||
|     if (domain.includes('*')) { |     if (domain.includes('*')) { | ||||||
|       console.log(`Wildcard domains like "${domain}" are not supported for ACME certificates`); |       logger.log('warn', `Wildcard domains like "${domain}" are not supported for automatic ACME certificates`, { domain, component: 'certificate-manager' }); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Check if domain has at least one dot and no invalid characters |     // Check if domain has at least one dot and no invalid characters | ||||||
|     const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; |     const validDomainRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; | ||||||
|     if (!validDomainRegex.test(domain)) { |     if (!validDomainRegex.test(domain)) { | ||||||
|       console.log(`Domain "${domain}" has invalid format`); |       logger.log('warn', `Domain "${domain}" has invalid format for certificate issuance`, { domain, component: 'certificate-manager' }); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user