147 lines
3.6 KiB
TypeScript
147 lines
3.6 KiB
TypeScript
![]() |
/**
|
||
|
* Routing Information Extractor
|
||
|
*
|
||
|
* Extracts minimal routing information from protocols
|
||
|
* without full parsing
|
||
|
*/
|
||
|
|
||
|
import type { IRoutingInfo, IConnectionContext, TProtocolType } from '../../protocols/common/types.js';
|
||
|
import { SniExtraction } from '../../protocols/tls/sni/sni-extraction.js';
|
||
|
import { HttpParser } from '../../protocols/http/index.js';
|
||
|
|
||
|
/**
|
||
|
* Extracts routing information from protocol data
|
||
|
*/
|
||
|
export class RoutingExtractor {
|
||
|
/**
|
||
|
* Extract routing info based on protocol type
|
||
|
*/
|
||
|
static extract(
|
||
|
data: Buffer,
|
||
|
protocol: TProtocolType,
|
||
|
context?: IConnectionContext
|
||
|
): IRoutingInfo | null {
|
||
|
switch (protocol) {
|
||
|
case 'tls':
|
||
|
case 'https':
|
||
|
return this.extractTlsRouting(data, context);
|
||
|
|
||
|
case 'http':
|
||
|
return this.extractHttpRouting(data);
|
||
|
|
||
|
default:
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract routing from TLS ClientHello (SNI)
|
||
|
*/
|
||
|
private static extractTlsRouting(
|
||
|
data: Buffer,
|
||
|
context?: IConnectionContext
|
||
|
): IRoutingInfo | null {
|
||
|
try {
|
||
|
// Quick SNI extraction without full parsing
|
||
|
const sni = SniExtraction.extractSNI(data);
|
||
|
|
||
|
if (sni) {
|
||
|
return {
|
||
|
domain: sni,
|
||
|
protocol: 'tls',
|
||
|
port: 443 // Default HTTPS port
|
||
|
};
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
} catch (error) {
|
||
|
// Extraction failed, return null
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Extract routing from HTTP headers (Host header)
|
||
|
*/
|
||
|
private static extractHttpRouting(data: Buffer): IRoutingInfo | null {
|
||
|
try {
|
||
|
// Look for first line
|
||
|
const firstLineEnd = data.indexOf('\n');
|
||
|
if (firstLineEnd === -1) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Parse request line
|
||
|
const firstLine = data.subarray(0, firstLineEnd).toString('ascii').trim();
|
||
|
const requestLine = HttpParser.parseRequestLine(firstLine);
|
||
|
|
||
|
if (!requestLine) {
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// Look for Host header
|
||
|
let pos = firstLineEnd + 1;
|
||
|
const maxSearch = Math.min(data.length, 4096); // Don't search too far
|
||
|
|
||
|
while (pos < maxSearch) {
|
||
|
const lineEnd = data.indexOf('\n', pos);
|
||
|
if (lineEnd === -1) break;
|
||
|
|
||
|
const line = data.subarray(pos, lineEnd).toString('ascii').trim();
|
||
|
|
||
|
// Empty line means end of headers
|
||
|
if (line.length === 0) break;
|
||
|
|
||
|
// Check for Host header
|
||
|
if (line.toLowerCase().startsWith('host:')) {
|
||
|
const hostValue = line.substring(5).trim();
|
||
|
const domain = HttpParser.extractDomainFromHost(hostValue);
|
||
|
|
||
|
return {
|
||
|
domain,
|
||
|
path: requestLine.path,
|
||
|
protocol: 'http',
|
||
|
port: 80 // Default HTTP port
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pos = lineEnd + 1;
|
||
|
}
|
||
|
|
||
|
// No Host header found, but we have the path
|
||
|
return {
|
||
|
path: requestLine.path,
|
||
|
protocol: 'http',
|
||
|
port: 80
|
||
|
};
|
||
|
} catch (error) {
|
||
|
// Extraction failed
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Try to extract domain from any protocol
|
||
|
*/
|
||
|
static extractDomain(data: Buffer, hint?: TProtocolType): string | null {
|
||
|
// If we have a hint, use it
|
||
|
if (hint) {
|
||
|
const routing = this.extract(data, hint);
|
||
|
return routing?.domain || null;
|
||
|
}
|
||
|
|
||
|
// Try TLS first (more specific)
|
||
|
const tlsRouting = this.extractTlsRouting(data);
|
||
|
if (tlsRouting?.domain) {
|
||
|
return tlsRouting.domain;
|
||
|
}
|
||
|
|
||
|
// Try HTTP
|
||
|
const httpRouting = this.extractHttpRouting(data);
|
||
|
if (httpRouting?.domain) {
|
||
|
return httpRouting.domain;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
}
|