| 
						 
							
							
							
						 
					 | 
				
			
			 | 
			 | 
			
				@@ -1,5 +1,9 @@
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import * as plugins from './plugins.js';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import type { IConnectionRecord, IDomainConfig, IPortProxySettings } from './classes.pp.interfaces.js';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import type {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  IConnectionRecord,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  IDomainConfig,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  IPortProxySettings,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				} from './classes.pp.interfaces.js';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import { ConnectionManager } from './classes.pp.connectionmanager.js';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import { SecurityManager } from './classes.pp.securitymanager.js';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -73,8 +77,8 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        `[${connectionId}] New connection from ${remoteIP} on port ${localPort}. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        `Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        `Active connections: ${this.connectionManager.getConnectionCount()}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `Keep-Alive: ${record.hasKeepAlive ? 'Enabled' : 'Disabled'}. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `Active connections: ${this.connectionManager.getConnectionCount()}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      console.log(
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -94,7 +98,10 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  /**
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   * Handle a connection that should be forwarded to NetworkProxy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				   */
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private handleNetworkProxyConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  private handleNetworkProxyConnection(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    socket: plugins.net.Socket,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    record: IConnectionRecord
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  ): void {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const connectionId = record.id;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    let initialDataReceived = false;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -144,7 +151,7 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `[${connectionId}] Non-TLS connection detected on port 443. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (record.incomingTerminationReason === null) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          record.incomingTerminationReason = 'non_tls_blocked';
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -159,8 +166,8 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (this.tlsManager.isTlsHandshake(chunk)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        record.isTLS = true;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Check session tickets if they're disabled
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (this.settings.allowSessionTicket === false && this.tlsManager.isClientHello(chunk)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Check for ClientHello to extract SNI - but don't enforce it for NetworkProxy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (this.tlsManager.isClientHello(chunk)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // Create connection info for SNI extraction
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          const connInfo = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            sourceIp: record.remoteIP,
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -169,83 +176,46 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            destPort: socket.localPort || 0,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // Extract SNI for domain-specific NetworkProxy handling
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // Extract SNI for domain-specific NetworkProxy handling if available
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          const serverName = this.tlsManager.extractSNI(chunk, connInfo);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // If allowSessionTicket is false and we can't determine SNI, terminate the connection
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (!serverName) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Always block when allowSessionTicket is false and there's no SNI
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `Terminating connection to force new TLS handshake with SNI.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // For NetworkProxy connections, we'll allow session tickets even without SNI
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // We'll only use the serverName if available to determine the specific NetworkProxy port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (serverName) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Save domain config and SNI in connection record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.domainConfig = domainConfig;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.lockedDomain = serverName;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Send a proper TLS alert before ending the connection
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Using "unrecognized_name" (112) alert which is a warning level alert (1) 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // that encourages clients to retry with proper SNI
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const alertData = Buffer.from([
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x15,       // Alert record type
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03, 0x03, // TLS 1.2 version
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00, 0x02, // Length
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x01,       // Warning alert level (not fatal)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x70        // unrecognized_name alert (code 112)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Use domain-specific NetworkProxy port if configured
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            try {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.write(alertData, () => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Only close the socket after we're sure the alert was sent
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Give the alert time to be processed by the client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  socket.end();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  `[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  // Ensure complete cleanup happens a bit later
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if (!socket.destroyed) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      socket.destroy();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  }, 100);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }, 100);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } catch (err) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              // If we can't send the alert, fall back to immediate termination
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.end();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (record.incomingTerminationReason === null) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // Save domain config and SNI in connection record
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          record.domainConfig = domainConfig;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          record.lockedDomain = serverName;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // Use domain-specific NetworkProxy port if configured
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (domainConfig && this.domainConfigManager.shouldUseNetworkProxy(domainConfig)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                `[${connectionId}] Using domain-specific NetworkProxy for ${serverName} on port ${networkProxyPort}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              // Forward to NetworkProxy with domain-specific port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.networkProxyBridge.forwardToNetworkProxy(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                connectionId,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                socket,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                record,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                chunk,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                networkProxyPort,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                (reason) => this.connectionManager.initiateCleanupOnce(record, reason)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Forward to NetworkProxy with domain-specific port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.networkProxyBridge.forwardToNetworkProxy(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              connectionId,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              chunk,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              networkProxyPort,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              (reason) => this.connectionManager.initiateCleanupOnce(record, reason)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          } else if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.settings.allowSessionTicket === false &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.settings.enableDetailedLogging
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Log that we're allowing a session resumption without SNI for NetworkProxy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] Allowing session resumption without SNI for NetworkProxy forwarding`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -260,14 +230,10 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // If not TLS, use normal direct connection
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${record.localPort}`);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.setupDirectConnection(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          socket,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          record,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          undefined,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          undefined,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          chunk
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `[${connectionId}] Non-TLS connection on NetworkProxy port ${record.localPort}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.setupDirectConnection(socket, record, undefined, undefined, chunk);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				  }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -385,14 +351,13 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      record.domainConfig = domainConfig;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // Check if this domain should use NetworkProxy (domain-specific setting)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (domainConfig && 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          this.domainConfigManager.shouldUseNetworkProxy(domainConfig) && 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          this.networkProxyBridge.getNetworkProxy()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        domainConfig &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.domainConfigManager.shouldUseNetworkProxy(domainConfig) &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.networkProxyBridge.getNetworkProxy()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -418,19 +383,20 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Skip IP validation if allowedIPs is empty
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          domainConfig.allowedIPs.length > 0 &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          !this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          !this.securityManager.isIPAuthorized(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.remoteIP,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ipRules.allowedIPs,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ipRules.blockedIPs
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          )
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          return rejectIncomingConnection(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            'rejected',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `Connection rejected: IP ${record.remoteIP} not allowed for domain ${domainConfig.domains.join(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              ', '
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            )}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `Connection rejected: IP ${
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record.remoteIP
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } not allowed for domain ${domainConfig.domains.join(', ')}`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      } else if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.settings.defaultAllowedIPs &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.settings.defaultAllowedIPs.length > 0
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      } else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          !this.securityManager.isIPAuthorized(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.remoteIP,
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -501,9 +467,17 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (forcedDomain) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          const ipRules = this.domainConfigManager.getEffectiveIPRules(forcedDomain);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (!this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            !this.securityManager.isIPAuthorized(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record.remoteIP,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              ipRules.allowedIPs,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              ipRules.blockedIPs
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            )
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] Connection from ${record.remoteIP} rejected: IP not allowed for domain ${forcedDomain.domains.join(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] Connection from ${
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                record.remoteIP
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              } rejected: IP not allowed for domain ${forcedDomain.domains.join(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                ', '
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              )} on port ${localPort}.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -513,9 +487,9 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] Port-based connection from ${record.remoteIP} on port ${localPort} matched domain ${forcedDomain.domains.join(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                ', '
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              )}.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] Port-based connection from ${
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                record.remoteIP
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              } on port ${localPort} matched domain ${forcedDomain.domains.join(', ')}.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -543,7 +517,7 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (!this.tlsManager.isTlsHandshake(chunk) && localPort === 443) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `[${connectionId}] Non-TLS connection detected on port 443 in SNI handler. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `Terminating connection - only TLS traffic is allowed on standard HTTPS port.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (record.incomingTerminationReason === null) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            record.incomingTerminationReason = 'non_tls_blocked';
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -578,54 +552,103 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          // If allowSessionTicket is false and this is a ClientHello with no SNI, terminate the connection
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (this.settings.allowSessionTicket === false && 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.tlsManager.isClientHello(chunk) && 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              !serverName) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Always block ClientHello without SNI when allowSessionTicket is false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          if (
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.settings.allowSessionTicket === false &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            this.tlsManager.isClientHello(chunk) &&
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            !serverName
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          ) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Block ClientHello without SNI when allowSessionTicket is false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              `Terminating connection to force new TLS handshake with SNI.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                `Sending warning unrecognized_name alert to encourage immediate retry with SNI.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Send a proper TLS alert before ending the connection
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Using "unrecognized_name" (112) alert which is a warning level alert (1) 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // that encourages clients to retry with proper SNI
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const alertData = Buffer.from([
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x15,       // Alert record type
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03, 0x03, // TLS 1.2 version
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00, 0x02, // Length
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x01,       // Warning alert level (not fatal)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x70        // unrecognized_name alert (code 112)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Set the termination reason first
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (record.incomingTerminationReason === null) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.connectionManager.incrementTerminationStat(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'incoming',
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                'session_ticket_blocked_no_sni'
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Create a warning-level alert for unrecognized_name
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // This encourages Chrome to retry immediately with SNI
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const serverNameUnknownAlertData = Buffer.from([
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x15, // Alert record type
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03, // TLS 1.2 version
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x02, // Length
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x01, // Warning alert level (not fatal)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x70, // unrecognized_name alert (code 112)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Send a handshake_failure alert instead of unrecognized_name
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const sslHandshakeFailureAlertData = Buffer.from([
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x15, // Alert record type
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03, // TLS 1.2 version
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x02, // Length
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x01, // Warning alert level (not fatal)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x28, // handshake_failure alert (40) instead of unrecognized_name (112)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            const closeNotifyAlert = Buffer.from([
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x15, // Alert record type
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x03, // TLS 1.2 version
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x02, // Length
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x01, // Warning alert level (1)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              0x00, // close_notify alert (0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            ]);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            try {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.write(alertData, () => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Only close the socket after we're sure the alert was sent
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Give the alert time to be processed by the client
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  socket.end();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              // Use cork/uncork to ensure the alert is sent as a single packet
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.cork();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const writeSuccessful = socket.write(closeNotifyAlert);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.uncork();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  // Ensure complete cleanup happens a bit later
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    if (!socket.destroyed) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                      socket.destroy();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  }, 100);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }, 100);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              // Function to handle the clean socket termination
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              const finishConnection = () => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // First call end() to initiate a graceful close (sends FIN)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                socket.end();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Allow a short delay for the alert and FIN to be transmitted
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // before we fully close the socket
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  if (!socket.destroyed) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    socket.destroy();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }, 150); // Short delay, but longer than the standard TCP ACK timeout
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              };
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              if (writeSuccessful) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // If the data was successfully written to the kernel buffer,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // we can finish the connection after a short delay to ensure transmission
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                setTimeout(finishConnection, 50);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // If the kernel buffer was full, wait for the drain event
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                socket.once('drain', () => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  setTimeout(finishConnection, 50);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                // Set a safety timeout in case drain never happens
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                setTimeout(() => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  socket.removeAllListeners('drain');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                  finishConnection();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }, 250);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } catch (err) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              // If we can't send the alert, fall back to immediate termination
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              socket.end();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (record.incomingTerminationReason === null) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				              this.connectionManager.incrementTerminationStat('incoming', 'session_ticket_blocked_no_sni');
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -681,9 +704,7 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      : this.settings.targetIP!;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Determine target port
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const targetPort = overridePort !== undefined 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      ? overridePort 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      : this.settings.toPort;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const targetPort = overridePort !== undefined ? overridePort : this.settings.toPort;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Setup connection options
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    const connectionOptions: plugins.net.NetConnectOpts = {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -956,7 +977,9 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        const combinedData = Buffer.concat(record.pendingData);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (this.settings.enableDetailedLogging) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Write pending data immediately
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@@ -1054,15 +1077,12 @@ export class ConnectionHandler {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // Set connection timeout
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        record,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        (record, reason) => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            `[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          this.connectionManager.initiateCleanupOnce(record, reason);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        console.log(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				          `[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        );
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        this.connectionManager.initiateCleanupOnce(record, reason);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      // Mark TLS handshake as complete for TLS connections
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				      if (record.isTLS) {
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				 
 |