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