update
This commit is contained in:
		| @@ -124,4 +124,4 @@ tap.test('should parse HTTP headers correctly', async (tools) => { | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -159,4 +159,4 @@ tap.test('should return 404 for non-existent challenge tokens', async (tapTest) | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -215,4 +215,4 @@ tap.test('should handle HTTP request parsing correctly', async (tools) => { | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -117,4 +117,4 @@ tap.test('should configure ACME challenge route', async () => { | |||||||
|   expect(challengeRoute.action.socketHandler).toBeDefined(); |   expect(challengeRoute.action.socketHandler).toBeDefined(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -119,4 +119,4 @@ tap.test('should defer certificate provisioning until ports are ready', async (t | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -238,4 +238,4 @@ tap.test('should renew certificates', async () => { | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -57,4 +57,4 @@ tap.test('should handle socket handler route type', async () => { | |||||||
|   expect(route.action.socketHandler).toBeDefined(); |   expect(route.action.socketHandler).toBeDefined(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -143,4 +143,4 @@ tap.test('cleanup queue bug - verify queue processing handles more than batch si | |||||||
|   console.log('\n✓ Test complete: Cleanup queue now correctly processes all connections'); |   console.log('\n✓ Test complete: Cleanup queue now correctly processes all connections'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -239,4 +239,4 @@ tap.test('should handle clients that error during connection', async () => { | |||||||
|   console.log('\n✅ PASS: Connection error cleanup working correctly!'); |   console.log('\n✅ PASS: Connection error cleanup working correctly!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -276,4 +276,4 @@ tap.test('comprehensive connection cleanup test - all scenarios', async () => { | |||||||
|   console.log('- NFTables connections'); |   console.log('- NFTables connections'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -296,4 +296,4 @@ tap.test('Cleanup and shutdown', async () => { | |||||||
|   allServers.length = 0; |   allServers.length = 0; | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -1,286 +0,0 @@ | |||||||
| import { expect, tap } from '@git.zone/tstest/tapbundle'; |  | ||||||
| import * as smartproxy from '../ts/index.js'; |  | ||||||
| import * as net from 'net'; |  | ||||||
| import * as crypto from 'crypto'; |  | ||||||
|  |  | ||||||
| tap.test('Connection Stability - Fragment Cleanup', async () => { |  | ||||||
|   // Create a simple TCP server |  | ||||||
|   const server = net.createServer(); |  | ||||||
|   await new Promise<void>((resolve) => { |  | ||||||
|     server.listen(0, '127.0.0.1', () => resolve()); |  | ||||||
|   }); |  | ||||||
|   const serverPort = (server.address() as net.AddressInfo).port; |  | ||||||
|  |  | ||||||
|   // Configure a route |  | ||||||
|   const routes: smartproxy.IRouteConfig[] = [{ |  | ||||||
|     match: { |  | ||||||
|       ports: 9000, |  | ||||||
|       domains: '*' |  | ||||||
|     }, |  | ||||||
|     action: { |  | ||||||
|       type: 'forward', |  | ||||||
|       target: { |  | ||||||
|         host: '127.0.0.1', |  | ||||||
|         port: serverPort |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }]; |  | ||||||
|    |  | ||||||
|   // Create SmartProxy instance with routes |  | ||||||
|   const proxy = new smartproxy.SmartProxy({  |  | ||||||
|     keepAliveTimeoutMs: 5000, |  | ||||||
|     routes  |  | ||||||
|   }); |  | ||||||
|    |  | ||||||
|   await proxy.start(); |  | ||||||
|  |  | ||||||
|   // Test 1: Send fragmented TLS hello |  | ||||||
|   const tlsHello = Buffer.concat([ |  | ||||||
|     Buffer.from([0x16, 0x03, 0x03]), // TLS handshake, version 1.2 |  | ||||||
|     Buffer.from([0x00, 0x50]), // Length: 80 bytes |  | ||||||
|     Buffer.from([0x01]), // ClientHello |  | ||||||
|     Buffer.from([0x00, 0x00, 0x4c]), // Handshake length |  | ||||||
|     Buffer.from([0x03, 0x03]), // TLS 1.2 |  | ||||||
|     crypto.randomBytes(32), // Random |  | ||||||
|     Buffer.from([0x00]), // Session ID length |  | ||||||
|     Buffer.from([0x00, 0x04]), // Cipher suites length |  | ||||||
|     Buffer.from([0xc0, 0x2f, 0xc0, 0x30]), // Cipher suites |  | ||||||
|     Buffer.from([0x01, 0x00]), // Compression methods |  | ||||||
|     Buffer.from([0x00, 0x1f]), // Extensions length |  | ||||||
|     // SNI extension |  | ||||||
|     Buffer.from([0x00, 0x00]), // Server name extension |  | ||||||
|     Buffer.from([0x00, 0x1b]), // Extension length |  | ||||||
|     Buffer.from([0x00, 0x19]), // Server name list length |  | ||||||
|     Buffer.from([0x00]), // Host name type |  | ||||||
|     Buffer.from([0x00, 0x16]), // Name length |  | ||||||
|     Buffer.from('test.example.com') // Server name |  | ||||||
|   ]); |  | ||||||
|  |  | ||||||
|   // Function to check fragment manager size |  | ||||||
|   const getFragmentCount = () => { |  | ||||||
|     // Access the fragment manager through the singleton |  | ||||||
|     const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); |  | ||||||
|     const tlsFragments = detector.fragmentManager.getHandler('tls'); |  | ||||||
|     const httpFragments = detector.fragmentManager.getHandler('http'); |  | ||||||
|     return tlsFragments.size + httpFragments.size; |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   // Test fragmented connections |  | ||||||
|   const connections: net.Socket[] = []; |  | ||||||
|    |  | ||||||
|   // Create multiple fragmented connections |  | ||||||
|   for (let i = 0; i < 5; i++) { |  | ||||||
|     const client = new net.Socket(); |  | ||||||
|     connections.push(client); |  | ||||||
|      |  | ||||||
|     await new Promise<void>((resolve, reject) => { |  | ||||||
|       client.connect(9000, '127.0.0.1', () => { |  | ||||||
|         // Send first fragment |  | ||||||
|         client.write(tlsHello.subarray(0, 20)); |  | ||||||
|         resolve(); |  | ||||||
|       }); |  | ||||||
|       client.on('error', reject); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Give time for fragments to accumulate |  | ||||||
|   await new Promise(resolve => setTimeout(resolve, 100)); |  | ||||||
|  |  | ||||||
|   // Check that fragments are being tracked |  | ||||||
|   const fragmentCount = getFragmentCount(); |  | ||||||
|   expect(fragmentCount).toBeGreaterThan(0); |  | ||||||
|  |  | ||||||
|   // Send remaining fragments and close connections |  | ||||||
|   for (const client of connections) { |  | ||||||
|     client.write(tlsHello.subarray(20)); |  | ||||||
|     client.end(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Wait for connections to close |  | ||||||
|   await new Promise(resolve => setTimeout(resolve, 500)); |  | ||||||
|  |  | ||||||
|   // Check that fragments are cleaned up |  | ||||||
|   const finalFragmentCount = getFragmentCount(); |  | ||||||
|   expect(finalFragmentCount).toEqual(0); |  | ||||||
|  |  | ||||||
|   // Cleanup |  | ||||||
|   await proxy.stop(); |  | ||||||
|   server.close(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| tap.test('Connection Stability - Memory Leak Prevention', async () => { |  | ||||||
|   // Create a simple echo server |  | ||||||
|   const server = net.createServer((socket) => { |  | ||||||
|     socket.pipe(socket); |  | ||||||
|   }); |  | ||||||
|    |  | ||||||
|   await new Promise<void>((resolve) => { |  | ||||||
|     server.listen(0, '127.0.0.1', () => resolve()); |  | ||||||
|   }); |  | ||||||
|   const serverPort = (server.address() as net.AddressInfo).port; |  | ||||||
|  |  | ||||||
|   // Configure a route |  | ||||||
|   const routes: smartproxy.IRouteConfig[] = [{ |  | ||||||
|     match: { |  | ||||||
|       ports: 9001, |  | ||||||
|       domains: '*' |  | ||||||
|     }, |  | ||||||
|     action: { |  | ||||||
|       type: 'forward', |  | ||||||
|       target: { |  | ||||||
|         host: '127.0.0.1', |  | ||||||
|         port: serverPort |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }]; |  | ||||||
|    |  | ||||||
|   // Create SmartProxy instance with routes |  | ||||||
|   const proxy = new smartproxy.SmartProxy({  |  | ||||||
|     keepAliveTimeoutMs: 5000, |  | ||||||
|     routes  |  | ||||||
|   }); |  | ||||||
|    |  | ||||||
|   await proxy.start(); |  | ||||||
|  |  | ||||||
|   // Function to get active connection count |  | ||||||
|   const getConnectionCount = () => { |  | ||||||
|     const connectionManager = (proxy as any).connectionManager; |  | ||||||
|     return connectionManager.getActiveConnectionCount(); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   // Create many short-lived connections |  | ||||||
|   const connectionPromises: Promise<void>[] = []; |  | ||||||
|    |  | ||||||
|   for (let i = 0; i < 20; i++) { |  | ||||||
|     const promise = new Promise<void>((resolve, reject) => { |  | ||||||
|       const client = new net.Socket(); |  | ||||||
|        |  | ||||||
|       client.connect(9001, '127.0.0.1', () => { |  | ||||||
|         // Send some data |  | ||||||
|         client.write('Hello World'); |  | ||||||
|          |  | ||||||
|         // Close after a short delay |  | ||||||
|         setTimeout(() => { |  | ||||||
|           client.end(); |  | ||||||
|         }, 50); |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       client.on('close', () => resolve()); |  | ||||||
|       client.on('error', reject); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     connectionPromises.push(promise); |  | ||||||
|      |  | ||||||
|     // Stagger connection creation |  | ||||||
|     await new Promise(resolve => setTimeout(resolve, 10)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Wait for all connections to complete |  | ||||||
|   await Promise.all(connectionPromises); |  | ||||||
|  |  | ||||||
|   // Give time for cleanup |  | ||||||
|   await new Promise(resolve => setTimeout(resolve, 1000)); |  | ||||||
|  |  | ||||||
|   // Check that all connections are cleaned up |  | ||||||
|   const finalConnectionCount = getConnectionCount(); |  | ||||||
|   expect(finalConnectionCount).toEqual(0); |  | ||||||
|  |  | ||||||
|   // Check fragment cleanup |  | ||||||
|   const fragmentCount = (() => { |  | ||||||
|     const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); |  | ||||||
|     const tlsFragments = detector.fragmentManager.getHandler('tls'); |  | ||||||
|     const httpFragments = detector.fragmentManager.getHandler('http'); |  | ||||||
|     return tlsFragments.size + httpFragments.size; |  | ||||||
|   })(); |  | ||||||
|    |  | ||||||
|   expect(fragmentCount).toEqual(0); |  | ||||||
|  |  | ||||||
|   // Cleanup |  | ||||||
|   await proxy.stop(); |  | ||||||
|   server.close(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| tap.test('Connection Stability - Rapid Connect/Disconnect', async () => { |  | ||||||
|   // Create a server that immediately closes connections |  | ||||||
|   const server = net.createServer((socket) => { |  | ||||||
|     socket.end(); |  | ||||||
|   }); |  | ||||||
|    |  | ||||||
|   await new Promise<void>((resolve) => { |  | ||||||
|     server.listen(0, '127.0.0.1', () => resolve()); |  | ||||||
|   }); |  | ||||||
|   const serverPort = (server.address() as net.AddressInfo).port; |  | ||||||
|  |  | ||||||
|   // Configure a route |  | ||||||
|   const routes: smartproxy.IRouteConfig[] = [{ |  | ||||||
|     match: { |  | ||||||
|       ports: 9002, |  | ||||||
|       domains: '*' |  | ||||||
|     }, |  | ||||||
|     action: { |  | ||||||
|       type: 'forward', |  | ||||||
|       target: { |  | ||||||
|         host: '127.0.0.1', |  | ||||||
|         port: serverPort |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|   }]; |  | ||||||
|    |  | ||||||
|   // Create SmartProxy instance with routes |  | ||||||
|   const proxy = new smartproxy.SmartProxy({  |  | ||||||
|     keepAliveTimeoutMs: 5000, |  | ||||||
|     routes  |  | ||||||
|   }); |  | ||||||
|    |  | ||||||
|   await proxy.start(); |  | ||||||
|  |  | ||||||
|   let errors = 0; |  | ||||||
|   const connections: Promise<void>[] = []; |  | ||||||
|  |  | ||||||
|   // Create many rapid connections |  | ||||||
|   for (let i = 0; i < 50; i++) { |  | ||||||
|     const promise = new Promise<void>((resolve) => { |  | ||||||
|       const client = new net.Socket(); |  | ||||||
|        |  | ||||||
|       client.on('error', () => { |  | ||||||
|         errors++; |  | ||||||
|         resolve(); |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       client.on('close', () => { |  | ||||||
|         resolve(); |  | ||||||
|       }); |  | ||||||
|        |  | ||||||
|       client.connect(9002, '127.0.0.1'); |  | ||||||
|     }); |  | ||||||
|      |  | ||||||
|     connections.push(promise); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Wait for all to complete |  | ||||||
|   await Promise.all(connections); |  | ||||||
|  |  | ||||||
|   // Give time for cleanup |  | ||||||
|   await new Promise(resolve => setTimeout(resolve, 500)); |  | ||||||
|  |  | ||||||
|   // Check that connections are cleaned up despite rapid connect/disconnect |  | ||||||
|   const connectionManager = (proxy as any).connectionManager; |  | ||||||
|   const finalConnectionCount = connectionManager.getActiveConnectionCount(); |  | ||||||
|   expect(finalConnectionCount).toEqual(0); |  | ||||||
|  |  | ||||||
|   // Check fragment cleanup |  | ||||||
|   const fragmentCount = (() => { |  | ||||||
|     const detector = (smartproxy.detection.ProtocolDetector as any).getInstance(); |  | ||||||
|     const tlsFragments = detector.fragmentManager.getHandler('tls'); |  | ||||||
|     const httpFragments = detector.fragmentManager.getHandler('http'); |  | ||||||
|     return tlsFragments.size + httpFragments.size; |  | ||||||
|   })(); |  | ||||||
|    |  | ||||||
|   expect(fragmentCount).toEqual(0); |  | ||||||
|  |  | ||||||
|   // Cleanup |  | ||||||
|   await proxy.stop(); |  | ||||||
|   server.close(); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| tap.start(); |  | ||||||
| @@ -138,4 +138,9 @@ tap.test('Protocol Detection - Invalid Data', async () => { | |||||||
|   expect(result.protocol).toEqual('unknown'); |   expect(result.protocol).toEqual('unknown'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | tap.test('cleanup detection', async () => { | ||||||
|  |   // Clean up the protocol detector instance | ||||||
|  |   smartproxy.detection.ProtocolDetector.destroy(); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | export default tap.start(); | ||||||
| @@ -79,4 +79,4 @@ tap.test('should verify certificate manager callback is preserved on updateRoute | |||||||
|   console.log('Fix verified: Certificate manager callback is preserved on updateRoutes'); |   console.log('Fix verified: Certificate manager callback is preserved on updateRoutes'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -180,4 +180,4 @@ tap.test('should handle ACME HTTP-01 challenges on port 80 with HttpProxy', asyn | |||||||
|   console.log('Test passed: ACME HTTP-01 challenges on port 80 use HttpProxy'); |   console.log('Test passed: ACME HTTP-01 challenges on port 80 use HttpProxy'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -242,4 +242,4 @@ tap.test('should handle ACME challenges on port 8080 with improved port binding | |||||||
|   } |   } | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -117,4 +117,4 @@ tap.test('Cleanup HttpProxy SecurityManager', async () => { | |||||||
|   securityManager.clearIPTracking(); |   securityManager.clearIPTracking(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -247,4 +247,4 @@ tap.test('keepalive support - verify keepalive connections are properly handled' | |||||||
|   console.log('  - Zombie detection respects keepalive settings'); |   console.log('  - Zombie detection respects keepalive settings'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -109,4 +109,4 @@ tap.test('Cleanup deduplicator', async () => { | |||||||
|   expect(deduplicator).toBeInstanceOf(LogDeduplicator); |   expect(deduplicator).toBeInstanceOf(LogDeduplicator); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -149,4 +149,4 @@ tap.test('should not have memory leaks in long-running operations', async (tools | |||||||
| }); | }); | ||||||
|  |  | ||||||
| // Run with: node --expose-gc test.memory-leak-check.node.ts | // Run with: node --expose-gc test.memory-leak-check.node.ts | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -57,4 +57,4 @@ tap.test('memory leak fixes verification', async () => { | |||||||
|   console.log('\n✅ All memory leak fixes verified!'); |   console.log('\n✅ All memory leak fixes verified!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -128,4 +128,4 @@ tap.test('memory leak fixes - unit tests', async () => { | |||||||
|   console.log('\n✅ All memory leak fixes verified!'); |   console.log('\n✅ All memory leak fixes verified!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -258,4 +258,4 @@ tap.test('should clean up resources', async () => { | |||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -192,4 +192,4 @@ tap.test('simple proxy chain test - identify connection accumulation', async () | |||||||
|   expect(finalCounts.proxy2).toEqual(0); |   expect(finalCounts.proxy2).toEqual(0); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -130,4 +130,4 @@ tap.test('PROXY protocol v1 generator', async () => { | |||||||
| // Skipping integration tests for now - focus on unit tests | // Skipping integration tests for now - focus on unit tests | ||||||
| // Integration tests would require more complex setup and teardown | // Integration tests would require more complex setup and teardown | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -198,4 +198,4 @@ tap.test('should handle routing failures without leaking connections', async () | |||||||
|   console.log('\n✅ PASS: Routing failures cleaned up correctly!'); |   console.log('\n✅ PASS: Routing failures cleaned up correctly!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -113,4 +113,4 @@ tap.test('should set update routes callback on certificate manager', async () => | |||||||
|   await proxy.stop(); |   await proxy.stop(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -58,4 +58,4 @@ tap.test('route security should be correctly configured', async () => { | |||||||
|   expect(isBlockedIPAllowed).toBeFalse(); |   expect(isBlockedIPAllowed).toBeFalse(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -336,4 +336,4 @@ tap.test('real code integration test - verify fix is applied', async () => { | |||||||
|   console.log('Real code integration test passed - fix is correctly applied!'); |   console.log('Real code integration test passed - fix is correctly applied!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -154,4 +154,4 @@ tap.test('Cleanup SharedSecurityManager', async () => { | |||||||
|   securityManager.clearIPTracking(); |   securityManager.clearIPTracking(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -51,4 +51,4 @@ tap.test('should verify SmartAcme cert managers are accessible', async () => { | |||||||
|   expect(memoryCertManager).toBeDefined(); |   expect(memoryCertManager).toBeDefined(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -141,4 +141,4 @@ tap.test('stuck connection cleanup - verify connections to hanging backends are | |||||||
|   console.log('✓ Test complete: Stuck connections are properly detected and cleaned up'); |   console.log('✓ Test complete: Stuck connections are properly detected and cleaned up'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -155,4 +155,4 @@ tap.test('long-lived connection survival test', async (tools) => { | |||||||
|   console.log('✅ Long-lived connection survived past 30-second timeout!'); |   console.log('✅ Long-lived connection survived past 30-second timeout!'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -303,4 +303,4 @@ tap.test('zombie connection cleanup - verify inactivity check detects and cleans | |||||||
|   expect(details.inner.halfZombies.length).toEqual(0); |   expect(details.inner.halfZombies.length).toEqual(0); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| tap.start(); | export default tap.start(); | ||||||
| @@ -49,11 +49,15 @@ export class HttpDetector implements IProtocolDetector { | |||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     // Check if we have complete headers first | ||||||
|  |     const headersEnd = buffer.indexOf('\r\n\r\n'); | ||||||
|  |     const isComplete = headersEnd !== -1; | ||||||
|  |      | ||||||
|     // Extract routing information |     // Extract routing information | ||||||
|     const routing = RoutingExtractor.extract(buffer, 'http'); |     const routing = RoutingExtractor.extract(buffer, 'http'); | ||||||
|      |      | ||||||
|     // If we don't need full headers, we can return early |     // If we don't need full headers and we have complete headers, we can return early | ||||||
|     if (quickResult.confidence >= 95 && !options?.extractFullHeaders) { |     if (quickResult.confidence >= 95 && !options?.extractFullHeaders && isComplete) { | ||||||
|       return { |       return { | ||||||
|         protocol: 'http', |         protocol: 'http', | ||||||
|         connectionInfo: {  |         connectionInfo: {  | ||||||
| @@ -66,10 +70,6 @@ export class HttpDetector implements IProtocolDetector { | |||||||
|       }; |       }; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     // Check if we have complete headers |  | ||||||
|     const headersEnd = buffer.indexOf('\r\n\r\n'); |  | ||||||
|     const isComplete = headersEnd !== -1; |  | ||||||
|      |  | ||||||
|     return { |     return { | ||||||
|       protocol: 'http', |       protocol: 'http', | ||||||
|       connectionInfo: { |       connectionInfo: { | ||||||
|   | |||||||
| @@ -18,6 +18,7 @@ export class ProtocolDetector { | |||||||
|   private fragmentManager: DetectionFragmentManager; |   private fragmentManager: DetectionFragmentManager; | ||||||
|   private tlsDetector: TlsDetector; |   private tlsDetector: TlsDetector; | ||||||
|   private httpDetector: HttpDetector; |   private httpDetector: HttpDetector; | ||||||
|  |   private connectionProtocols: Map<string, 'tls' | 'http'> = new Map(); | ||||||
|    |    | ||||||
|   constructor() { |   constructor() { | ||||||
|     this.fragmentManager = new DetectionFragmentManager(); |     this.fragmentManager = new DetectionFragmentManager(); | ||||||
| @@ -122,14 +123,25 @@ export class ProtocolDetector { | |||||||
|      |      | ||||||
|     const connectionId = DetectionFragmentManager.createConnectionId(context); |     const connectionId = DetectionFragmentManager.createConnectionId(context); | ||||||
|      |      | ||||||
|     // First peek to determine protocol type |     // Check if we already know the protocol for this connection | ||||||
|     if (this.tlsDetector.canHandle(buffer)) { |     const knownProtocol = this.connectionProtocols.get(connectionId); | ||||||
|  |      | ||||||
|  |     if (knownProtocol === 'http') { | ||||||
|  |       const result = this.httpDetector.detectWithContext(buffer, context, options); | ||||||
|  |       if (result) { | ||||||
|  |         if (result.isComplete) { | ||||||
|  |           this.connectionProtocols.delete(connectionId); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } else if (knownProtocol === 'tls') { | ||||||
|       // Handle TLS with fragment accumulation |       // Handle TLS with fragment accumulation | ||||||
|       const handler = this.fragmentManager.getHandler('tls'); |       const handler = this.fragmentManager.getHandler('tls'); | ||||||
|       const fragmentResult = handler.addFragment(connectionId, buffer); |       const fragmentResult = handler.addFragment(connectionId, buffer); | ||||||
|        |        | ||||||
|       if (fragmentResult.error) { |       if (fragmentResult.error) { | ||||||
|         handler.complete(connectionId); |         handler.complete(connectionId); | ||||||
|  |         this.connectionProtocols.delete(connectionId); | ||||||
|         return { |         return { | ||||||
|           protocol: 'unknown', |           protocol: 'unknown', | ||||||
|           connectionInfo: { protocol: 'unknown' }, |           connectionInfo: { protocol: 'unknown' }, | ||||||
| @@ -141,17 +153,52 @@ export class ProtocolDetector { | |||||||
|       if (result) { |       if (result) { | ||||||
|         if (result.isComplete) { |         if (result.isComplete) { | ||||||
|           handler.complete(connectionId); |           handler.complete(connectionId); | ||||||
|  |           this.connectionProtocols.delete(connectionId); | ||||||
|  |         } | ||||||
|  |         return result; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |     // If we don't know the protocol yet, try to detect it | ||||||
|  |     if (!knownProtocol) { | ||||||
|  |       // First peek to determine protocol type | ||||||
|  |       if (this.tlsDetector.canHandle(buffer)) { | ||||||
|  |         this.connectionProtocols.set(connectionId, 'tls'); | ||||||
|  |         // Handle TLS with fragment accumulation | ||||||
|  |         const handler = this.fragmentManager.getHandler('tls'); | ||||||
|  |         const fragmentResult = handler.addFragment(connectionId, buffer); | ||||||
|  |          | ||||||
|  |         if (fragmentResult.error) { | ||||||
|  |           handler.complete(connectionId); | ||||||
|  |           this.connectionProtocols.delete(connectionId); | ||||||
|  |           return { | ||||||
|  |             protocol: 'unknown', | ||||||
|  |             connectionInfo: { protocol: 'unknown' }, | ||||||
|  |             isComplete: true | ||||||
|  |           }; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const result = this.tlsDetector.detect(fragmentResult.buffer!, options); | ||||||
|  |         if (result) { | ||||||
|  |           if (result.isComplete) { | ||||||
|  |             handler.complete(connectionId); | ||||||
|  |             this.connectionProtocols.delete(connectionId); | ||||||
|           } |           } | ||||||
|           return result; |           return result; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|        |        | ||||||
|       if (this.httpDetector.canHandle(buffer)) { |       if (this.httpDetector.canHandle(buffer)) { | ||||||
|  |         this.connectionProtocols.set(connectionId, 'http'); | ||||||
|         const result = this.httpDetector.detectWithContext(buffer, context, options); |         const result = this.httpDetector.detectWithContext(buffer, context, options); | ||||||
|         if (result) { |         if (result) { | ||||||
|  |           if (result.isComplete) { | ||||||
|  |             this.connectionProtocols.delete(connectionId); | ||||||
|  |           } | ||||||
|           return result; |           return result; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|  |     } | ||||||
|      |      | ||||||
|     // Can't determine protocol |     // Can't determine protocol | ||||||
|     return { |     return { | ||||||
| @@ -208,6 +255,9 @@ export class ProtocolDetector { | |||||||
|     // Clean up both TLS and HTTP fragments for this connection |     // Clean up both TLS and HTTP fragments for this connection | ||||||
|     instance.fragmentManager.getHandler('tls').complete(connectionId); |     instance.fragmentManager.getHandler('tls').complete(connectionId); | ||||||
|     instance.fragmentManager.getHandler('http').complete(connectionId); |     instance.fragmentManager.getHandler('http').complete(connectionId); | ||||||
|  |      | ||||||
|  |     // Remove from connection protocols tracking | ||||||
|  |     instance.connectionProtocols.delete(connectionId); | ||||||
|   } |   } | ||||||
|    |    | ||||||
|   /** |   /** | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user