310 lines
8.9 KiB
TypeScript
310 lines
8.9 KiB
TypeScript
/**
|
|
* Protocol Detector
|
|
*
|
|
* Simplified protocol detection using the new architecture
|
|
*/
|
|
|
|
import type { IDetectionResult, IDetectionOptions } from './models/detection-types.js';
|
|
import type { IConnectionContext } from '../protocols/common/types.js';
|
|
import { TlsDetector } from './detectors/tls-detector.js';
|
|
import { HttpDetector } from './detectors/http-detector.js';
|
|
import { DetectionFragmentManager } from './utils/fragment-manager.js';
|
|
|
|
/**
|
|
* Main protocol detector class
|
|
*/
|
|
export class ProtocolDetector {
|
|
private static instance: ProtocolDetector;
|
|
private fragmentManager: DetectionFragmentManager;
|
|
private tlsDetector: TlsDetector;
|
|
private httpDetector: HttpDetector;
|
|
private connectionProtocols: Map<string, 'tls' | 'http'> = new Map();
|
|
|
|
constructor() {
|
|
this.fragmentManager = new DetectionFragmentManager();
|
|
this.tlsDetector = new TlsDetector();
|
|
this.httpDetector = new HttpDetector(this.fragmentManager);
|
|
}
|
|
|
|
private static getInstance(): ProtocolDetector {
|
|
if (!this.instance) {
|
|
this.instance = new ProtocolDetector();
|
|
}
|
|
return this.instance;
|
|
}
|
|
|
|
/**
|
|
* Detect protocol from buffer data
|
|
*/
|
|
static async detect(buffer: Buffer, options?: IDetectionOptions): Promise<IDetectionResult> {
|
|
return this.getInstance().detectInstance(buffer, options);
|
|
}
|
|
|
|
private async detectInstance(buffer: Buffer, options?: IDetectionOptions): Promise<IDetectionResult> {
|
|
// Quick sanity check
|
|
if (!buffer || buffer.length === 0) {
|
|
return {
|
|
protocol: 'unknown',
|
|
connectionInfo: { protocol: 'unknown' },
|
|
isComplete: true
|
|
};
|
|
}
|
|
|
|
// Try TLS detection first (more specific)
|
|
if (this.tlsDetector.canHandle(buffer)) {
|
|
const tlsResult = this.tlsDetector.detect(buffer, options);
|
|
if (tlsResult) {
|
|
return tlsResult;
|
|
}
|
|
}
|
|
|
|
// Try HTTP detection
|
|
if (this.httpDetector.canHandle(buffer)) {
|
|
const httpResult = this.httpDetector.detect(buffer, options);
|
|
if (httpResult) {
|
|
return httpResult;
|
|
}
|
|
}
|
|
|
|
// Neither TLS nor HTTP
|
|
return {
|
|
protocol: 'unknown',
|
|
connectionInfo: { protocol: 'unknown' },
|
|
isComplete: true
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Detect protocol with connection tracking for fragmented data
|
|
* @deprecated Use detectWithContext instead
|
|
*/
|
|
static async detectWithConnectionTracking(
|
|
buffer: Buffer,
|
|
connectionId: string,
|
|
options?: IDetectionOptions
|
|
): Promise<IDetectionResult> {
|
|
// Convert connection ID to context
|
|
const context: IConnectionContext = {
|
|
id: connectionId,
|
|
sourceIp: 'unknown',
|
|
sourcePort: 0,
|
|
destIp: 'unknown',
|
|
destPort: 0,
|
|
timestamp: Date.now()
|
|
};
|
|
|
|
return this.getInstance().detectWithContextInstance(buffer, context, options);
|
|
}
|
|
|
|
/**
|
|
* Detect protocol with connection context for fragmented data
|
|
*/
|
|
static async detectWithContext(
|
|
buffer: Buffer,
|
|
context: IConnectionContext,
|
|
options?: IDetectionOptions
|
|
): Promise<IDetectionResult> {
|
|
return this.getInstance().detectWithContextInstance(buffer, context, options);
|
|
}
|
|
|
|
private async detectWithContextInstance(
|
|
buffer: Buffer,
|
|
context: IConnectionContext,
|
|
options?: IDetectionOptions
|
|
): Promise<IDetectionResult> {
|
|
// Quick sanity check
|
|
if (!buffer || buffer.length === 0) {
|
|
return {
|
|
protocol: 'unknown',
|
|
connectionInfo: { protocol: 'unknown' },
|
|
isComplete: true
|
|
};
|
|
}
|
|
|
|
const connectionId = DetectionFragmentManager.createConnectionId(context);
|
|
|
|
// Check if we already know the protocol for this connection
|
|
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
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
if (this.httpDetector.canHandle(buffer)) {
|
|
this.connectionProtocols.set(connectionId, 'http');
|
|
const result = this.httpDetector.detectWithContext(buffer, context, options);
|
|
if (result) {
|
|
if (result.isComplete) {
|
|
this.connectionProtocols.delete(connectionId);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Can't determine protocol
|
|
return {
|
|
protocol: 'unknown',
|
|
connectionInfo: { protocol: 'unknown' },
|
|
isComplete: false,
|
|
bytesNeeded: Math.max(
|
|
this.tlsDetector.getMinimumBytes(),
|
|
this.httpDetector.getMinimumBytes()
|
|
)
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Clean up resources
|
|
*/
|
|
static cleanup(): void {
|
|
this.getInstance().cleanupInstance();
|
|
}
|
|
|
|
private cleanupInstance(): void {
|
|
this.fragmentManager.cleanup();
|
|
}
|
|
|
|
/**
|
|
* Destroy detector instance
|
|
*/
|
|
static destroy(): void {
|
|
this.getInstance().destroyInstance();
|
|
this.instance = null as any;
|
|
}
|
|
|
|
private destroyInstance(): void {
|
|
this.fragmentManager.destroy();
|
|
}
|
|
|
|
/**
|
|
* Clean up old connection tracking entries
|
|
*
|
|
* @param _maxAge Maximum age in milliseconds (default: 30 seconds)
|
|
*/
|
|
static cleanupConnections(_maxAge: number = 30000): void {
|
|
// Cleanup is now handled internally by the fragment manager
|
|
this.getInstance().fragmentManager.cleanup();
|
|
}
|
|
|
|
/**
|
|
* Clean up fragments for a specific connection
|
|
*/
|
|
static cleanupConnection(context: IConnectionContext): void {
|
|
const instance = this.getInstance();
|
|
const connectionId = DetectionFragmentManager.createConnectionId(context);
|
|
|
|
// Clean up both TLS and HTTP fragments for this connection
|
|
instance.fragmentManager.getHandler('tls').complete(connectionId);
|
|
instance.fragmentManager.getHandler('http').complete(connectionId);
|
|
|
|
// Remove from connection protocols tracking
|
|
instance.connectionProtocols.delete(connectionId);
|
|
}
|
|
|
|
/**
|
|
* Extract domain from connection info
|
|
*/
|
|
static extractDomain(connectionInfo: any): string | undefined {
|
|
return connectionInfo.domain || connectionInfo.sni || connectionInfo.host;
|
|
}
|
|
|
|
/**
|
|
* Create a connection ID from connection parameters
|
|
* @deprecated Use createConnectionContext instead
|
|
*/
|
|
static createConnectionId(params: {
|
|
sourceIp?: string;
|
|
sourcePort?: number;
|
|
destIp?: string;
|
|
destPort?: number;
|
|
socketId?: string;
|
|
}): string {
|
|
// If socketId is provided, use it
|
|
if (params.socketId) {
|
|
return params.socketId;
|
|
}
|
|
|
|
// Otherwise create from connection tuple
|
|
const { sourceIp = 'unknown', sourcePort = 0, destIp = 'unknown', destPort = 0 } = params;
|
|
return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
|
|
}
|
|
|
|
/**
|
|
* Create a connection context from parameters
|
|
*/
|
|
static createConnectionContext(params: {
|
|
sourceIp?: string;
|
|
sourcePort?: number;
|
|
destIp?: string;
|
|
destPort?: number;
|
|
socketId?: string;
|
|
}): IConnectionContext {
|
|
return {
|
|
id: params.socketId,
|
|
sourceIp: params.sourceIp || 'unknown',
|
|
sourcePort: params.sourcePort || 0,
|
|
destIp: params.destIp || 'unknown',
|
|
destPort: params.destPort || 0,
|
|
timestamp: Date.now()
|
|
};
|
|
}
|
|
} |