fix(detection): fix SNI detection in TLS detector
This commit is contained in:
@@ -1,34 +1,45 @@
|
||||
/**
|
||||
* Main protocol detector that orchestrates detection across different protocols
|
||||
* Protocol Detector
|
||||
*
|
||||
* Simplified protocol detection using the new architecture
|
||||
*/
|
||||
|
||||
import type { IDetectionResult, IDetectionOptions, IConnectionInfo } from './models/detection-types.js';
|
||||
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 {
|
||||
/**
|
||||
* Connection tracking for fragmented detection
|
||||
*/
|
||||
private static connectionTracking = new Map<string, {
|
||||
startTime: number;
|
||||
protocol?: 'tls' | 'http' | 'unknown';
|
||||
}>();
|
||||
private static instance: ProtocolDetector;
|
||||
private fragmentManager: DetectionFragmentManager;
|
||||
private tlsDetector: TlsDetector;
|
||||
private httpDetector: HttpDetector;
|
||||
|
||||
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
|
||||
*
|
||||
* @param buffer The buffer to analyze
|
||||
* @param options Detection options
|
||||
* @returns Detection result with protocol information
|
||||
*/
|
||||
static async detect(
|
||||
buffer: Buffer,
|
||||
options?: IDetectionOptions
|
||||
): Promise<IDetectionResult> {
|
||||
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 {
|
||||
@@ -39,18 +50,16 @@ export class ProtocolDetector {
|
||||
}
|
||||
|
||||
// Try TLS detection first (more specific)
|
||||
const tlsDetector = new TlsDetector();
|
||||
if (tlsDetector.canHandle(buffer)) {
|
||||
const tlsResult = tlsDetector.detect(buffer, options);
|
||||
if (this.tlsDetector.canHandle(buffer)) {
|
||||
const tlsResult = this.tlsDetector.detect(buffer, options);
|
||||
if (tlsResult) {
|
||||
return tlsResult;
|
||||
}
|
||||
}
|
||||
|
||||
// Try HTTP detection
|
||||
const httpDetector = new HttpDetector();
|
||||
if (httpDetector.canHandle(buffer)) {
|
||||
const httpResult = httpDetector.detect(buffer, options);
|
||||
if (this.httpDetector.canHandle(buffer)) {
|
||||
const httpResult = this.httpDetector.detect(buffer, options);
|
||||
if (httpResult) {
|
||||
return httpResult;
|
||||
}
|
||||
@@ -66,142 +75,121 @@ export class ProtocolDetector {
|
||||
|
||||
/**
|
||||
* Detect protocol with connection tracking for fragmented data
|
||||
*
|
||||
* @param buffer The buffer to analyze
|
||||
* @param connectionId Unique connection identifier
|
||||
* @param options Detection options
|
||||
* @returns Detection result with protocol information
|
||||
* @deprecated Use detectWithContext instead
|
||||
*/
|
||||
static async detectWithConnectionTracking(
|
||||
buffer: Buffer,
|
||||
connectionId: string,
|
||||
options?: IDetectionOptions
|
||||
): Promise<IDetectionResult> {
|
||||
// Initialize or get connection tracking
|
||||
let tracking = this.connectionTracking.get(connectionId);
|
||||
if (!tracking) {
|
||||
tracking = { startTime: Date.now() };
|
||||
this.connectionTracking.set(connectionId, tracking);
|
||||
}
|
||||
// Convert connection ID to context
|
||||
const context: IConnectionContext = {
|
||||
id: connectionId,
|
||||
sourceIp: 'unknown',
|
||||
sourcePort: 0,
|
||||
destIp: 'unknown',
|
||||
destPort: 0,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
|
||||
// Check timeout
|
||||
if (options?.timeout) {
|
||||
const elapsed = Date.now() - tracking.startTime;
|
||||
if (elapsed > options.timeout) {
|
||||
// Timeout - clean up and return unknown
|
||||
this.connectionTracking.delete(connectionId);
|
||||
TlsDetector.detectWithFragments(Buffer.alloc(0), connectionId); // Force cleanup
|
||||
HttpDetector.detectWithFragments(Buffer.alloc(0), connectionId); // Force cleanup
|
||||
|
||||
return {
|
||||
protocol: 'unknown',
|
||||
connectionInfo: { protocol: 'unknown' },
|
||||
isComplete: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// If we already know the protocol, use the appropriate detector
|
||||
if (tracking.protocol === 'tls') {
|
||||
const result = TlsDetector.detectWithFragments(buffer, connectionId, options);
|
||||
if (result && result.isComplete) {
|
||||
this.connectionTracking.delete(connectionId);
|
||||
}
|
||||
return result || {
|
||||
protocol: 'unknown',
|
||||
connectionInfo: { protocol: 'unknown' },
|
||||
isComplete: true
|
||||
};
|
||||
} else if (tracking.protocol === 'http') {
|
||||
const result = HttpDetector.detectWithFragments(buffer, connectionId, options);
|
||||
if (result && result.isComplete) {
|
||||
this.connectionTracking.delete(connectionId);
|
||||
}
|
||||
return result || {
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
// First time detection - try to determine protocol
|
||||
// Quick checks first
|
||||
if (buffer.length > 0) {
|
||||
// TLS always starts with specific byte values
|
||||
if (buffer[0] >= 0x14 && buffer[0] <= 0x18) {
|
||||
tracking.protocol = 'tls';
|
||||
const result = TlsDetector.detectWithFragments(buffer, connectionId, options);
|
||||
if (result) {
|
||||
if (result.isComplete) {
|
||||
this.connectionTracking.delete(connectionId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// HTTP starts with ASCII text
|
||||
else if (HttpDetector.quickCheck(buffer)) {
|
||||
tracking.protocol = 'http';
|
||||
const result = HttpDetector.detectWithFragments(buffer, connectionId, options);
|
||||
if (result) {
|
||||
if (result.isComplete) {
|
||||
this.connectionTracking.delete(connectionId);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
// First peek to determine protocol type
|
||||
if (this.tlsDetector.canHandle(buffer)) {
|
||||
const result = this.tlsDetector.detectWithContext(buffer, context, options);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't determine protocol yet
|
||||
if (this.httpDetector.canHandle(buffer)) {
|
||||
const result = this.httpDetector.detectWithContext(buffer, context, options);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Can't determine protocol
|
||||
return {
|
||||
protocol: 'unknown',
|
||||
connectionInfo: { protocol: 'unknown' },
|
||||
isComplete: false,
|
||||
bytesNeeded: 10 // Need more data to determine protocol
|
||||
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 {
|
||||
const now = Date.now();
|
||||
const toDelete: string[] = [];
|
||||
|
||||
for (const [connectionId, tracking] of this.connectionTracking.entries()) {
|
||||
if (now - tracking.startTime > maxAge) {
|
||||
toDelete.push(connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
for (const connectionId of toDelete) {
|
||||
this.connectionTracking.delete(connectionId);
|
||||
// Also clean up detector-specific buffers
|
||||
TlsDetector.detectWithFragments(Buffer.alloc(0), connectionId); // Force cleanup
|
||||
HttpDetector.detectWithFragments(Buffer.alloc(0), connectionId); // Force cleanup
|
||||
}
|
||||
|
||||
// Also trigger cleanup in detectors
|
||||
HttpDetector.cleanupFragments(maxAge);
|
||||
// Cleanup is now handled internally by the fragment manager
|
||||
this.getInstance().fragmentManager.cleanup();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract domain from connection info
|
||||
*
|
||||
* @param connectionInfo Connection information from detection
|
||||
* @returns The domain/hostname if found
|
||||
*/
|
||||
static extractDomain(connectionInfo: IConnectionInfo): string | undefined {
|
||||
// For both TLS and HTTP, domain is stored in the domain field
|
||||
return connectionInfo.domain;
|
||||
static extractDomain(connectionInfo: any): string | undefined {
|
||||
return connectionInfo.domain || connectionInfo.sni || connectionInfo.host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connection ID from connection parameters
|
||||
*
|
||||
* @param params Connection parameters
|
||||
* @returns A unique connection identifier
|
||||
* @deprecated Use createConnectionContext instead
|
||||
*/
|
||||
static createConnectionId(params: {
|
||||
sourceIp?: string;
|
||||
@@ -219,4 +207,24 @@ export class ProtocolDetector {
|
||||
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()
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user