@push.rocks/smartproxy 3.41.7 → 4.0.0

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.
Files changed (44) hide show
  1. package/dist_ts/00_commitinfo_data.js +2 -2
  2. package/dist_ts/classes.portproxy.js +83 -69
  3. package/dist_ts/classes.pp.acmemanager.d.ts +34 -0
  4. package/dist_ts/classes.pp.acmemanager.js +123 -0
  5. package/dist_ts/classes.pp.connectionhandler.d.ts +39 -0
  6. package/dist_ts/classes.pp.connectionhandler.js +693 -0
  7. package/dist_ts/classes.pp.connectionmanager.d.ts +78 -0
  8. package/dist_ts/classes.pp.connectionmanager.js +378 -0
  9. package/dist_ts/classes.pp.domainconfigmanager.d.ts +55 -0
  10. package/dist_ts/classes.pp.domainconfigmanager.js +103 -0
  11. package/dist_ts/classes.pp.interfaces.d.ts +109 -0
  12. package/dist_ts/classes.pp.interfaces.js +2 -0
  13. package/dist_ts/classes.pp.networkproxybridge.d.ts +43 -0
  14. package/dist_ts/classes.pp.networkproxybridge.js +211 -0
  15. package/dist_ts/classes.pp.portproxy.d.ts +48 -0
  16. package/dist_ts/classes.pp.portproxy.js +268 -0
  17. package/dist_ts/classes.pp.portrangemanager.d.ts +56 -0
  18. package/dist_ts/classes.pp.portrangemanager.js +179 -0
  19. package/dist_ts/classes.pp.securitymanager.d.ts +47 -0
  20. package/dist_ts/classes.pp.securitymanager.js +126 -0
  21. package/dist_ts/classes.pp.snihandler.d.ts +160 -0
  22. package/dist_ts/classes.pp.snihandler.js +1073 -0
  23. package/dist_ts/classes.pp.timeoutmanager.d.ts +47 -0
  24. package/dist_ts/classes.pp.timeoutmanager.js +154 -0
  25. package/dist_ts/classes.pp.tlsmanager.d.ts +57 -0
  26. package/dist_ts/classes.pp.tlsmanager.js +132 -0
  27. package/dist_ts/index.d.ts +2 -2
  28. package/dist_ts/index.js +3 -3
  29. package/package.json +1 -1
  30. package/ts/00_commitinfo_data.ts +1 -1
  31. package/ts/classes.pp.acmemanager.ts +149 -0
  32. package/ts/classes.pp.connectionhandler.ts +982 -0
  33. package/ts/classes.pp.connectionmanager.ts +446 -0
  34. package/ts/classes.pp.domainconfigmanager.ts +123 -0
  35. package/ts/classes.pp.interfaces.ts +136 -0
  36. package/ts/classes.pp.networkproxybridge.ts +258 -0
  37. package/ts/classes.pp.portproxy.ts +344 -0
  38. package/ts/classes.pp.portrangemanager.ts +214 -0
  39. package/ts/classes.pp.securitymanager.ts +147 -0
  40. package/ts/{classes.snihandler.ts → classes.pp.snihandler.ts} +2 -169
  41. package/ts/classes.pp.timeoutmanager.ts +190 -0
  42. package/ts/classes.pp.tlsmanager.ts +206 -0
  43. package/ts/index.ts +2 -2
  44. package/ts/classes.portproxy.ts +0 -2496
@@ -0,0 +1,1073 @@
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
+ * with support for fragmented packets, TLS 1.3 resumption, Chrome-specific
6
+ * connection behaviors, and tab hibernation/reactivation scenarios.
7
+ */
8
+ export class SniHandler {
9
+ // TLS record types and constants
10
+ static { this.TLS_HANDSHAKE_RECORD_TYPE = 22; }
11
+ static { this.TLS_APPLICATION_DATA_TYPE = 23; } // TLS Application Data record type
12
+ static { this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE = 1; }
13
+ static { this.TLS_SNI_EXTENSION_TYPE = 0x0000; }
14
+ static { this.TLS_SESSION_TICKET_EXTENSION_TYPE = 0x0023; }
15
+ static { this.TLS_SNI_HOST_NAME_TYPE = 0; }
16
+ static { this.TLS_PSK_EXTENSION_TYPE = 0x0029; } // Pre-Shared Key extension type for TLS 1.3
17
+ static { this.TLS_PSK_KE_MODES_EXTENSION_TYPE = 0x002d; } // PSK Key Exchange Modes
18
+ static { this.TLS_EARLY_DATA_EXTENSION_TYPE = 0x002a; } // Early Data (0-RTT) extension
19
+ // Buffer for handling fragmented ClientHello messages
20
+ static { this.fragmentedBuffers = new Map(); }
21
+ static { this.fragmentTimeout = 1000; } // ms to wait for fragments before cleanup
22
+ /**
23
+ * Extract the client random value from a ClientHello message
24
+ *
25
+ * @param buffer - The buffer containing the ClientHello
26
+ * @returns The 32-byte client random or undefined if extraction fails
27
+ */
28
+ static extractClientRandom(buffer) {
29
+ try {
30
+ if (!this.isClientHello(buffer) || buffer.length < 46) {
31
+ return undefined;
32
+ }
33
+ // In a ClientHello message, the client random starts at position 11
34
+ // after record header (5 bytes), handshake type (1 byte),
35
+ // handshake length (3 bytes), and client version (2 bytes)
36
+ return buffer.slice(11, 11 + 32);
37
+ }
38
+ catch (error) {
39
+ return undefined;
40
+ }
41
+ }
42
+ /**
43
+ * Checks if a buffer contains a TLS handshake message (record type 22)
44
+ * @param buffer - The buffer to check
45
+ * @returns true if the buffer starts with a TLS handshake record type
46
+ */
47
+ static isTlsHandshake(buffer) {
48
+ return buffer.length > 0 && buffer[0] === this.TLS_HANDSHAKE_RECORD_TYPE;
49
+ }
50
+ /**
51
+ * Checks if a buffer contains TLS application data (record type 23)
52
+ * @param buffer - The buffer to check
53
+ * @returns true if the buffer starts with a TLS application data record type
54
+ */
55
+ static isTlsApplicationData(buffer) {
56
+ return buffer.length > 0 && buffer[0] === this.TLS_APPLICATION_DATA_TYPE;
57
+ }
58
+ /**
59
+ * Creates a connection ID based on source/destination information
60
+ * Used to track fragmented ClientHello messages across multiple packets
61
+ *
62
+ * @param connectionInfo - Object containing connection identifiers (IP/port)
63
+ * @returns A string ID for the connection
64
+ */
65
+ static createConnectionId(connectionInfo) {
66
+ const { sourceIp, sourcePort, destIp, destPort } = connectionInfo;
67
+ return `${sourceIp}:${sourcePort}-${destIp}:${destPort}`;
68
+ }
69
+ /**
70
+ * Handles potential fragmented ClientHello messages by buffering and reassembling
71
+ * TLS record fragments that might span multiple TCP packets.
72
+ *
73
+ * @param buffer - The current buffer fragment
74
+ * @param connectionId - Unique identifier for the connection
75
+ * @param enableLogging - Whether to enable logging
76
+ * @returns A complete buffer if reassembly is successful, or undefined if more fragments are needed
77
+ */
78
+ static handleFragmentedClientHello(buffer, connectionId, enableLogging = false) {
79
+ const log = (message) => {
80
+ if (enableLogging) {
81
+ console.log(`[SNI Fragment] ${message}`);
82
+ }
83
+ };
84
+ // Check if we've seen this connection before
85
+ if (!this.fragmentedBuffers.has(connectionId)) {
86
+ // New connection, start with this buffer
87
+ this.fragmentedBuffers.set(connectionId, buffer);
88
+ // Set timeout to clean up if we don't get a complete ClientHello
89
+ setTimeout(() => {
90
+ if (this.fragmentedBuffers.has(connectionId)) {
91
+ this.fragmentedBuffers.delete(connectionId);
92
+ log(`Connection ${connectionId} timed out waiting for complete ClientHello`);
93
+ }
94
+ }, this.fragmentTimeout);
95
+ // Evaluate if this buffer already contains a complete ClientHello
96
+ try {
97
+ if (buffer.length >= 5) {
98
+ // Get the record length from TLS header
99
+ const recordLength = (buffer[3] << 8) + buffer[4] + 5; // +5 for the TLS record header itself
100
+ log(`Initial buffer size: ${buffer.length}, expected record length: ${recordLength}`);
101
+ // Check if this buffer already contains a complete TLS record
102
+ if (buffer.length >= recordLength) {
103
+ log(`Initial buffer contains complete ClientHello, length: ${buffer.length}`);
104
+ return buffer;
105
+ }
106
+ }
107
+ else {
108
+ log(`Initial buffer too small (${buffer.length} bytes), needs at least 5 bytes for TLS header`);
109
+ }
110
+ }
111
+ catch (e) {
112
+ log(`Error checking initial buffer completeness: ${e}`);
113
+ }
114
+ log(`Started buffering connection ${connectionId}, initial size: ${buffer.length}`);
115
+ return undefined; // Need more fragments
116
+ }
117
+ else {
118
+ // Existing connection, append this buffer
119
+ const existingBuffer = this.fragmentedBuffers.get(connectionId);
120
+ const newBuffer = Buffer.concat([existingBuffer, buffer]);
121
+ this.fragmentedBuffers.set(connectionId, newBuffer);
122
+ log(`Appended to buffer for ${connectionId}, new size: ${newBuffer.length}`);
123
+ // Check if we now have a complete ClientHello
124
+ try {
125
+ if (newBuffer.length >= 5) {
126
+ // Get the record length from TLS header
127
+ const recordLength = (newBuffer[3] << 8) + newBuffer[4] + 5; // +5 for the TLS record header itself
128
+ log(`Reassembled buffer size: ${newBuffer.length}, expected record length: ${recordLength}`);
129
+ // Check if we have a complete TLS record now
130
+ if (newBuffer.length >= recordLength) {
131
+ log(`Assembled complete ClientHello, length: ${newBuffer.length}, needed: ${recordLength}`);
132
+ // Extract the complete TLS record (might be followed by more data)
133
+ const completeRecord = newBuffer.slice(0, recordLength);
134
+ // Check if this record is indeed a ClientHello (type 1) at position 5
135
+ if (completeRecord.length > 5 &&
136
+ completeRecord[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
137
+ log(`Verified record is a ClientHello handshake message`);
138
+ // Complete message received, remove from tracking
139
+ this.fragmentedBuffers.delete(connectionId);
140
+ return completeRecord;
141
+ }
142
+ else {
143
+ log(`Record is complete but not a ClientHello handshake, continuing to buffer`);
144
+ // This might be another TLS record type preceding the ClientHello
145
+ // Try checking for a ClientHello starting at the end of this record
146
+ if (newBuffer.length > recordLength + 5) {
147
+ const nextRecordType = newBuffer[recordLength];
148
+ log(`Next record type: ${nextRecordType} (looking for ${this.TLS_HANDSHAKE_RECORD_TYPE})`);
149
+ if (nextRecordType === this.TLS_HANDSHAKE_RECORD_TYPE) {
150
+ const handshakeType = newBuffer[recordLength + 5];
151
+ log(`Next handshake type: ${handshakeType} (looking for ${this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE})`);
152
+ if (handshakeType === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
153
+ // Found a ClientHello in the next record, return the entire buffer
154
+ log(`Found ClientHello in subsequent record, returning full buffer`);
155
+ this.fragmentedBuffers.delete(connectionId);
156
+ return newBuffer;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
164
+ catch (e) {
165
+ log(`Error checking reassembled buffer completeness: ${e}`);
166
+ }
167
+ return undefined; // Still need more fragments
168
+ }
169
+ }
170
+ /**
171
+ * Checks if a buffer contains a TLS ClientHello message
172
+ * @param buffer - The buffer to check
173
+ * @returns true if the buffer appears to be a ClientHello message
174
+ */
175
+ static isClientHello(buffer) {
176
+ // Minimum ClientHello size (TLS record header + handshake header)
177
+ if (buffer.length < 9) {
178
+ return false;
179
+ }
180
+ // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE)
181
+ if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
182
+ return false;
183
+ }
184
+ // Skip version and length in TLS record header (5 bytes total)
185
+ // Check handshake type at byte 5 (must be CLIENT_HELLO)
186
+ return buffer[5] === this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE;
187
+ }
188
+ /**
189
+ * Checks if a ClientHello message contains session resumption indicators
190
+ * such as session tickets or PSK (Pre-Shared Key) extensions.
191
+ *
192
+ * @param buffer - The buffer containing a ClientHello message
193
+ * @param enableLogging - Whether to enable logging
194
+ * @returns Object containing details about session resumption and SNI presence
195
+ */
196
+ static hasSessionResumption(buffer, enableLogging = false) {
197
+ const log = (message) => {
198
+ if (enableLogging) {
199
+ console.log(`[Session Resumption] ${message}`);
200
+ }
201
+ };
202
+ if (!this.isClientHello(buffer)) {
203
+ return { isResumption: false, hasSNI: false };
204
+ }
205
+ try {
206
+ // Check for session ID presence first
207
+ let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
208
+ pos += 32; // Skip client random
209
+ if (pos + 1 > buffer.length)
210
+ return { isResumption: false, hasSNI: false };
211
+ const sessionIdLength = buffer[pos];
212
+ let hasNonEmptySessionId = sessionIdLength > 0;
213
+ if (hasNonEmptySessionId) {
214
+ log(`Detected non-empty session ID (length: ${sessionIdLength})`);
215
+ }
216
+ // Continue to check for extensions
217
+ pos += 1 + sessionIdLength;
218
+ // Skip cipher suites
219
+ if (pos + 2 > buffer.length)
220
+ return { isResumption: false, hasSNI: false };
221
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
222
+ pos += 2 + cipherSuitesLength;
223
+ // Skip compression methods
224
+ if (pos + 1 > buffer.length)
225
+ return { isResumption: false, hasSNI: false };
226
+ const compressionMethodsLength = buffer[pos];
227
+ pos += 1 + compressionMethodsLength;
228
+ // Check for extensions
229
+ if (pos + 2 > buffer.length)
230
+ return { isResumption: false, hasSNI: false };
231
+ // Look for session resumption extensions
232
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
233
+ pos += 2;
234
+ // Extensions end position
235
+ const extensionsEnd = pos + extensionsLength;
236
+ if (extensionsEnd > buffer.length)
237
+ return { isResumption: false, hasSNI: false };
238
+ // Track resumption indicators
239
+ let hasSessionTicket = false;
240
+ let hasPSK = false;
241
+ let hasEarlyData = false;
242
+ // Iterate through extensions
243
+ while (pos + 4 <= extensionsEnd) {
244
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
245
+ pos += 2;
246
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
247
+ pos += 2;
248
+ if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
249
+ log('Found session ticket extension');
250
+ hasSessionTicket = true;
251
+ // Check if session ticket has non-zero length (active ticket)
252
+ if (extensionLength > 0) {
253
+ log(`Session ticket has length ${extensionLength} - active ticket present`);
254
+ }
255
+ }
256
+ else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
257
+ log('Found PSK extension (TLS 1.3 resumption mechanism)');
258
+ hasPSK = true;
259
+ }
260
+ else if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
261
+ log('Found Early Data extension (TLS 1.3 0-RTT)');
262
+ hasEarlyData = true;
263
+ }
264
+ // Skip extension data
265
+ pos += extensionLength;
266
+ }
267
+ // Check if SNI is included
268
+ let hasSNI = false;
269
+ // Reset position and scan again for SNI extension
270
+ pos = 5 + 1 + 3 + 2; // Reset to after handshake type, length and client version
271
+ pos += 32; // Skip client random
272
+ if (pos + 1 <= buffer.length) {
273
+ const sessionIdLength = buffer[pos];
274
+ pos += 1 + sessionIdLength;
275
+ // Skip cipher suites
276
+ if (pos + 2 <= buffer.length) {
277
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
278
+ pos += 2 + cipherSuitesLength;
279
+ // Skip compression methods
280
+ if (pos + 1 <= buffer.length) {
281
+ const compressionMethodsLength = buffer[pos];
282
+ pos += 1 + compressionMethodsLength;
283
+ // Check for extensions
284
+ if (pos + 2 <= buffer.length) {
285
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
286
+ pos += 2;
287
+ // Extensions end position
288
+ const extensionsEnd = pos + extensionsLength;
289
+ if (extensionsEnd <= buffer.length) {
290
+ // Scan for SNI extension
291
+ while (pos + 4 <= extensionsEnd) {
292
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
293
+ pos += 2;
294
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
295
+ pos += 2;
296
+ if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
297
+ // Check that the SNI extension actually has content
298
+ if (extensionLength > 0) {
299
+ hasSNI = true;
300
+ // Try to extract the actual SNI value for logging
301
+ try {
302
+ // Skip to server_name_list_length (2 bytes)
303
+ const tempPos = pos;
304
+ if (tempPos + 2 <= extensionsEnd) {
305
+ const nameListLength = (buffer[tempPos] << 8) + buffer[tempPos + 1];
306
+ // Skip server_name_list_length (2 bytes)
307
+ if (tempPos + 2 + 1 <= extensionsEnd) {
308
+ // Check name_type (should be 0 for hostname)
309
+ if (buffer[tempPos + 2] === 0) {
310
+ // Skip name_type (1 byte)
311
+ if (tempPos + 3 + 2 <= extensionsEnd) {
312
+ // Get name_length (2 bytes)
313
+ const nameLength = (buffer[tempPos + 3] << 8) + buffer[tempPos + 4];
314
+ // Extract the hostname
315
+ if (tempPos + 5 + nameLength <= extensionsEnd) {
316
+ const hostname = buffer
317
+ .slice(tempPos + 5, tempPos + 5 + nameLength)
318
+ .toString('utf8');
319
+ log(`Found SNI extension with server_name: ${hostname}`);
320
+ }
321
+ }
322
+ }
323
+ }
324
+ }
325
+ }
326
+ catch (e) {
327
+ log(`Error extracting SNI value: ${e}`);
328
+ log('Found SNI extension with length: ' + extensionLength);
329
+ }
330
+ }
331
+ else {
332
+ log('Found empty SNI extension, treating as no SNI');
333
+ }
334
+ break;
335
+ }
336
+ // Skip extension data
337
+ pos += extensionLength;
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+ // Consider it a resumption if any resumption mechanism is present
345
+ const isResumption = hasSessionTicket || hasPSK || hasEarlyData || (hasNonEmptySessionId && !hasPSK); // Legacy resumption
346
+ if (isResumption) {
347
+ log('Session resumption detected: ' +
348
+ (hasSessionTicket ? 'session ticket, ' : '') +
349
+ (hasPSK ? 'PSK, ' : '') +
350
+ (hasEarlyData ? 'early data, ' : '') +
351
+ (hasNonEmptySessionId ? 'session ID' : '') +
352
+ (hasSNI ? ', with SNI' : ', without SNI'));
353
+ }
354
+ // Return an object with both flags
355
+ // For clarity: connections should be blocked if they have session resumption without SNI
356
+ if (isResumption) {
357
+ log(`Resumption summary - hasSNI: ${hasSNI ? 'yes' : 'no'}, resumption type: ${hasSessionTicket ? 'session ticket, ' : ''}${hasPSK ? 'PSK, ' : ''}${hasEarlyData ? 'early data, ' : ''}${hasNonEmptySessionId ? 'session ID' : ''}`);
358
+ }
359
+ return {
360
+ isResumption,
361
+ hasSNI,
362
+ };
363
+ }
364
+ catch (error) {
365
+ log(`Error checking for session resumption: ${error}`);
366
+ return { isResumption: false, hasSNI: false };
367
+ }
368
+ }
369
+ /**
370
+ * Detects characteristics of a tab reactivation TLS handshake
371
+ * These often have specific patterns in Chrome and other browsers
372
+ *
373
+ * @param buffer - The buffer containing a ClientHello message
374
+ * @param enableLogging - Whether to enable logging
375
+ * @returns true if this appears to be a tab reactivation handshake
376
+ */
377
+ static isTabReactivationHandshake(buffer, enableLogging = false) {
378
+ const log = (message) => {
379
+ if (enableLogging) {
380
+ console.log(`[Tab Reactivation] ${message}`);
381
+ }
382
+ };
383
+ if (!this.isClientHello(buffer)) {
384
+ return false;
385
+ }
386
+ try {
387
+ // Check for session ID presence (tab reactivation often has a session ID)
388
+ let pos = 5 + 1 + 3 + 2; // Position after handshake type, length and client version
389
+ pos += 32; // Skip client random
390
+ if (pos + 1 > buffer.length)
391
+ return false;
392
+ const sessionIdLength = buffer[pos];
393
+ // Non-empty session ID is a good indicator
394
+ if (sessionIdLength > 0) {
395
+ log(`Detected non-empty session ID (length: ${sessionIdLength})`);
396
+ // Skip to extensions
397
+ pos += 1 + sessionIdLength;
398
+ // Skip cipher suites
399
+ if (pos + 2 > buffer.length)
400
+ return false;
401
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
402
+ pos += 2 + cipherSuitesLength;
403
+ // Skip compression methods
404
+ if (pos + 1 > buffer.length)
405
+ return false;
406
+ const compressionMethodsLength = buffer[pos];
407
+ pos += 1 + compressionMethodsLength;
408
+ // Check for extensions
409
+ if (pos + 2 > buffer.length)
410
+ return false;
411
+ // Look for specific extensions that indicate tab reactivation
412
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
413
+ pos += 2;
414
+ // Extensions end position
415
+ const extensionsEnd = pos + extensionsLength;
416
+ if (extensionsEnd > buffer.length)
417
+ return false;
418
+ // Tab reactivation often has session tickets but no SNI
419
+ let hasSessionTicket = false;
420
+ let hasSNI = false;
421
+ let hasPSK = false;
422
+ // Iterate through extensions
423
+ while (pos + 4 <= extensionsEnd) {
424
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
425
+ pos += 2;
426
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
427
+ pos += 2;
428
+ if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
429
+ hasSessionTicket = true;
430
+ }
431
+ else if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
432
+ hasSNI = true;
433
+ }
434
+ else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
435
+ hasPSK = true;
436
+ }
437
+ // Skip extension data
438
+ pos += extensionLength;
439
+ }
440
+ // Pattern for tab reactivation: session identifier + (ticket or PSK) but no SNI
441
+ if ((hasSessionTicket || hasPSK) && !hasSNI) {
442
+ log('Detected tab reactivation pattern: session resumption without SNI');
443
+ return true;
444
+ }
445
+ }
446
+ }
447
+ catch (error) {
448
+ log(`Error checking for tab reactivation: ${error}`);
449
+ }
450
+ return false;
451
+ }
452
+ /**
453
+ * Extracts the SNI (Server Name Indication) from a TLS ClientHello message.
454
+ * Implements robust parsing with support for session resumption edge cases.
455
+ *
456
+ * @param buffer - The buffer containing the TLS ClientHello message
457
+ * @param enableLogging - Whether to enable detailed debug logging
458
+ * @returns The extracted server name or undefined if not found
459
+ */
460
+ static extractSNI(buffer, enableLogging = false) {
461
+ // Logging helper
462
+ const log = (message) => {
463
+ if (enableLogging) {
464
+ console.log(`[SNI Extraction] ${message}`);
465
+ }
466
+ };
467
+ try {
468
+ // Buffer must be at least 5 bytes (TLS record header)
469
+ if (buffer.length < 5) {
470
+ log('Buffer too small for TLS record header');
471
+ return undefined;
472
+ }
473
+ // Check record type (must be TLS_HANDSHAKE_RECORD_TYPE = 22)
474
+ if (buffer[0] !== this.TLS_HANDSHAKE_RECORD_TYPE) {
475
+ log(`Not a TLS handshake record: ${buffer[0]}`);
476
+ return undefined;
477
+ }
478
+ // Check TLS version
479
+ const majorVersion = buffer[1];
480
+ const minorVersion = buffer[2];
481
+ log(`TLS version: ${majorVersion}.${minorVersion}`);
482
+ // Parse record length (bytes 3-4, big-endian)
483
+ const recordLength = (buffer[3] << 8) + buffer[4];
484
+ log(`Record length: ${recordLength}`);
485
+ // Validate record length against buffer size
486
+ if (buffer.length < recordLength + 5) {
487
+ log('Buffer smaller than expected record length');
488
+ return undefined;
489
+ }
490
+ // Start of handshake message in the buffer
491
+ let pos = 5;
492
+ // Check handshake type (must be CLIENT_HELLO = 1)
493
+ if (buffer[pos] !== this.TLS_CLIENT_HELLO_HANDSHAKE_TYPE) {
494
+ log(`Not a ClientHello message: ${buffer[pos]}`);
495
+ return undefined;
496
+ }
497
+ // Skip handshake type (1 byte)
498
+ pos += 1;
499
+ // Parse handshake length (3 bytes, big-endian)
500
+ const handshakeLength = (buffer[pos] << 16) + (buffer[pos + 1] << 8) + buffer[pos + 2];
501
+ log(`Handshake length: ${handshakeLength}`);
502
+ // Skip handshake length (3 bytes)
503
+ pos += 3;
504
+ // Check client version (2 bytes)
505
+ const clientMajorVersion = buffer[pos];
506
+ const clientMinorVersion = buffer[pos + 1];
507
+ log(`Client version: ${clientMajorVersion}.${clientMinorVersion}`);
508
+ // Skip client version (2 bytes)
509
+ pos += 2;
510
+ // Skip client random (32 bytes)
511
+ pos += 32;
512
+ // Parse session ID
513
+ if (pos + 1 > buffer.length) {
514
+ log('Buffer too small for session ID length');
515
+ return undefined;
516
+ }
517
+ const sessionIdLength = buffer[pos];
518
+ log(`Session ID length: ${sessionIdLength}`);
519
+ // Skip session ID length (1 byte) and session ID
520
+ pos += 1 + sessionIdLength;
521
+ // Check if we have enough bytes left
522
+ if (pos + 2 > buffer.length) {
523
+ log('Buffer too small for cipher suites length');
524
+ return undefined;
525
+ }
526
+ // Parse cipher suites length (2 bytes, big-endian)
527
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
528
+ log(`Cipher suites length: ${cipherSuitesLength}`);
529
+ // Skip cipher suites length (2 bytes) and cipher suites
530
+ pos += 2 + cipherSuitesLength;
531
+ // Check if we have enough bytes left
532
+ if (pos + 1 > buffer.length) {
533
+ log('Buffer too small for compression methods length');
534
+ return undefined;
535
+ }
536
+ // Parse compression methods length (1 byte)
537
+ const compressionMethodsLength = buffer[pos];
538
+ log(`Compression methods length: ${compressionMethodsLength}`);
539
+ // Skip compression methods length (1 byte) and compression methods
540
+ pos += 1 + compressionMethodsLength;
541
+ // Check if we have enough bytes for extensions length
542
+ if (pos + 2 > buffer.length) {
543
+ log('No extensions present or buffer too small');
544
+ return undefined;
545
+ }
546
+ // Parse extensions length (2 bytes, big-endian)
547
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
548
+ log(`Extensions length: ${extensionsLength}`);
549
+ // Skip extensions length (2 bytes)
550
+ pos += 2;
551
+ // Extensions end position
552
+ const extensionsEnd = pos + extensionsLength;
553
+ // Check if extensions length is valid
554
+ if (extensionsEnd > buffer.length) {
555
+ log('Extensions length exceeds buffer size');
556
+ return undefined;
557
+ }
558
+ // Track if we found session tickets (for improved resumption handling)
559
+ let hasSessionTicket = false;
560
+ let hasPskExtension = false;
561
+ // Iterate through extensions
562
+ while (pos + 4 <= extensionsEnd) {
563
+ // Parse extension type (2 bytes, big-endian)
564
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
565
+ log(`Extension type: 0x${extensionType.toString(16).padStart(4, '0')}`);
566
+ // Skip extension type (2 bytes)
567
+ pos += 2;
568
+ // Parse extension length (2 bytes, big-endian)
569
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
570
+ log(`Extension length: ${extensionLength}`);
571
+ // Skip extension length (2 bytes)
572
+ pos += 2;
573
+ // Check if this is the SNI extension
574
+ if (extensionType === this.TLS_SNI_EXTENSION_TYPE) {
575
+ log('Found SNI extension');
576
+ // Ensure we have enough bytes for the server name list
577
+ if (pos + 2 > extensionsEnd) {
578
+ log('Extension too small for server name list length');
579
+ pos += extensionLength; // Skip this extension
580
+ continue;
581
+ }
582
+ // Parse server name list length (2 bytes, big-endian)
583
+ const serverNameListLength = (buffer[pos] << 8) + buffer[pos + 1];
584
+ log(`Server name list length: ${serverNameListLength}`);
585
+ // Skip server name list length (2 bytes)
586
+ pos += 2;
587
+ // Ensure server name list length is valid
588
+ if (pos + serverNameListLength > extensionsEnd) {
589
+ log('Server name list length exceeds extension size');
590
+ break; // Exit the loop, extension parsing is broken
591
+ }
592
+ // End position of server name list
593
+ const serverNameListEnd = pos + serverNameListLength;
594
+ // Iterate through server names
595
+ while (pos + 3 <= serverNameListEnd) {
596
+ // Check name type (must be HOST_NAME_TYPE = 0 for hostname)
597
+ const nameType = buffer[pos];
598
+ log(`Name type: ${nameType}`);
599
+ if (nameType !== this.TLS_SNI_HOST_NAME_TYPE) {
600
+ log(`Unsupported name type: ${nameType}`);
601
+ pos += 1; // Skip name type (1 byte)
602
+ // Skip name length (2 bytes) and name data
603
+ if (pos + 2 <= serverNameListEnd) {
604
+ const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
605
+ pos += 2 + nameLength;
606
+ }
607
+ else {
608
+ log('Invalid server name entry');
609
+ break;
610
+ }
611
+ continue;
612
+ }
613
+ // Skip name type (1 byte)
614
+ pos += 1;
615
+ // Ensure we have enough bytes for name length
616
+ if (pos + 2 > serverNameListEnd) {
617
+ log('Server name entry too small for name length');
618
+ break;
619
+ }
620
+ // Parse name length (2 bytes, big-endian)
621
+ const nameLength = (buffer[pos] << 8) + buffer[pos + 1];
622
+ log(`Name length: ${nameLength}`);
623
+ // Skip name length (2 bytes)
624
+ pos += 2;
625
+ // Ensure we have enough bytes for the name
626
+ if (pos + nameLength > serverNameListEnd) {
627
+ log('Name length exceeds server name list size');
628
+ break;
629
+ }
630
+ // Extract server name (hostname)
631
+ const serverName = buffer.slice(pos, pos + nameLength).toString('utf8');
632
+ log(`Extracted server name: ${serverName}`);
633
+ return serverName;
634
+ }
635
+ }
636
+ else if (extensionType === this.TLS_SESSION_TICKET_EXTENSION_TYPE) {
637
+ // If we encounter a session ticket extension, mark it for later
638
+ log('Found session ticket extension');
639
+ hasSessionTicket = true;
640
+ pos += extensionLength; // Skip this extension
641
+ }
642
+ else if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
643
+ // TLS 1.3 PSK extension - mark for resumption support
644
+ log('Found PSK extension (TLS 1.3 resumption indicator)');
645
+ hasPskExtension = true;
646
+ // We'll skip the extension here and process it separately if needed
647
+ pos += extensionLength;
648
+ }
649
+ else {
650
+ // Skip this extension
651
+ pos += extensionLength;
652
+ }
653
+ }
654
+ // Log if we found session resumption indicators but no SNI
655
+ if (hasSessionTicket || hasPskExtension) {
656
+ log('Session resumption indicators present but no SNI found');
657
+ }
658
+ log('No SNI extension found in ClientHello');
659
+ return undefined;
660
+ }
661
+ catch (error) {
662
+ log(`Error parsing SNI: ${error instanceof Error ? error.message : String(error)}`);
663
+ return undefined;
664
+ }
665
+ }
666
+ /**
667
+ * Attempts to extract SNI from the PSK extension in a TLS 1.3 ClientHello.
668
+ *
669
+ * In TLS 1.3, when a client attempts to resume a session, it may include
670
+ * the server name in the PSK identity hint rather than in the SNI extension.
671
+ *
672
+ * @param buffer - The buffer containing the TLS ClientHello message
673
+ * @param enableLogging - Whether to enable detailed debug logging
674
+ * @returns The extracted server name or undefined if not found
675
+ */
676
+ static extractSNIFromPSKExtension(buffer, enableLogging = false) {
677
+ const log = (message) => {
678
+ if (enableLogging) {
679
+ console.log(`[PSK-SNI Extraction] ${message}`);
680
+ }
681
+ };
682
+ try {
683
+ // Ensure this is a ClientHello
684
+ if (!this.isClientHello(buffer)) {
685
+ log('Not a ClientHello message');
686
+ return undefined;
687
+ }
688
+ // Find the start position of extensions
689
+ let pos = 5; // Start after record header
690
+ // Skip handshake type (1 byte)
691
+ pos += 1;
692
+ // Skip handshake length (3 bytes)
693
+ pos += 3;
694
+ // Skip client version (2 bytes)
695
+ pos += 2;
696
+ // Skip client random (32 bytes)
697
+ pos += 32;
698
+ // Skip session ID
699
+ if (pos + 1 > buffer.length)
700
+ return undefined;
701
+ const sessionIdLength = buffer[pos];
702
+ pos += 1 + sessionIdLength;
703
+ // Skip cipher suites
704
+ if (pos + 2 > buffer.length)
705
+ return undefined;
706
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
707
+ pos += 2 + cipherSuitesLength;
708
+ // Skip compression methods
709
+ if (pos + 1 > buffer.length)
710
+ return undefined;
711
+ const compressionMethodsLength = buffer[pos];
712
+ pos += 1 + compressionMethodsLength;
713
+ // Check if we have extensions
714
+ if (pos + 2 > buffer.length) {
715
+ log('No extensions present');
716
+ return undefined;
717
+ }
718
+ // Get extensions length
719
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
720
+ pos += 2;
721
+ // Extensions end position
722
+ const extensionsEnd = pos + extensionsLength;
723
+ if (extensionsEnd > buffer.length)
724
+ return undefined;
725
+ // Look for PSK extension
726
+ while (pos + 4 <= extensionsEnd) {
727
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
728
+ pos += 2;
729
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
730
+ pos += 2;
731
+ if (extensionType === this.TLS_PSK_EXTENSION_TYPE) {
732
+ log('Found PSK extension');
733
+ // PSK extension structure:
734
+ // 2 bytes: identities list length
735
+ if (pos + 2 > extensionsEnd)
736
+ break;
737
+ const identitiesLength = (buffer[pos] << 8) + buffer[pos + 1];
738
+ pos += 2;
739
+ // End of identities list
740
+ const identitiesEnd = pos + identitiesLength;
741
+ if (identitiesEnd > extensionsEnd)
742
+ break;
743
+ // Process each PSK identity
744
+ while (pos + 2 <= identitiesEnd) {
745
+ // Identity length (2 bytes)
746
+ if (pos + 2 > identitiesEnd)
747
+ break;
748
+ const identityLength = (buffer[pos] << 8) + buffer[pos + 1];
749
+ pos += 2;
750
+ if (pos + identityLength > identitiesEnd)
751
+ break;
752
+ // Try to extract hostname from identity
753
+ // Chrome often embeds the hostname in the PSK identity
754
+ // This is a heuristic as there's no standard format
755
+ if (identityLength > 0) {
756
+ const identity = buffer.slice(pos, pos + identityLength);
757
+ // Skip identity bytes
758
+ pos += identityLength;
759
+ // Skip obfuscated ticket age (4 bytes)
760
+ if (pos + 4 <= identitiesEnd) {
761
+ pos += 4;
762
+ }
763
+ else {
764
+ break;
765
+ }
766
+ // Try to parse the identity as UTF-8
767
+ try {
768
+ const identityStr = identity.toString('utf8');
769
+ log(`PSK identity: ${identityStr}`);
770
+ // Check if the identity contains hostname hints
771
+ // Chrome often embeds the hostname in a known format
772
+ // Try to extract using common patterns
773
+ // Pattern 1: Look for domain name pattern
774
+ const domainPattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/i;
775
+ const domainMatch = identityStr.match(domainPattern);
776
+ if (domainMatch && domainMatch[0]) {
777
+ log(`Found domain in PSK identity: ${domainMatch[0]}`);
778
+ return domainMatch[0];
779
+ }
780
+ // Pattern 2: Chrome sometimes uses a specific format with delimiters
781
+ // This is a heuristic approach since the format isn't standardized
782
+ const parts = identityStr.split('|');
783
+ if (parts.length > 1) {
784
+ for (const part of parts) {
785
+ if (part.includes('.') && !part.includes('/')) {
786
+ const possibleDomain = part.trim();
787
+ if (/^[a-z0-9.-]+$/i.test(possibleDomain)) {
788
+ log(`Found possible domain in PSK delimiter format: ${possibleDomain}`);
789
+ return possibleDomain;
790
+ }
791
+ }
792
+ }
793
+ }
794
+ }
795
+ catch (e) {
796
+ log('Failed to parse PSK identity as UTF-8');
797
+ }
798
+ }
799
+ }
800
+ }
801
+ else {
802
+ // Skip this extension
803
+ pos += extensionLength;
804
+ }
805
+ }
806
+ log('No hostname found in PSK extension');
807
+ return undefined;
808
+ }
809
+ catch (error) {
810
+ log(`Error parsing PSK: ${error instanceof Error ? error.message : String(error)}`);
811
+ return undefined;
812
+ }
813
+ }
814
+ /**
815
+ * Checks if the buffer contains TLS 1.3 early data (0-RTT)
816
+ * @param buffer - The buffer to check
817
+ * @param enableLogging - Whether to enable logging
818
+ * @returns true if early data is detected
819
+ */
820
+ static hasEarlyData(buffer, enableLogging = false) {
821
+ const log = (message) => {
822
+ if (enableLogging) {
823
+ console.log(`[Early Data] ${message}`);
824
+ }
825
+ };
826
+ try {
827
+ // Check if this is a valid ClientHello first
828
+ if (!this.isClientHello(buffer)) {
829
+ return false;
830
+ }
831
+ // Find the extensions section
832
+ let pos = 5; // Start after record header
833
+ // Skip handshake type (1 byte)
834
+ pos += 1;
835
+ // Skip handshake length (3 bytes)
836
+ pos += 3;
837
+ // Skip client version (2 bytes)
838
+ pos += 2;
839
+ // Skip client random (32 bytes)
840
+ pos += 32;
841
+ // Skip session ID
842
+ if (pos + 1 > buffer.length)
843
+ return false;
844
+ const sessionIdLength = buffer[pos];
845
+ pos += 1 + sessionIdLength;
846
+ // Skip cipher suites
847
+ if (pos + 2 > buffer.length)
848
+ return false;
849
+ const cipherSuitesLength = (buffer[pos] << 8) + buffer[pos + 1];
850
+ pos += 2 + cipherSuitesLength;
851
+ // Skip compression methods
852
+ if (pos + 1 > buffer.length)
853
+ return false;
854
+ const compressionMethodsLength = buffer[pos];
855
+ pos += 1 + compressionMethodsLength;
856
+ // Check if we have extensions
857
+ if (pos + 2 > buffer.length)
858
+ return false;
859
+ // Get extensions length
860
+ const extensionsLength = (buffer[pos] << 8) + buffer[pos + 1];
861
+ pos += 2;
862
+ // Extensions end position
863
+ const extensionsEnd = pos + extensionsLength;
864
+ if (extensionsEnd > buffer.length)
865
+ return false;
866
+ // Look for early data extension
867
+ while (pos + 4 <= extensionsEnd) {
868
+ const extensionType = (buffer[pos] << 8) + buffer[pos + 1];
869
+ pos += 2;
870
+ const extensionLength = (buffer[pos] << 8) + buffer[pos + 1];
871
+ pos += 2;
872
+ if (extensionType === this.TLS_EARLY_DATA_EXTENSION_TYPE) {
873
+ log('Early Data (0-RTT) extension detected');
874
+ return true;
875
+ }
876
+ // Skip to next extension
877
+ pos += extensionLength;
878
+ }
879
+ return false;
880
+ }
881
+ catch (error) {
882
+ log(`Error checking for early data: ${error}`);
883
+ return false;
884
+ }
885
+ }
886
+ /**
887
+ * Attempts to extract SNI from an initial ClientHello packet and handles
888
+ * session resumption edge cases more robustly than the standard extraction.
889
+ *
890
+ * This method handles:
891
+ * 1. Standard SNI extraction
892
+ * 2. TLS 1.3 PSK-based resumption (Chrome, Firefox, etc.)
893
+ * 3. Session ticket-based resumption
894
+ * 4. Fragmented ClientHello messages
895
+ * 5. TLS 1.3 Early Data (0-RTT)
896
+ * 6. Chrome's connection racing behaviors
897
+ *
898
+ * @param buffer - The buffer containing the TLS ClientHello message
899
+ * @param connectionInfo - Optional connection information for fragment handling
900
+ * @param enableLogging - Whether to enable detailed debug logging
901
+ * @returns The extracted server name or undefined if not found
902
+ */
903
+ static extractSNIWithResumptionSupport(buffer, connectionInfo, enableLogging = false) {
904
+ const log = (message) => {
905
+ if (enableLogging) {
906
+ console.log(`[SNI Extraction] ${message}`);
907
+ }
908
+ };
909
+ // Log buffer details for debugging
910
+ if (enableLogging) {
911
+ log(`Buffer size: ${buffer.length} bytes`);
912
+ log(`Buffer starts with: ${buffer.slice(0, Math.min(10, buffer.length)).toString('hex')}`);
913
+ if (buffer.length >= 5) {
914
+ const recordType = buffer[0];
915
+ const majorVersion = buffer[1];
916
+ const minorVersion = buffer[2];
917
+ const recordLength = (buffer[3] << 8) + buffer[4];
918
+ log(`TLS Record: type=${recordType}, version=${majorVersion}.${minorVersion}, length=${recordLength}`);
919
+ }
920
+ }
921
+ // Check if we need to handle fragmented packets
922
+ let processBuffer = buffer;
923
+ if (connectionInfo) {
924
+ const connectionId = this.createConnectionId(connectionInfo);
925
+ const reassembledBuffer = this.handleFragmentedClientHello(buffer, connectionId, enableLogging);
926
+ if (!reassembledBuffer) {
927
+ log(`Waiting for more fragments on connection ${connectionId}`);
928
+ return undefined; // Need more fragments to complete ClientHello
929
+ }
930
+ processBuffer = reassembledBuffer;
931
+ log(`Using reassembled buffer of length ${processBuffer.length}`);
932
+ }
933
+ // First try the standard SNI extraction
934
+ const standardSni = this.extractSNI(processBuffer, enableLogging);
935
+ if (standardSni) {
936
+ log(`Found standard SNI: ${standardSni}`);
937
+ return standardSni;
938
+ }
939
+ // Check for session resumption when standard SNI extraction fails
940
+ if (this.isClientHello(processBuffer)) {
941
+ const resumptionInfo = this.hasSessionResumption(processBuffer, enableLogging);
942
+ if (resumptionInfo.isResumption) {
943
+ log(`Detected session resumption in ClientHello without standard SNI`);
944
+ // Try to extract SNI from PSK extension
945
+ const pskSni = this.extractSNIFromPSKExtension(processBuffer, enableLogging);
946
+ if (pskSni) {
947
+ log(`Extracted SNI from PSK extension: ${pskSni}`);
948
+ return pskSni;
949
+ }
950
+ }
951
+ }
952
+ // Log detailed info about the ClientHello when SNI extraction fails
953
+ if (this.isClientHello(processBuffer) && enableLogging) {
954
+ log(`SNI extraction failed for ClientHello. Buffer details:`);
955
+ if (processBuffer.length >= 43) {
956
+ // ClientHello with at least client random
957
+ const clientRandom = processBuffer.slice(11, 11 + 32).toString('hex');
958
+ log(`Client Random: ${clientRandom}`);
959
+ // Log session ID length and presence
960
+ const sessionIdLength = processBuffer[43];
961
+ log(`Session ID length: ${sessionIdLength}`);
962
+ if (sessionIdLength > 0 && processBuffer.length >= 44 + sessionIdLength) {
963
+ const sessionId = processBuffer.slice(44, 44 + sessionIdLength).toString('hex');
964
+ log(`Session ID: ${sessionId}`);
965
+ }
966
+ }
967
+ }
968
+ return undefined;
969
+ }
970
+ /**
971
+ * Main entry point for SNI extraction that handles all edge cases.
972
+ * This should be called for each TLS packet received from a client.
973
+ *
974
+ * The method uses connection tracking to handle fragmented ClientHello
975
+ * messages and various TLS 1.3 behaviors, including Chrome's connection
976
+ * racing patterns.
977
+ *
978
+ * @param buffer - The buffer containing TLS data
979
+ * @param connectionInfo - Connection metadata (IPs and ports)
980
+ * @param enableLogging - Whether to enable detailed debug logging
981
+ * @param cachedSni - Optional cached SNI from previous connections (for racing detection)
982
+ * @returns The extracted server name or undefined if not found or more data needed
983
+ */
984
+ static processTlsPacket(buffer, connectionInfo, enableLogging = false, cachedSni) {
985
+ const log = (message) => {
986
+ if (enableLogging) {
987
+ console.log(`[TLS Packet] ${message}`);
988
+ }
989
+ };
990
+ // Add timestamp if not provided
991
+ if (!connectionInfo.timestamp) {
992
+ connectionInfo.timestamp = Date.now();
993
+ }
994
+ // Check if this is a TLS handshake or application data
995
+ if (!this.isTlsHandshake(buffer) && !this.isTlsApplicationData(buffer)) {
996
+ log('Not a TLS handshake or application data packet');
997
+ return undefined;
998
+ }
999
+ // Create connection ID for tracking
1000
+ const connectionId = this.createConnectionId(connectionInfo);
1001
+ log(`Processing TLS packet for connection ${connectionId}, buffer length: ${buffer.length}`);
1002
+ // Handle application data with cached SNI (for connection racing)
1003
+ if (this.isTlsApplicationData(buffer)) {
1004
+ // First check if explicit cachedSni was provided
1005
+ if (cachedSni) {
1006
+ log(`Using provided cached SNI for application data: ${cachedSni}`);
1007
+ return cachedSni;
1008
+ }
1009
+ log('Application data packet without cached SNI, cannot determine hostname');
1010
+ return undefined;
1011
+ }
1012
+ // Enhanced session resumption detection
1013
+ if (this.isClientHello(buffer)) {
1014
+ const resumptionInfo = this.hasSessionResumption(buffer, enableLogging);
1015
+ if (resumptionInfo.isResumption) {
1016
+ log(`Session resumption detected in TLS packet`);
1017
+ // Always try standard SNI extraction first
1018
+ const standardSni = this.extractSNI(buffer, enableLogging);
1019
+ if (standardSni) {
1020
+ log(`Found standard SNI in session resumption: ${standardSni}`);
1021
+ return standardSni;
1022
+ }
1023
+ // Enhanced session resumption SNI extraction
1024
+ // Try extracting from PSK identity
1025
+ const pskSni = this.extractSNIFromPSKExtension(buffer, enableLogging);
1026
+ if (pskSni) {
1027
+ log(`Extracted SNI from PSK extension: ${pskSni}`);
1028
+ return pskSni;
1029
+ }
1030
+ // Additional check for SNI in session tickets
1031
+ if (enableLogging) {
1032
+ log(`Checking for session ticket information to extract server name...`);
1033
+ // Log more details for debugging
1034
+ try {
1035
+ // Look at the raw buffer for patterns
1036
+ log(`Buffer hexdump (first 100 bytes): ${buffer.slice(0, 100).toString('hex')}`);
1037
+ // Try to find hostname-like patterns in the buffer
1038
+ const bufferStr = buffer.toString('utf8', 0, buffer.length);
1039
+ const hostnamePattern = /([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?/gi;
1040
+ const hostMatches = bufferStr.match(hostnamePattern);
1041
+ if (hostMatches && hostMatches.length > 0) {
1042
+ log(`Possible hostnames found in buffer: ${hostMatches.join(', ')}`);
1043
+ // Check if any match looks like a valid domain
1044
+ for (const match of hostMatches) {
1045
+ if (match.includes('.') && match.length > 3) {
1046
+ log(`Potential SNI found in session data: ${match}`);
1047
+ // Don't automatically use this - just log for debugging
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ catch (e) {
1053
+ log(`Error scanning for patterns: ${e}`);
1054
+ }
1055
+ }
1056
+ log(`Session resumption without extractable SNI`);
1057
+ // If allowSessionTicket=false, should be rejected by caller
1058
+ }
1059
+ }
1060
+ // For handshake messages, try the full extraction process
1061
+ const sni = this.extractSNIWithResumptionSupport(buffer, connectionInfo, enableLogging);
1062
+ if (sni) {
1063
+ log(`Successfully extracted SNI: ${sni}`);
1064
+ return sni;
1065
+ }
1066
+ // If we couldn't extract an SNI, check if this is a valid ClientHello
1067
+ if (this.isClientHello(buffer)) {
1068
+ log('Valid ClientHello detected, but no SNI extracted - might need more data');
1069
+ }
1070
+ return undefined;
1071
+ }
1072
+ }
1073
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5wcC5zbmloYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5wcC5zbmloYW5kbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFFaEM7Ozs7O0dBS0c7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQUNyQixpQ0FBaUM7YUFDVCw4QkFBeUIsR0FBRyxFQUFFLENBQUM7YUFDL0IsOEJBQXlCLEdBQUcsRUFBRSxDQUFDLEdBQUMsbUNBQW1DO2FBQ25FLG9DQUErQixHQUFHLENBQUMsQ0FBQzthQUNwQywyQkFBc0IsR0FBRyxNQUFNLENBQUM7YUFDaEMsc0NBQWlDLEdBQUcsTUFBTSxDQUFDO2FBQzNDLDJCQUFzQixHQUFHLENBQUMsQ0FBQzthQUMzQiwyQkFBc0IsR0FBRyxNQUFNLENBQUMsR0FBQyw0Q0FBNEM7YUFDN0Usb0NBQStCLEdBQUcsTUFBTSxDQUFDLEdBQUMseUJBQXlCO2FBQ25FLGtDQUE2QixHQUFHLE1BQU0sQ0FBQyxHQUFDLCtCQUErQjtJQUUvRixzREFBc0Q7YUFDdkMsc0JBQWlCLEdBQXdCLElBQUksR0FBRyxFQUFFLENBQUM7YUFDbkQsb0JBQWUsR0FBVyxJQUFJLENBQUMsR0FBQywwQ0FBMEM7SUFFekY7Ozs7O09BS0c7SUFDSyxNQUFNLENBQUMsbUJBQW1CLENBQUMsTUFBYztRQUMvQyxJQUFJLENBQUM7WUFDSCxJQUFJLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLEVBQUUsRUFBRSxDQUFDO2dCQUN0RCxPQUFPLFNBQVMsQ0FBQztZQUNuQixDQUFDO1lBRUQsb0VBQW9FO1lBQ3BFLDBEQUEwRDtZQUMxRCwyREFBMkQ7WUFDM0QsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLEVBQUUsRUFBRSxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7UUFDbkMs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