@push.rocks/smartproxy 21.0.0 → 21.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.md +18 -0
- package/dist_ts/core/utils/proxy-protocol.d.ts +5 -17
- package/dist_ts/core/utils/proxy-protocol.js +13 -97
- package/dist_ts/core/utils/websocket-utils.d.ts +6 -7
- package/dist_ts/core/utils/websocket-utils.js +10 -66
- package/dist_ts/detection/detectors/http-detector-v2.d.ts +33 -0
- package/dist_ts/detection/detectors/http-detector-v2.js +87 -0
- package/dist_ts/detection/detectors/http-detector.d.ts +33 -0
- package/dist_ts/detection/detectors/http-detector.js +89 -0
- package/dist_ts/detection/detectors/quick-detector.d.ts +28 -0
- package/dist_ts/detection/detectors/quick-detector.js +131 -0
- package/dist_ts/detection/detectors/routing-extractor.d.ts +28 -0
- package/dist_ts/detection/detectors/routing-extractor.js +122 -0
- package/dist_ts/detection/detectors/tls-detector-v2.d.ts +33 -0
- package/dist_ts/detection/detectors/tls-detector-v2.js +80 -0
- package/dist_ts/detection/detectors/tls-detector.d.ts +55 -0
- package/dist_ts/detection/detectors/tls-detector.js +206 -0
- package/dist_ts/detection/index.d.ts +17 -0
- package/dist_ts/detection/index.js +22 -0
- package/dist_ts/detection/models/detection-types.d.ts +87 -0
- package/dist_ts/detection/models/detection-types.js +5 -0
- package/dist_ts/detection/models/interfaces.d.ts +97 -0
- package/dist_ts/detection/models/interfaces.js +5 -0
- package/dist_ts/detection/protocol-detector-v2.d.ts +46 -0
- package/dist_ts/detection/protocol-detector-v2.js +116 -0
- package/dist_ts/detection/protocol-detector.d.ts +74 -0
- package/dist_ts/detection/protocol-detector.js +173 -0
- package/dist_ts/detection/utils/buffer-utils.d.ts +61 -0
- package/dist_ts/detection/utils/buffer-utils.js +127 -0
- package/dist_ts/detection/utils/fragment-manager.d.ts +31 -0
- package/dist_ts/detection/utils/fragment-manager.js +53 -0
- package/dist_ts/detection/utils/parser-utils.d.ts +42 -0
- package/dist_ts/detection/utils/parser-utils.js +63 -0
- package/dist_ts/index.d.ts +2 -0
- package/dist_ts/index.js +3 -1
- package/dist_ts/protocols/common/fragment-handler.d.ts +73 -0
- package/dist_ts/protocols/common/fragment-handler.js +117 -0
- package/dist_ts/protocols/common/index.d.ts +7 -0
- package/dist_ts/protocols/common/index.js +8 -0
- package/dist_ts/protocols/common/types.d.ts +68 -0
- package/dist_ts/protocols/common/types.js +7 -0
- package/dist_ts/protocols/http/constants.d.ts +119 -0
- package/dist_ts/protocols/http/constants.js +200 -0
- package/dist_ts/protocols/http/index.d.ts +7 -0
- package/dist_ts/protocols/http/index.js +8 -0
- package/dist_ts/protocols/http/parser.d.ts +58 -0
- package/dist_ts/protocols/http/parser.js +184 -0
- package/dist_ts/protocols/http/types.d.ts +62 -0
- package/dist_ts/protocols/http/types.js +5 -0
- package/dist_ts/protocols/index.d.ts +11 -0
- package/dist_ts/protocols/index.js +12 -0
- package/dist_ts/protocols/proxy/index.d.ts +6 -0
- package/dist_ts/protocols/proxy/index.js +7 -0
- package/dist_ts/protocols/proxy/parser.d.ts +44 -0
- package/dist_ts/protocols/proxy/parser.js +153 -0
- package/dist_ts/protocols/proxy/types.d.ts +47 -0
- package/dist_ts/protocols/proxy/types.js +6 -0
- package/dist_ts/protocols/tls/alerts/index.d.ts +4 -0
- package/dist_ts/protocols/tls/alerts/index.js +5 -0
- package/dist_ts/protocols/tls/alerts/tls-alert.d.ts +150 -0
- package/dist_ts/protocols/tls/alerts/tls-alert.js +226 -0
- package/dist_ts/protocols/tls/constants.d.ts +122 -0
- package/dist_ts/protocols/tls/constants.js +135 -0
- package/dist_ts/protocols/tls/index.d.ts +12 -0
- package/dist_ts/protocols/tls/index.js +27 -0
- package/dist_ts/protocols/tls/parser.d.ts +53 -0
- package/dist_ts/protocols/tls/parser.js +294 -0
- package/dist_ts/protocols/tls/sni/client-hello-parser.d.ts +100 -0
- package/dist_ts/protocols/tls/sni/client-hello-parser.js +463 -0
- package/dist_ts/protocols/tls/sni/index.d.ts +5 -0
- package/dist_ts/protocols/tls/sni/index.js +6 -0
- package/dist_ts/protocols/tls/sni/sni-extraction.d.ts +58 -0
- package/dist_ts/protocols/tls/sni/sni-extraction.js +275 -0
- package/dist_ts/protocols/tls/types.d.ts +65 -0
- package/dist_ts/protocols/tls/types.js +5 -0
- package/dist_ts/protocols/tls/utils/index.d.ts +4 -0
- package/dist_ts/protocols/tls/utils/index.js +5 -0
- package/dist_ts/protocols/tls/utils/tls-utils.d.ts +158 -0
- package/dist_ts/protocols/tls/utils/tls-utils.js +187 -0
- package/dist_ts/protocols/websocket/constants.d.ts +55 -0
- package/dist_ts/protocols/websocket/constants.js +58 -0
- package/dist_ts/protocols/websocket/index.d.ts +7 -0
- package/dist_ts/protocols/websocket/index.js +8 -0
- package/dist_ts/protocols/websocket/types.d.ts +47 -0
- package/dist_ts/protocols/websocket/types.js +5 -0
- package/dist_ts/protocols/websocket/utils.d.ts +25 -0
- package/dist_ts/protocols/websocket/utils.js +103 -0
- package/dist_ts/proxies/http-proxy/models/http-types.d.ts +25 -27
- package/dist_ts/proxies/http-proxy/models/http-types.js +24 -44
- package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +5 -0
- package/dist_ts/proxies/smart-proxy/route-connection-handler.js +81 -61
- package/dist_ts/proxies/smart-proxy/tls-manager.js +2 -1
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.d.ts +2 -0
- package/dist_ts/proxies/smart-proxy/utils/route-helpers.js +61 -52
- package/dist_ts/tls/index.d.ts +5 -7
- package/dist_ts/tls/index.js +8 -11
- package/dist_ts/tls/sni/client-hello-parser.js +3 -2
- package/dist_ts/tls/sni/sni-handler.js +4 -4
- package/dist_ts/tls/utils/tls-utils.d.ts +1 -110
- package/dist_ts/tls/utils/tls-utils.js +4 -116
- package/package.json +1 -1
- package/readme.plan.md +0 -0
- package/ts/core/utils/proxy-protocol.ts +14 -131
- package/ts/core/utils/websocket-utils.ts +12 -60
- package/ts/detection/detectors/http-detector.ts +114 -0
- package/ts/detection/detectors/quick-detector.ts +148 -0
- package/ts/detection/detectors/routing-extractor.ts +147 -0
- package/ts/detection/detectors/tls-detector.ts +252 -0
- package/ts/detection/index.ts +25 -0
- package/ts/detection/models/detection-types.ts +102 -0
- package/ts/detection/models/interfaces.ts +115 -0
- package/ts/detection/protocol-detector.ts +230 -0
- package/ts/detection/utils/buffer-utils.ts +141 -0
- package/ts/detection/utils/fragment-manager.ts +64 -0
- package/ts/detection/utils/parser-utils.ts +77 -0
- package/ts/index.ts +3 -1
- package/ts/protocols/common/fragment-handler.ts +163 -0
- package/ts/protocols/common/index.ts +8 -0
- package/ts/protocols/common/types.ts +76 -0
- package/ts/protocols/http/constants.ts +219 -0
- package/ts/protocols/http/index.ts +8 -0
- package/ts/protocols/http/parser.ts +219 -0
- package/ts/protocols/http/types.ts +70 -0
- package/ts/protocols/index.ts +12 -0
- package/ts/protocols/proxy/index.ts +7 -0
- package/ts/protocols/proxy/parser.ts +183 -0
- package/ts/protocols/proxy/types.ts +53 -0
- package/ts/{tls → protocols/tls}/alerts/tls-alert.ts +1 -1
- package/ts/protocols/tls/index.ts +37 -0
- package/ts/protocols/tls/sni/index.ts +6 -0
- package/ts/{tls → protocols/tls}/utils/tls-utils.ts +1 -1
- package/ts/protocols/websocket/constants.ts +60 -0
- package/ts/protocols/websocket/index.ts +8 -0
- package/ts/protocols/websocket/types.ts +53 -0
- package/ts/protocols/websocket/utils.ts +98 -0
- package/ts/proxies/http-proxy/models/http-types.ts +29 -46
- package/ts/proxies/smart-proxy/models/interfaces.ts +7 -0
- package/ts/proxies/smart-proxy/route-connection-handler.ts +91 -68
- package/ts/proxies/smart-proxy/tls-manager.ts +1 -0
- package/ts/proxies/smart-proxy/utils/route-helpers.ts +72 -56
- package/ts/tls/index.ts +8 -12
- package/ts/tls/sni/sni-handler.ts +3 -3
- /package/ts/{tls → protocols/tls}/alerts/index.ts +0 -0
- /package/ts/{tls → protocols/tls}/sni/client-hello-parser.ts +0 -0
- /package/ts/{tls → protocols/tls}/sni/sni-extraction.ts +0 -0
- /package/ts/{tls → protocols/tls}/utils/index.ts +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick Protocol Detector
|
|
3
|
+
*
|
|
4
|
+
* Lightweight protocol identification based on minimal bytes
|
|
5
|
+
* No parsing, just identification
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { IProtocolDetector, IProtocolDetectionResult } from '../../protocols/common/types.js';
|
|
9
|
+
import { TlsRecordType } from '../../protocols/tls/index.js';
|
|
10
|
+
import { HttpParser } from '../../protocols/http/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Quick protocol detector for fast identification
|
|
14
|
+
*/
|
|
15
|
+
export class QuickProtocolDetector implements IProtocolDetector {
|
|
16
|
+
/**
|
|
17
|
+
* Check if this detector can handle the data
|
|
18
|
+
*/
|
|
19
|
+
canHandle(data: Buffer): boolean {
|
|
20
|
+
return data.length >= 1;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Perform quick detection based on first few bytes
|
|
25
|
+
*/
|
|
26
|
+
quickDetect(data: Buffer): IProtocolDetectionResult {
|
|
27
|
+
if (data.length === 0) {
|
|
28
|
+
return {
|
|
29
|
+
protocol: 'unknown',
|
|
30
|
+
confidence: 0,
|
|
31
|
+
requiresMoreData: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check for TLS
|
|
36
|
+
const tlsResult = this.checkTls(data);
|
|
37
|
+
if (tlsResult.confidence > 80) {
|
|
38
|
+
return tlsResult;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for HTTP
|
|
42
|
+
const httpResult = this.checkHttp(data);
|
|
43
|
+
if (httpResult.confidence > 80) {
|
|
44
|
+
return httpResult;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Need more data or unknown
|
|
48
|
+
return {
|
|
49
|
+
protocol: 'unknown',
|
|
50
|
+
confidence: 0,
|
|
51
|
+
requiresMoreData: data.length < 20
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if data looks like TLS
|
|
57
|
+
*/
|
|
58
|
+
private checkTls(data: Buffer): IProtocolDetectionResult {
|
|
59
|
+
if (data.length < 3) {
|
|
60
|
+
return {
|
|
61
|
+
protocol: 'tls',
|
|
62
|
+
confidence: 0,
|
|
63
|
+
requiresMoreData: true
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const firstByte = data[0];
|
|
68
|
+
const secondByte = data[1];
|
|
69
|
+
|
|
70
|
+
// Check for valid TLS record type
|
|
71
|
+
const validRecordTypes = [
|
|
72
|
+
TlsRecordType.CHANGE_CIPHER_SPEC,
|
|
73
|
+
TlsRecordType.ALERT,
|
|
74
|
+
TlsRecordType.HANDSHAKE,
|
|
75
|
+
TlsRecordType.APPLICATION_DATA,
|
|
76
|
+
TlsRecordType.HEARTBEAT
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
if (!validRecordTypes.includes(firstByte)) {
|
|
80
|
+
return {
|
|
81
|
+
protocol: 'tls',
|
|
82
|
+
confidence: 0
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check TLS version byte (0x03 for all TLS/SSL versions)
|
|
87
|
+
if (secondByte !== 0x03) {
|
|
88
|
+
return {
|
|
89
|
+
protocol: 'tls',
|
|
90
|
+
confidence: 0
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// High confidence it's TLS
|
|
95
|
+
return {
|
|
96
|
+
protocol: 'tls',
|
|
97
|
+
confidence: 95,
|
|
98
|
+
metadata: {
|
|
99
|
+
recordType: firstByte
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Check if data looks like HTTP
|
|
106
|
+
*/
|
|
107
|
+
private checkHttp(data: Buffer): IProtocolDetectionResult {
|
|
108
|
+
if (data.length < 3) {
|
|
109
|
+
return {
|
|
110
|
+
protocol: 'http',
|
|
111
|
+
confidence: 0,
|
|
112
|
+
requiresMoreData: true
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Quick check for HTTP methods
|
|
117
|
+
const start = data.subarray(0, Math.min(10, data.length)).toString('ascii');
|
|
118
|
+
|
|
119
|
+
// Check common HTTP methods
|
|
120
|
+
const httpMethods = ['GET ', 'POST ', 'PUT ', 'DELETE ', 'HEAD ', 'OPTIONS', 'PATCH ', 'CONNECT', 'TRACE '];
|
|
121
|
+
for (const method of httpMethods) {
|
|
122
|
+
if (start.startsWith(method)) {
|
|
123
|
+
return {
|
|
124
|
+
protocol: 'http',
|
|
125
|
+
confidence: 95,
|
|
126
|
+
metadata: {
|
|
127
|
+
method: method.trim()
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Check if it might be HTTP but need more data
|
|
134
|
+
if (HttpParser.isPrintableAscii(data, Math.min(20, data.length))) {
|
|
135
|
+
// Could be HTTP, but not sure
|
|
136
|
+
return {
|
|
137
|
+
protocol: 'http',
|
|
138
|
+
confidence: 30,
|
|
139
|
+
requiresMoreData: data.length < 20
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
protocol: 'http',
|
|
145
|
+
confidence: 0
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Routing Information Extractor
|
|
3
|
+
*
|
|
4
|
+
* Extracts minimal routing information from protocols
|
|
5
|
+
* without full parsing
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { IRoutingInfo, IConnectionContext, TProtocolType } from '../../protocols/common/types.js';
|
|
9
|
+
import { SniExtraction } from '../../protocols/tls/sni/sni-extraction.js';
|
|
10
|
+
import { HttpParser } from '../../protocols/http/index.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Extracts routing information from protocol data
|
|
14
|
+
*/
|
|
15
|
+
export class RoutingExtractor {
|
|
16
|
+
/**
|
|
17
|
+
* Extract routing info based on protocol type
|
|
18
|
+
*/
|
|
19
|
+
static extract(
|
|
20
|
+
data: Buffer,
|
|
21
|
+
protocol: TProtocolType,
|
|
22
|
+
context?: IConnectionContext
|
|
23
|
+
): IRoutingInfo | null {
|
|
24
|
+
switch (protocol) {
|
|
25
|
+
case 'tls':
|
|
26
|
+
case 'https':
|
|
27
|
+
return this.extractTlsRouting(data, context);
|
|
28
|
+
|
|
29
|
+
case 'http':
|
|
30
|
+
return this.extractHttpRouting(data);
|
|
31
|
+
|
|
32
|
+
default:
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Extract routing from TLS ClientHello (SNI)
|
|
39
|
+
*/
|
|
40
|
+
private static extractTlsRouting(
|
|
41
|
+
data: Buffer,
|
|
42
|
+
context?: IConnectionContext
|
|
43
|
+
): IRoutingInfo | null {
|
|
44
|
+
try {
|
|
45
|
+
// Quick SNI extraction without full parsing
|
|
46
|
+
const sni = SniExtraction.extractSNI(data);
|
|
47
|
+
|
|
48
|
+
if (sni) {
|
|
49
|
+
return {
|
|
50
|
+
domain: sni,
|
|
51
|
+
protocol: 'tls',
|
|
52
|
+
port: 443 // Default HTTPS port
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Extraction failed, return null
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract routing from HTTP headers (Host header)
|
|
65
|
+
*/
|
|
66
|
+
private static extractHttpRouting(data: Buffer): IRoutingInfo | null {
|
|
67
|
+
try {
|
|
68
|
+
// Look for first line
|
|
69
|
+
const firstLineEnd = data.indexOf('\n');
|
|
70
|
+
if (firstLineEnd === -1) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Parse request line
|
|
75
|
+
const firstLine = data.subarray(0, firstLineEnd).toString('ascii').trim();
|
|
76
|
+
const requestLine = HttpParser.parseRequestLine(firstLine);
|
|
77
|
+
|
|
78
|
+
if (!requestLine) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Look for Host header
|
|
83
|
+
let pos = firstLineEnd + 1;
|
|
84
|
+
const maxSearch = Math.min(data.length, 4096); // Don't search too far
|
|
85
|
+
|
|
86
|
+
while (pos < maxSearch) {
|
|
87
|
+
const lineEnd = data.indexOf('\n', pos);
|
|
88
|
+
if (lineEnd === -1) break;
|
|
89
|
+
|
|
90
|
+
const line = data.subarray(pos, lineEnd).toString('ascii').trim();
|
|
91
|
+
|
|
92
|
+
// Empty line means end of headers
|
|
93
|
+
if (line.length === 0) break;
|
|
94
|
+
|
|
95
|
+
// Check for Host header
|
|
96
|
+
if (line.toLowerCase().startsWith('host:')) {
|
|
97
|
+
const hostValue = line.substring(5).trim();
|
|
98
|
+
const domain = HttpParser.extractDomainFromHost(hostValue);
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
domain,
|
|
102
|
+
path: requestLine.path,
|
|
103
|
+
protocol: 'http',
|
|
104
|
+
port: 80 // Default HTTP port
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pos = lineEnd + 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// No Host header found, but we have the path
|
|
112
|
+
return {
|
|
113
|
+
path: requestLine.path,
|
|
114
|
+
protocol: 'http',
|
|
115
|
+
port: 80
|
|
116
|
+
};
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Extraction failed
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Try to extract domain from any protocol
|
|
125
|
+
*/
|
|
126
|
+
static extractDomain(data: Buffer, hint?: TProtocolType): string | null {
|
|
127
|
+
// If we have a hint, use it
|
|
128
|
+
if (hint) {
|
|
129
|
+
const routing = this.extract(data, hint);
|
|
130
|
+
return routing?.domain || null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Try TLS first (more specific)
|
|
134
|
+
const tlsRouting = this.extractTlsRouting(data);
|
|
135
|
+
if (tlsRouting?.domain) {
|
|
136
|
+
return tlsRouting.domain;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Try HTTP
|
|
140
|
+
const httpRouting = this.extractHttpRouting(data);
|
|
141
|
+
if (httpRouting?.domain) {
|
|
142
|
+
return httpRouting.domain;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TLS protocol detector
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// TLS detector doesn't need plugins imports
|
|
6
|
+
import type { IProtocolDetector } from '../models/interfaces.js';
|
|
7
|
+
import type { IDetectionResult, IDetectionOptions, IConnectionInfo } from '../models/detection-types.js';
|
|
8
|
+
import { readUInt16BE, BufferAccumulator } from '../utils/buffer-utils.js';
|
|
9
|
+
import { tlsVersionToString } from '../utils/parser-utils.js';
|
|
10
|
+
|
|
11
|
+
// Import from protocols
|
|
12
|
+
import { TlsRecordType, TlsHandshakeType, TlsExtensionType } from '../../protocols/tls/index.js';
|
|
13
|
+
|
|
14
|
+
// Import TLS utilities for SNI extraction from protocols
|
|
15
|
+
import { SniExtraction } from '../../protocols/tls/sni/sni-extraction.js';
|
|
16
|
+
import { ClientHelloParser } from '../../protocols/tls/sni/client-hello-parser.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* TLS detector implementation
|
|
20
|
+
*/
|
|
21
|
+
export class TlsDetector implements IProtocolDetector {
|
|
22
|
+
/**
|
|
23
|
+
* Minimum bytes needed to identify TLS (record header)
|
|
24
|
+
*/
|
|
25
|
+
private static readonly MIN_TLS_HEADER_SIZE = 5;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Fragment tracking for incomplete handshakes
|
|
29
|
+
*/
|
|
30
|
+
private static fragmentedBuffers = new Map<string, BufferAccumulator>();
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create connection ID from context
|
|
34
|
+
*/
|
|
35
|
+
private createConnectionId(context: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number }): string {
|
|
36
|
+
return `${context.sourceIp || 'unknown'}:${context.sourcePort || 0}->${context.destIp || 'unknown'}:${context.destPort || 0}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Detect TLS protocol from buffer
|
|
41
|
+
*/
|
|
42
|
+
detect(buffer: Buffer, options?: IDetectionOptions): IDetectionResult | null {
|
|
43
|
+
// Check if buffer is too small
|
|
44
|
+
if (buffer.length < TlsDetector.MIN_TLS_HEADER_SIZE) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if this is a TLS record
|
|
49
|
+
if (!this.isTlsRecord(buffer)) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Extract basic TLS info
|
|
54
|
+
const recordType = buffer[0];
|
|
55
|
+
const tlsMajor = buffer[1];
|
|
56
|
+
const tlsMinor = buffer[2];
|
|
57
|
+
const recordLength = readUInt16BE(buffer, 3);
|
|
58
|
+
|
|
59
|
+
// Initialize connection info
|
|
60
|
+
const connectionInfo: IConnectionInfo = {
|
|
61
|
+
protocol: 'tls',
|
|
62
|
+
tlsVersion: tlsVersionToString(tlsMajor, tlsMinor) || undefined
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// If it's a handshake, try to extract more info
|
|
66
|
+
if (recordType === TlsRecordType.HANDSHAKE && buffer.length >= 6) {
|
|
67
|
+
const handshakeType = buffer[5];
|
|
68
|
+
|
|
69
|
+
// For ClientHello, extract SNI and other info
|
|
70
|
+
if (handshakeType === TlsHandshakeType.CLIENT_HELLO) {
|
|
71
|
+
// Check if we have the complete handshake
|
|
72
|
+
const totalRecordLength = recordLength + 5; // Including TLS header
|
|
73
|
+
if (buffer.length >= totalRecordLength) {
|
|
74
|
+
// Extract SNI using existing logic
|
|
75
|
+
const sni = SniExtraction.extractSNI(buffer);
|
|
76
|
+
if (sni) {
|
|
77
|
+
connectionInfo.domain = sni;
|
|
78
|
+
connectionInfo.sni = sni;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Parse ClientHello for additional info
|
|
82
|
+
const parseResult = ClientHelloParser.parseClientHello(buffer);
|
|
83
|
+
if (parseResult.isValid) {
|
|
84
|
+
// Extract ALPN if present
|
|
85
|
+
const alpnExtension = parseResult.extensions.find(
|
|
86
|
+
ext => ext.type === TlsExtensionType.APPLICATION_LAYER_PROTOCOL_NEGOTIATION
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (alpnExtension) {
|
|
90
|
+
connectionInfo.alpn = this.parseAlpnExtension(alpnExtension.data);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Store cipher suites if needed
|
|
94
|
+
if (parseResult.cipherSuites && options?.extractFullHeaders) {
|
|
95
|
+
connectionInfo.cipherSuites = this.parseCipherSuites(parseResult.cipherSuites);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Return complete result
|
|
100
|
+
return {
|
|
101
|
+
protocol: 'tls',
|
|
102
|
+
connectionInfo,
|
|
103
|
+
remainingBuffer: buffer.length > totalRecordLength
|
|
104
|
+
? buffer.subarray(totalRecordLength)
|
|
105
|
+
: undefined,
|
|
106
|
+
isComplete: true
|
|
107
|
+
};
|
|
108
|
+
} else {
|
|
109
|
+
// Incomplete handshake
|
|
110
|
+
return {
|
|
111
|
+
protocol: 'tls',
|
|
112
|
+
connectionInfo,
|
|
113
|
+
isComplete: false,
|
|
114
|
+
bytesNeeded: totalRecordLength
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// For other TLS record types, just return basic info
|
|
121
|
+
return {
|
|
122
|
+
protocol: 'tls',
|
|
123
|
+
connectionInfo,
|
|
124
|
+
isComplete: true,
|
|
125
|
+
remainingBuffer: buffer.length > recordLength + 5
|
|
126
|
+
? buffer.subarray(recordLength + 5)
|
|
127
|
+
: undefined
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Check if buffer can be handled by this detector
|
|
133
|
+
*/
|
|
134
|
+
canHandle(buffer: Buffer): boolean {
|
|
135
|
+
return buffer.length >= TlsDetector.MIN_TLS_HEADER_SIZE &&
|
|
136
|
+
this.isTlsRecord(buffer);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get minimum bytes needed for detection
|
|
141
|
+
*/
|
|
142
|
+
getMinimumBytes(): number {
|
|
143
|
+
return TlsDetector.MIN_TLS_HEADER_SIZE;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if buffer contains a valid TLS record
|
|
148
|
+
*/
|
|
149
|
+
private isTlsRecord(buffer: Buffer): boolean {
|
|
150
|
+
const recordType = buffer[0];
|
|
151
|
+
|
|
152
|
+
// Check for valid record type
|
|
153
|
+
const validTypes = [
|
|
154
|
+
TlsRecordType.CHANGE_CIPHER_SPEC,
|
|
155
|
+
TlsRecordType.ALERT,
|
|
156
|
+
TlsRecordType.HANDSHAKE,
|
|
157
|
+
TlsRecordType.APPLICATION_DATA,
|
|
158
|
+
TlsRecordType.HEARTBEAT
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
if (!validTypes.includes(recordType)) {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Check TLS version bytes (should be 0x03 0x0X)
|
|
166
|
+
if (buffer[1] !== 0x03) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check record length is reasonable
|
|
171
|
+
const recordLength = readUInt16BE(buffer, 3);
|
|
172
|
+
if (recordLength > 16384) { // Max TLS record size
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Parse ALPN extension data
|
|
181
|
+
*/
|
|
182
|
+
private parseAlpnExtension(data: Buffer): string[] {
|
|
183
|
+
const protocols: string[] = [];
|
|
184
|
+
|
|
185
|
+
if (data.length < 2) {
|
|
186
|
+
return protocols;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const listLength = readUInt16BE(data, 0);
|
|
190
|
+
let offset = 2;
|
|
191
|
+
|
|
192
|
+
while (offset < Math.min(2 + listLength, data.length)) {
|
|
193
|
+
const protoLength = data[offset];
|
|
194
|
+
offset++;
|
|
195
|
+
|
|
196
|
+
if (offset + protoLength <= data.length) {
|
|
197
|
+
const protocol = data.subarray(offset, offset + protoLength).toString('ascii');
|
|
198
|
+
protocols.push(protocol);
|
|
199
|
+
offset += protoLength;
|
|
200
|
+
} else {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return protocols;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Parse cipher suites
|
|
210
|
+
*/
|
|
211
|
+
private parseCipherSuites(cipherData: Buffer): number[] {
|
|
212
|
+
const suites: number[] = [];
|
|
213
|
+
|
|
214
|
+
for (let i = 0; i < cipherData.length - 1; i += 2) {
|
|
215
|
+
const suite = readUInt16BE(cipherData, i);
|
|
216
|
+
suites.push(suite);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return suites;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Detect with context for fragmented data
|
|
224
|
+
*/
|
|
225
|
+
detectWithContext(
|
|
226
|
+
buffer: Buffer,
|
|
227
|
+
context: { sourceIp?: string; sourcePort?: number; destIp?: string; destPort?: number },
|
|
228
|
+
options?: IDetectionOptions
|
|
229
|
+
): IDetectionResult | null {
|
|
230
|
+
const connectionId = this.createConnectionId(context);
|
|
231
|
+
|
|
232
|
+
// Get or create buffer accumulator for this connection
|
|
233
|
+
let accumulator = TlsDetector.fragmentedBuffers.get(connectionId);
|
|
234
|
+
if (!accumulator) {
|
|
235
|
+
accumulator = new BufferAccumulator();
|
|
236
|
+
TlsDetector.fragmentedBuffers.set(connectionId, accumulator);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Add new data
|
|
240
|
+
accumulator.append(buffer);
|
|
241
|
+
|
|
242
|
+
// Try detection on accumulated data
|
|
243
|
+
const result = this.detect(accumulator.getBuffer(), options);
|
|
244
|
+
|
|
245
|
+
// If detection is complete or we have too much data, clean up
|
|
246
|
+
if (result?.isComplete || accumulator.length() > 65536) {
|
|
247
|
+
TlsDetector.fragmentedBuffers.delete(connectionId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Protocol Detection Module
|
|
3
|
+
*
|
|
4
|
+
* This module provides unified protocol detection capabilities for
|
|
5
|
+
* both TLS and HTTP protocols, extracting connection information
|
|
6
|
+
* without consuming the data stream.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Main detector
|
|
10
|
+
export * from './protocol-detector.js';
|
|
11
|
+
|
|
12
|
+
// Models
|
|
13
|
+
export * from './models/detection-types.js';
|
|
14
|
+
export * from './models/interfaces.js';
|
|
15
|
+
|
|
16
|
+
// Individual detectors
|
|
17
|
+
export * from './detectors/tls-detector.js';
|
|
18
|
+
export * from './detectors/http-detector.js';
|
|
19
|
+
export * from './detectors/quick-detector.js';
|
|
20
|
+
export * from './detectors/routing-extractor.js';
|
|
21
|
+
|
|
22
|
+
// Utilities
|
|
23
|
+
export * from './utils/buffer-utils.js';
|
|
24
|
+
export * from './utils/parser-utils.js';
|
|
25
|
+
export * from './utils/fragment-manager.js';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for protocol detection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Supported protocol types that can be detected
|
|
7
|
+
*/
|
|
8
|
+
export type TProtocolType = 'tls' | 'http' | 'unknown';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* HTTP method types
|
|
12
|
+
*/
|
|
13
|
+
export type THttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | 'CONNECT' | 'TRACE';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* TLS version identifiers
|
|
17
|
+
*/
|
|
18
|
+
export type TTlsVersion = 'SSLv3' | 'TLSv1.0' | 'TLSv1.1' | 'TLSv1.2' | 'TLSv1.3';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Connection information extracted from protocol detection
|
|
22
|
+
*/
|
|
23
|
+
export interface IConnectionInfo {
|
|
24
|
+
/**
|
|
25
|
+
* The detected protocol type
|
|
26
|
+
*/
|
|
27
|
+
protocol: TProtocolType;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Domain/hostname extracted from the connection
|
|
31
|
+
* - For TLS: from SNI extension
|
|
32
|
+
* - For HTTP: from Host header
|
|
33
|
+
*/
|
|
34
|
+
domain?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* HTTP-specific fields
|
|
38
|
+
*/
|
|
39
|
+
method?: THttpMethod;
|
|
40
|
+
path?: string;
|
|
41
|
+
httpVersion?: string;
|
|
42
|
+
headers?: Record<string, string>;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* TLS-specific fields
|
|
46
|
+
*/
|
|
47
|
+
tlsVersion?: TTlsVersion;
|
|
48
|
+
sni?: string;
|
|
49
|
+
alpn?: string[];
|
|
50
|
+
cipherSuites?: number[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Result of protocol detection
|
|
55
|
+
*/
|
|
56
|
+
export interface IDetectionResult {
|
|
57
|
+
/**
|
|
58
|
+
* The detected protocol type
|
|
59
|
+
*/
|
|
60
|
+
protocol: TProtocolType;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extracted connection information
|
|
64
|
+
*/
|
|
65
|
+
connectionInfo: IConnectionInfo;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Any remaining buffer data after detection headers
|
|
69
|
+
* This can be used to continue processing the stream
|
|
70
|
+
*/
|
|
71
|
+
remainingBuffer?: Buffer;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether the detection is complete or needs more data
|
|
75
|
+
*/
|
|
76
|
+
isComplete: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Minimum bytes needed for complete detection (if incomplete)
|
|
80
|
+
*/
|
|
81
|
+
bytesNeeded?: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Options for protocol detection
|
|
86
|
+
*/
|
|
87
|
+
export interface IDetectionOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Maximum bytes to buffer for detection (default: 8192)
|
|
90
|
+
*/
|
|
91
|
+
maxBufferSize?: number;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Timeout for detection in milliseconds (default: 5000)
|
|
95
|
+
*/
|
|
96
|
+
timeout?: number;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Whether to extract full headers or just essential info
|
|
100
|
+
*/
|
|
101
|
+
extractFullHeaders?: boolean;
|
|
102
|
+
}
|