@push.rocks/smartproxy 3.37.0 → 3.37.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.portproxy.js +14 -185
- package/dist_ts/classes.snihandler.d.ts +45 -0
- package/dist_ts/classes.snihandler.js +274 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.portproxy.ts +13 -199
- package/ts/classes.snihandler.ts +331 -0
- package/ts/index.ts +1 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
/**
|
|
3
|
+
* SNI (Server Name Indication) handler for TLS connections.
|
|
4
|
+
* Provides robust extraction of SNI values from TLS ClientHello messages.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SniHandler {
|
|
7
|
+
private static readonly TLS_HANDSHAKE_RECORD_TYPE;
|
|
8
|
+
private static readonly TLS_CLIENT_HELLO_HANDSHAKE_TYPE;
|
|
9
|
+
private static readonly TLS_SNI_EXTENSION_TYPE;
|
|
10
|
+
private static readonly TLS_SESSION_TICKET_EXTENSION_TYPE;
|
|
11
|
+
private static readonly TLS_SNI_HOST_NAME_TYPE;
|
|
12
|
+
/**
|
|
13
|
+
* Checks if a buffer contains a TLS handshake message (record type 22)
|
|
14
|
+
* @param buffer - The buffer to check
|
|
15
|
+
* @returns true if the buffer starts with a TLS handshake record type
|
|
16
|
+
*/
|
|
17
|
+
static isTlsHandshake(buffer: Buffer): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a buffer contains a TLS ClientHello message
|
|
20
|
+
* @param buffer - The buffer to check
|
|
21
|
+
* @returns true if the buffer appears to be a ClientHello message
|
|
22
|
+
*/
|
|
23
|
+
static isClientHello(buffer: Buffer): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
|
|
26
|
+
* Implements robust parsing with support for session resumption edge cases.
|
|
27
|
+
*
|
|
28
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
29
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
30
|
+
* @returns The extracted server name or undefined if not found
|
|
31
|
+
*/
|
|
32
|
+
static extractSNI(buffer: Buffer, enableLogging?: boolean): string | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Attempts to extract SNI from an initial ClientHello packet and handles
|
|
35
|
+
* session resumption edge cases more robustly than the standard extraction.
|
|
36
|
+
*
|
|
37
|
+
* This method is specifically designed for Chrome and other browsers that
|
|
38
|
+
* may send different ClientHello formats during session resumption.
|
|
39
|
+
*
|
|
40
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
41
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
42
|
+
* @returns The extracted server name or undefined if not found
|
|
43
|
+
*/
|
|
44
|
+
static extractSNIWithResumptionSupport(buffer: Buffer, enableLogging?: boolean): string | undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { Buffer } from 'buffer';
|
|
2
|
+
/**
|
|
3
|
+
* SNI (Server Name Indication) handler for TLS connections.
|
|
4
|
+
* Provides robust extraction of SNI values from TLS ClientHello messages.
|
|
5
|
+
*/
|
|
6
|
+
export class SniHandler {
|
|
7
|
+
// TLS record types and constants
|
|
8
|
+
static { this.TLS_HANDSHAKE_RECORD_TYPE = 22; }
|
|
9
|
+
static { this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE = 1; }
|
|
10
|
+
static { this.TLS_SNI_EXTENSION_TYPE = 0x0000; }
|
|
11
|
+
static { this.TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023; }
|
|
12
|
+
static { this.TLS_SNI_HOST_NAME_TYPE = 0; }
|
|
13
|
+
/**
|
|
14
|
+
* Checks if a buffer contains a TLS handshake message (record type 22)
|
|
15
|
+
* @param buffer - The buffer to check
|
|
16
|
+
* @returns true if the buffer starts with a TLS handshake record type
|
|
17
|
+
*/
|
|
18
|
+
static isTlsHandshake(buffer) {
|
|
19
|
+
return buffer.length > 0 && buffer[0] === this.TLS_HANDSHAKE_RECORD_TYPE;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Checks if a buffer contains a TLS ClientHello message
|
|
23
|
+
* @param buffer - The buffer to check
|
|
24
|
+
* @returns true if the buffer appears to be a ClientHello message
|
|
25
|
+
*/
|
|
26
|
+
static isClientHello(buffer) {
|
|
27
|
+
// Minimum ClientHello size (TLS record header + handshake header)
|
|
28
|
+
if (buffer.length < 9) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
// Check record type (must be TLS_HANDSHAKE_RECORD_TYPE)
|
|
32
|
+
if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// Skip version and length in TLS record header (5 bytes total)
|
|
36
|
+
// Check handshake type at byte 5 (must be CLIENT_HELLO)
|
|
37
|
+
return buffer[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
|
|
41
|
+
* Implements robust parsing with support for session resumption edge cases.
|
|
42
|
+
*
|
|
43
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
44
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
45
|
+
* @returns The extracted server name or undefined if not found
|
|
46
|
+
*/
|
|
47
|
+
static extractSNI(buffer, enableLogging = false) {
|
|
48
|
+
// Logging helper
|
|
49
|
+
const log = (message) => {
|
|
50
|
+
if (enableLogging) {
|
|
51
|
+
console.log(`[SNI Extraction] ${message}`);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
try {
|
|
55
|
+
// Buffer must be at least 5 bytes (TLS record header)
|
|
56
|
+
if (buffer.length < 5) {
|
|
57
|
+
log('Buffer too small for TLS record header');
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
// Check record type (must be TLS_HANDSHAKE_RECORD_TYPE = 22)
|
|
61
|
+
if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
|
|
62
|
+
log(`Not a TLS handshake record: ${buffer[0]}`);
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
// Check TLS version
|
|
66
|
+
const majorVersion = buffer[1];
|
|
67
|
+
const minorVersion = buffer[2];
|
|
68
|
+
log(`TLS version: ${majorVersion}.${minorVersion}`);
|
|
69
|
+
// Parse record length (bytes 3-4, big-endian)
|
|
70
|
+
const recordLength = (buffer[3] << 8) + buffer[4];
|
|
71
|
+
log(`Record length: ${recordLength}`);
|
|
72
|
+
// Validate record length against buffer size
|
|
73
|
+
if (buffer.length < recordLength + 5) {
|
|
74
|
+
log('Buffer smaller than expected record length');
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
// Start of handshake message in the buffer
|
|
78
|
+
let pos = 5;
|
|
79
|
+
// Check handshake type (must be CLIENT_HELLO = 1)
|
|
80
|
+
if (buffer[pos] !== this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
|
|
81
|
+
log(`Not a ClientHello message: ${buffer[pos]}`);
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
// Skip handshake type (1 byte)
|
|
85
|
+
pos += 1;
|
|
86
|
+
// Parse handshake length (3 bytes, big-endian)
|
|
87
|
+
const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2];
|
|
88
|
+
log(`Handshake length: ${handshakeLength}`);
|
|
89
|
+
// Skip handshake length (3 bytes)
|
|
90
|
+
pos += 3;
|
|
91
|
+
// Check client version (2 bytes)
|
|
92
|
+
const clientMajorVersion = buffer[pos];
|
|
93
|
+
const clientMinorVersion = buffer[pos + 1];
|
|
94
|
+
log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`);
|
|
95
|
+
// Skip client version (2 bytes)
|
|
96
|
+
pos += 2;
|
|
97
|
+
// Skip client random (32 bytes)
|
|
98
|
+
pos += 32;
|
|
99
|
+
// Parse session ID
|
|
100
|
+
if (pos + 1 > buffer.length) {
|
|
101
|
+
log('Buffer too small for session ID length');
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
const sessionIdLength = buffer[pos];
|
|
105
|
+
log(`Session ID length: ${sessionIdLength}`);
|
|
106
|
+
// Skip session ID length (1 byte) and session ID
|
|
107
|
+
pos += 1 + sessionIdLength;
|
|
108
|
+
// Check if we have enough bytes left
|
|
109
|
+
if (pos + 2 > buffer.length) {
|
|
110
|
+
log('Buffer too small for cipher suites length');
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
// Parse cipher suites length (2 bytes, big-endian)
|
|
114
|
+
const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
115
|
+
log(`Cipher suites length: ${cipherSuitesLength}`);
|
|
116
|
+
// Skip cipher suites length (2 bytes) and cipher suites
|
|
117
|
+
pos += 2 + cipherSuitesLength;
|
|
118
|
+
// Check if we have enough bytes left
|
|
119
|
+
if (pos + 1 > buffer.length) {
|
|
120
|
+
log('Buffer too small for compression methods length');
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
// Parse compression methods length (1 byte)
|
|
124
|
+
const compressionMethodsLength = buffer[pos];
|
|
125
|
+
log(`Compression methods length: ${compressionMethodsLength}`);
|
|
126
|
+
// Skip compression methods length (1 byte) and compression methods
|
|
127
|
+
pos += 1 + compressionMethodsLength;
|
|
128
|
+
// Check if we have enough bytes for extensions length
|
|
129
|
+
if (pos + 2 > buffer.length) {
|
|
130
|
+
log('No extensions present or buffer too small');
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
133
|
+
// Parse extensions length (2 bytes, big-endian)
|
|
134
|
+
const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
135
|
+
log(`Extensions length: ${extensionsLength}`);
|
|
136
|
+
// Skip extensions length (2 bytes)
|
|
137
|
+
pos += 2;
|
|
138
|
+
// Extensions end position
|
|
139
|
+
const extensionsEnd = pos + extensionsLength;
|
|
140
|
+
// Check if extensions length is valid
|
|
141
|
+
if (extensionsEnd > buffer.length) {
|
|
142
|
+
log('Extensions length exceeds buffer size');
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
// Track if we found session tickets (for improved resumption handling)
|
|
146
|
+
let hasSessionTicket = false;
|
|
147
|
+
// Iterate through extensions
|
|
148
|
+
while (pos + 4 <= extensionsEnd) {
|
|
149
|
+
// Parse extension type (2 bytes, big-endian)
|
|
150
|
+
const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
|
|
151
|
+
log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`);
|
|
152
|
+
// Skip extension type (2 bytes)
|
|
153
|
+
pos += 2;
|
|
154
|
+
// Parse extension length (2 bytes, big-endian)
|
|
155
|
+
const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
156
|
+
log(`Extension length: ${extensionLength}`);
|
|
157
|
+
// Skip extension length (2 bytes)
|
|
158
|
+
pos += 2;
|
|
159
|
+
// Check if this is the SNI extension
|
|
160
|
+
if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
|
|
161
|
+
log('Found SNI extension');
|
|
162
|
+
// Ensure we have enough bytes for the server name list
|
|
163
|
+
if (pos + 2 > extensionsEnd) {
|
|
164
|
+
log('Extension too small for server name list length');
|
|
165
|
+
pos += extensionLength; // Skip this extension
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Parse server name list length (2 bytes, big-endian)
|
|
169
|
+
const serverNameListLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
170
|
+
log(`Server name list length: ${serverNameListLength}`);
|
|
171
|
+
// Skip server name list length (2 bytes)
|
|
172
|
+
pos += 2;
|
|
173
|
+
// Ensure server name list length is valid
|
|
174
|
+
if (pos + serverNameListLength > extensionsEnd) {
|
|
175
|
+
log('Server name list length exceeds extension size');
|
|
176
|
+
break; // Exit the loop, extension parsing is broken
|
|
177
|
+
}
|
|
178
|
+
// End position of server name list
|
|
179
|
+
const serverNameListEnd = pos + serverNameListLength;
|
|
180
|
+
// Iterate through server names
|
|
181
|
+
while (pos + 3 <= serverNameListEnd) {
|
|
182
|
+
// Check name type (must be HOST_NAME_TYPE = 0 for hostname)
|
|
183
|
+
const nameType = buffer[pos];
|
|
184
|
+
log(`Name type: ${nameType}`);
|
|
185
|
+
if (nameType !== this.TLS_SNI_HOST_NAME_TYPE) {
|
|
186
|
+
log(`Unsupported name type: ${nameType}`);
|
|
187
|
+
pos += 1; // Skip name type (1 byte)
|
|
188
|
+
// Skip name length (2 bytes) and name data
|
|
189
|
+
if (pos + 2 <= serverNameListEnd) {
|
|
190
|
+
const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
191
|
+
pos += 2 + nameLength;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
log('Invalid server name entry');
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
// Skip name type (1 byte)
|
|
200
|
+
pos += 1;
|
|
201
|
+
// Ensure we have enough bytes for name length
|
|
202
|
+
if (pos + 2 > serverNameListEnd) {
|
|
203
|
+
log('Server name entry too small for name length');
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
// Parse name length (2 bytes, big-endian)
|
|
207
|
+
const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
|
|
208
|
+
log(`Name length: ${nameLength}`);
|
|
209
|
+
// Skip name length (2 bytes)
|
|
210
|
+
pos += 2;
|
|
211
|
+
// Ensure we have enough bytes for the name
|
|
212
|
+
if (pos + nameLength > serverNameListEnd) {
|
|
213
|
+
log('Name length exceeds server name list size');
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
// Extract server name (hostname)
|
|
217
|
+
const serverName = buffer.slice(pos, pos + nameLength).toString('utf8');
|
|
218
|
+
log(`Extracted server name: ${serverName}`);
|
|
219
|
+
return serverName;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
|
|
223
|
+
// If we encounter a session ticket extension, mark it for later
|
|
224
|
+
log('Found session ticket extension');
|
|
225
|
+
hasSessionTicket = true;
|
|
226
|
+
pos += extensionLength; // Skip this extension
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Skip this extension
|
|
230
|
+
pos += extensionLength;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Log if we found a session ticket but no SNI
|
|
234
|
+
if (hasSessionTicket) {
|
|
235
|
+
log('Session ticket present but no SNI found - possible resumption scenario');
|
|
236
|
+
}
|
|
237
|
+
log('No SNI extension found in ClientHello');
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
log(`Error parsing SNI: ${error instanceof Error ? error.message : String(error)}`);
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Attempts to extract SNI from an initial ClientHello packet and handles
|
|
247
|
+
* session resumption edge cases more robustly than the standard extraction.
|
|
248
|
+
*
|
|
249
|
+
* This method is specifically designed for Chrome and other browsers that
|
|
250
|
+
* may send different ClientHello formats during session resumption.
|
|
251
|
+
*
|
|
252
|
+
* @param buffer - The buffer containing the TLS ClientHello message
|
|
253
|
+
* @param enableLogging - Whether to enable detailed debug logging
|
|
254
|
+
* @returns The extracted server name or undefined if not found
|
|
255
|
+
*/
|
|
256
|
+
static extractSNIWithResumptionSupport(buffer, enableLogging = false) {
|
|
257
|
+
// First try the standard SNI extraction
|
|
258
|
+
const standardSni = this.extractSNI(buffer, enableLogging);
|
|
259
|
+
if (standardSni) {
|
|
260
|
+
return standardSni;
|
|
261
|
+
}
|
|
262
|
+
// If standard extraction failed and we have a valid ClientHello,
|
|
263
|
+
// this might be a session resumption with non-standard format
|
|
264
|
+
if (this.isClientHello(buffer)) {
|
|
265
|
+
if (enableLogging) {
|
|
266
|
+
console.log('[SNI Extraction] Detected ClientHello without standard SNI, possible session resumption');
|
|
267
|
+
}
|
|
268
|
+
// Additional handling could be implemented here for specific browser behaviors
|
|
269
|
+
// For now, this is a placeholder for future improvements
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zbmloYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zbmloYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFaEM7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFVBQVU7SUFDckIsaUNBQWlDO2FBQ1QsOEJBQXlCLEdBQUcsRUFBRSxDQUFDO2FBQy9CLG9DQUErQixHQUFHLENBQUMsQ0FBQzthQUNwQywyQkFBc0IsR0FBRyxNQUFNLENBQUM7YUFDaEMsc0NBQWlDLEdBQUcsTUFBTSxDQUFDO2FBQzNDLDJCQUFzQixHQUFHLENBQUMsQ0FBQztJQUVuRDs7OztPQUlHO0lBQ0ksTUFBTSxDQUFDLGNBQWMsQ0FBQyxNQUFjO1FBQ3pDLE9BQU8sTUFBTSxDQUFDLE1BQU0sR0FBRyxDQUFDLElBQUksTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyx5QkFBeUIsQ0FBQztJQUMzRSxDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLE1BQU0sQ0FBQyxhQUFhLENBQUMsTUFBYztRQUN4QyxrRUFBa0U7UUFDbEUsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQ3RCLE9BQU8sS0FBSyxDQUFDO1FBQ2YsQ0FBQztRQUVELHdEQUF3RDtRQUN4RCxJQUFJLE1BQU0sQ0FBQyxDQUFDLENBQUMsS0FBSyxJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQztZQUNqRCxPQUFPLEtBQUssQ0FBQztRQUNmLENBQUM7UUFFRCwrREFBK0Q7UUFDL0Qsd0RBQXdEO1FBQ3hELE9BQU8sTUFBTSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQywrQkFBK0IsQ0FBQztJQUM1RCxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBYyxFQUFFLGdCQUF5QixLQUFLO1FBQ3JFLGlCQUFp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
|
package/dist_ts/index.d.ts
CHANGED
package/dist_ts/index.js
CHANGED
|
@@ -3,4 +3,5 @@ export * from './classes.networkproxy.js';
|
|
|
3
3
|
export * from './classes.portproxy.js';
|
|
4
4
|
export * from './classes.port80handler.js';
|
|
5
5
|
export * from './classes.sslredirect.js';
|
|
6
|
-
|
|
6
|
+
export * from './classes.snihandler.js';
|
|
7
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMkJBQTJCLENBQUM7QUFDMUMsY0FBYyx3QkFBd0IsQ0FBQztBQUN2QyxjQUFjLDRCQUE0QixDQUFDO0FBQzNDLGNBQWMsMEJBQTBCLENBQUM7QUFDekMsY0FBYyx5QkFBeUIsQ0FBQyJ9
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartproxy",
|
|
3
|
-
"version": "3.37.
|
|
3
|
+
"version": "3.37.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '3.37.
|
|
6
|
+
version: '3.37.1',
|
|
7
7
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
|
8
8
|
}
|
package/ts/classes.portproxy.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { NetworkProxy } from './classes.networkproxy.js';
|
|
3
|
+
import { SniHandler } from './classes.snihandler.js';
|
|
3
4
|
|
|
4
5
|
/** Domain configuration with per-domain allowed port ranges */
|
|
5
6
|
export interface IDomainConfig {
|
|
@@ -117,192 +118,8 @@ interface IConnectionRecord {
|
|
|
117
118
|
domainSwitches?: number; // Number of times the domain has been switched on this connection
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* Enhanced for robustness and detailed logging.
|
|
123
|
-
* @param buffer - Buffer containing the TLS ClientHello.
|
|
124
|
-
* @param enableLogging - Whether to enable detailed logging.
|
|
125
|
-
* @returns The server name if found, otherwise undefined.
|
|
126
|
-
*/
|
|
127
|
-
function extractSNI(buffer: Buffer, enableLogging: boolean = false): string | undefined {
|
|
128
|
-
try {
|
|
129
|
-
// Check if buffer is too small for TLS
|
|
130
|
-
if (buffer.length < 5) {
|
|
131
|
-
if (enableLogging) console.log('Buffer too small for TLS header');
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Check record type (has to be handshake - 22)
|
|
136
|
-
const recordType = buffer.readUInt8(0);
|
|
137
|
-
if (recordType !== 22) {
|
|
138
|
-
if (enableLogging) console.log(`Not a TLS handshake. Record type: ${recordType}`);
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Check TLS version (has to be 3.1 or higher)
|
|
143
|
-
const majorVersion = buffer.readUInt8(1);
|
|
144
|
-
const minorVersion = buffer.readUInt8(2);
|
|
145
|
-
if (enableLogging) console.log(`TLS Version: ${majorVersion}.${minorVersion}`);
|
|
146
|
-
|
|
147
|
-
// Check record length
|
|
148
|
-
const recordLength = buffer.readUInt16BE(3);
|
|
149
|
-
if (buffer.length < 5 + recordLength) {
|
|
150
|
-
if (enableLogging)
|
|
151
|
-
console.log(
|
|
152
|
-
`Buffer too small for TLS record. Expected: ${5 + recordLength}, Got: ${buffer.length}`
|
|
153
|
-
);
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
let offset = 5;
|
|
158
|
-
const handshakeType = buffer.readUInt8(offset);
|
|
159
|
-
if (handshakeType !== 1) {
|
|
160
|
-
if (enableLogging) console.log(`Not a ClientHello. Handshake type: ${handshakeType}`);
|
|
161
|
-
return undefined;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
offset += 4; // Skip handshake header (type + length)
|
|
165
|
-
|
|
166
|
-
// Client version
|
|
167
|
-
const clientMajorVersion = buffer.readUInt8(offset);
|
|
168
|
-
const clientMinorVersion = buffer.readUInt8(offset + 1);
|
|
169
|
-
if (enableLogging) console.log(`Client Version: ${clientMajorVersion}.${clientMinorVersion}`);
|
|
170
|
-
|
|
171
|
-
offset += 2 + 32; // Skip client version and random
|
|
172
|
-
|
|
173
|
-
// Session ID
|
|
174
|
-
const sessionIDLength = buffer.readUInt8(offset);
|
|
175
|
-
if (enableLogging) console.log(`Session ID Length: ${sessionIDLength}`);
|
|
176
|
-
offset += 1 + sessionIDLength; // Skip session ID
|
|
177
|
-
|
|
178
|
-
// Cipher suites
|
|
179
|
-
if (offset + 2 > buffer.length) {
|
|
180
|
-
if (enableLogging) console.log('Buffer too small for cipher suites length');
|
|
181
|
-
return undefined;
|
|
182
|
-
}
|
|
183
|
-
const cipherSuitesLength = buffer.readUInt16BE(offset);
|
|
184
|
-
if (enableLogging) console.log(`Cipher Suites Length: ${cipherSuitesLength}`);
|
|
185
|
-
offset += 2 + cipherSuitesLength; // Skip cipher suites
|
|
186
|
-
|
|
187
|
-
// Compression methods
|
|
188
|
-
if (offset + 1 > buffer.length) {
|
|
189
|
-
if (enableLogging) console.log('Buffer too small for compression methods length');
|
|
190
|
-
return undefined;
|
|
191
|
-
}
|
|
192
|
-
const compressionMethodsLength = buffer.readUInt8(offset);
|
|
193
|
-
if (enableLogging) console.log(`Compression Methods Length: ${compressionMethodsLength}`);
|
|
194
|
-
offset += 1 + compressionMethodsLength; // Skip compression methods
|
|
195
|
-
|
|
196
|
-
// Extensions
|
|
197
|
-
if (offset + 2 > buffer.length) {
|
|
198
|
-
if (enableLogging) console.log('Buffer too small for extensions length');
|
|
199
|
-
return undefined;
|
|
200
|
-
}
|
|
201
|
-
const extensionsLength = buffer.readUInt16BE(offset);
|
|
202
|
-
if (enableLogging) console.log(`Extensions Length: ${extensionsLength}`);
|
|
203
|
-
offset += 2;
|
|
204
|
-
const extensionsEnd = offset + extensionsLength;
|
|
205
|
-
|
|
206
|
-
if (extensionsEnd > buffer.length) {
|
|
207
|
-
if (enableLogging)
|
|
208
|
-
console.log(
|
|
209
|
-
`Buffer too small for extensions. Expected end: ${extensionsEnd}, Buffer length: ${buffer.length}`
|
|
210
|
-
);
|
|
211
|
-
return undefined;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Parse extensions
|
|
215
|
-
while (offset + 4 <= extensionsEnd) {
|
|
216
|
-
const extensionType = buffer.readUInt16BE(offset);
|
|
217
|
-
const extensionLength = buffer.readUInt16BE(offset + 2);
|
|
218
|
-
|
|
219
|
-
if (enableLogging)
|
|
220
|
-
console.log(`Extension Type: 0x${extensionType.toString(16)}, Length: ${extensionLength}`);
|
|
221
|
-
|
|
222
|
-
offset += 4;
|
|
223
|
-
|
|
224
|
-
if (extensionType === 0x0000) {
|
|
225
|
-
// SNI extension
|
|
226
|
-
if (offset + 2 > buffer.length) {
|
|
227
|
-
if (enableLogging) console.log('Buffer too small for SNI list length');
|
|
228
|
-
return undefined;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
const sniListLength = buffer.readUInt16BE(offset);
|
|
232
|
-
if (enableLogging) console.log(`SNI List Length: ${sniListLength}`);
|
|
233
|
-
offset += 2;
|
|
234
|
-
const sniListEnd = offset + sniListLength;
|
|
235
|
-
|
|
236
|
-
if (sniListEnd > buffer.length) {
|
|
237
|
-
if (enableLogging)
|
|
238
|
-
console.log(
|
|
239
|
-
`Buffer too small for SNI list. Expected end: ${sniListEnd}, Buffer length: ${buffer.length}`
|
|
240
|
-
);
|
|
241
|
-
return undefined;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
while (offset + 3 < sniListEnd) {
|
|
245
|
-
const nameType = buffer.readUInt8(offset++);
|
|
246
|
-
const nameLen = buffer.readUInt16BE(offset);
|
|
247
|
-
offset += 2;
|
|
248
|
-
|
|
249
|
-
if (enableLogging) console.log(`Name Type: ${nameType}, Name Length: ${nameLen}`);
|
|
250
|
-
|
|
251
|
-
if (nameType === 0) {
|
|
252
|
-
// host_name
|
|
253
|
-
if (offset + nameLen > buffer.length) {
|
|
254
|
-
if (enableLogging)
|
|
255
|
-
console.log(
|
|
256
|
-
`Buffer too small for hostname. Expected: ${offset + nameLen}, Got: ${
|
|
257
|
-
buffer.length
|
|
258
|
-
}`
|
|
259
|
-
);
|
|
260
|
-
return undefined;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const serverName = buffer.toString('utf8', offset, offset + nameLen);
|
|
264
|
-
if (enableLogging) console.log(`Extracted SNI: ${serverName}`);
|
|
265
|
-
return serverName;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
offset += nameLen;
|
|
269
|
-
}
|
|
270
|
-
break;
|
|
271
|
-
} else {
|
|
272
|
-
offset += extensionLength;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (enableLogging) console.log('No SNI extension found');
|
|
277
|
-
return undefined;
|
|
278
|
-
} catch (err) {
|
|
279
|
-
console.log(`Error extracting SNI: ${err}`);
|
|
280
|
-
return undefined;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Checks if a TLS record is a proper ClientHello message (more accurate than just checking record type)
|
|
286
|
-
* @param buffer - Buffer containing the TLS record
|
|
287
|
-
* @returns true if the buffer contains a proper ClientHello message
|
|
288
|
-
*/
|
|
289
|
-
function isClientHello(buffer: Buffer): boolean {
|
|
290
|
-
try {
|
|
291
|
-
if (buffer.length < 9) return false; // Too small for a proper ClientHello
|
|
292
|
-
|
|
293
|
-
// Check record type (has to be handshake - 22)
|
|
294
|
-
if (buffer.readUInt8(0) !== 22) return false;
|
|
295
|
-
|
|
296
|
-
// After the TLS record header (5 bytes), check the handshake type (1 for ClientHello)
|
|
297
|
-
if (buffer.readUInt8(5) !== 1) return false;
|
|
298
|
-
|
|
299
|
-
// Basic checks passed, this appears to be a ClientHello
|
|
300
|
-
return true;
|
|
301
|
-
} catch (err) {
|
|
302
|
-
console.log(`Error checking for ClientHello: ${err}`);
|
|
303
|
-
return false;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
121
|
+
// SNI functions are now imported from SniHandler class
|
|
122
|
+
// No need for wrapper functions
|
|
306
123
|
|
|
307
124
|
// Helper: Check if a port falls within any of the given port ranges
|
|
308
125
|
const isPortInRanges = (port: number, ranges: Array<{ from: number; to: number }>): boolean => {
|
|
@@ -346,10 +163,7 @@ const generateConnectionId = (): string => {
|
|
|
346
163
|
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
347
164
|
};
|
|
348
165
|
|
|
349
|
-
//
|
|
350
|
-
const isTlsHandshake = (buffer: Buffer): boolean => {
|
|
351
|
-
return buffer.length > 0 && buffer[0] === 22; // ContentType.handshake
|
|
352
|
-
};
|
|
166
|
+
// SNI functions are now imported from SniHandler class
|
|
353
167
|
|
|
354
168
|
// Helper: Ensure timeout values don't exceed Node.js max safe integer
|
|
355
169
|
const ensureSafeTimeout = (timeout: number): number => {
|
|
@@ -761,7 +575,7 @@ export class PortProxy {
|
|
|
761
575
|
record.bytesReceived += chunk.length;
|
|
762
576
|
|
|
763
577
|
// Check for TLS handshake
|
|
764
|
-
if (!record.isTLS && isTlsHandshake(chunk)) {
|
|
578
|
+
if (!record.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
765
579
|
record.isTLS = true;
|
|
766
580
|
|
|
767
581
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1049,10 +863,10 @@ export class PortProxy {
|
|
|
1049
863
|
// Define a handler for checking renegotiation with improved detection
|
|
1050
864
|
const renegotiationHandler = (renegChunk: Buffer) => {
|
|
1051
865
|
// Only process if this looks like a TLS ClientHello
|
|
1052
|
-
if (isClientHello(renegChunk)) {
|
|
866
|
+
if (SniHandler.isClientHello(renegChunk)) {
|
|
1053
867
|
try {
|
|
1054
868
|
// Extract SNI from ClientHello
|
|
1055
|
-
const newSNI =
|
|
869
|
+
const newSNI = SniHandler.extractSNIWithResumptionSupport(renegChunk, this.settings.enableTlsDebugLogging);
|
|
1056
870
|
|
|
1057
871
|
// Skip if no SNI was found
|
|
1058
872
|
if (!newSNI) return;
|
|
@@ -1644,7 +1458,7 @@ export class PortProxy {
|
|
|
1644
1458
|
connectionRecord.hasReceivedInitialData = true;
|
|
1645
1459
|
|
|
1646
1460
|
// Check if this looks like a TLS handshake
|
|
1647
|
-
if (isTlsHandshake(chunk)) {
|
|
1461
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1648
1462
|
connectionRecord.isTLS = true;
|
|
1649
1463
|
|
|
1650
1464
|
// Forward directly to NetworkProxy without SNI processing
|
|
@@ -1706,7 +1520,7 @@ export class PortProxy {
|
|
|
1706
1520
|
this.updateActivity(connectionRecord);
|
|
1707
1521
|
|
|
1708
1522
|
// Check for TLS handshake if this is the first chunk
|
|
1709
|
-
if (!connectionRecord.isTLS && isTlsHandshake(chunk)) {
|
|
1523
|
+
if (!connectionRecord.isTLS && SniHandler.isTlsHandshake(chunk)) {
|
|
1710
1524
|
connectionRecord.isTLS = true;
|
|
1711
1525
|
|
|
1712
1526
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1714,7 +1528,7 @@ export class PortProxy {
|
|
|
1714
1528
|
`[${connectionId}] TLS handshake detected from ${remoteIP}, ${chunk.length} bytes`
|
|
1715
1529
|
);
|
|
1716
1530
|
// Try to extract SNI and log detailed debug info
|
|
1717
|
-
|
|
1531
|
+
SniHandler.extractSNIWithResumptionSupport(chunk, true);
|
|
1718
1532
|
}
|
|
1719
1533
|
}
|
|
1720
1534
|
});
|
|
@@ -1743,7 +1557,7 @@ export class PortProxy {
|
|
|
1743
1557
|
connectionRecord.hasReceivedInitialData = true;
|
|
1744
1558
|
|
|
1745
1559
|
// Check if this looks like a TLS handshake
|
|
1746
|
-
const isTlsHandshakeDetected = initialChunk && isTlsHandshake(initialChunk);
|
|
1560
|
+
const isTlsHandshakeDetected = initialChunk && SniHandler.isTlsHandshake(initialChunk);
|
|
1747
1561
|
if (isTlsHandshakeDetected) {
|
|
1748
1562
|
connectionRecord.isTLS = true;
|
|
1749
1563
|
|
|
@@ -1912,7 +1726,7 @@ export class PortProxy {
|
|
|
1912
1726
|
// Try to extract SNI
|
|
1913
1727
|
let serverName = '';
|
|
1914
1728
|
|
|
1915
|
-
if (isTlsHandshake(chunk)) {
|
|
1729
|
+
if (SniHandler.isTlsHandshake(chunk)) {
|
|
1916
1730
|
connectionRecord.isTLS = true;
|
|
1917
1731
|
|
|
1918
1732
|
if (this.settings.enableTlsDebugLogging) {
|
|
@@ -1921,7 +1735,7 @@ export class PortProxy {
|
|
|
1921
1735
|
);
|
|
1922
1736
|
}
|
|
1923
1737
|
|
|
1924
|
-
serverName =
|
|
1738
|
+
serverName = SniHandler.extractSNIWithResumptionSupport(chunk, this.settings.enableTlsDebugLogging) || '';
|
|
1925
1739
|
}
|
|
1926
1740
|
|
|
1927
1741
|
// Lock the connection to the negotiated SNI.
|