Compare commits
18 Commits
Author | SHA1 | Date | |
---|---|---|---|
d612df107e | |||
1c34578c36 | |||
1f9943b5a7 | |||
67ddf97547 | |||
8a96b45ece | |||
2b6464acd5 | |||
efbb4335d7 | |||
9dd402054d | |||
6c1efc1dc0 | |||
cad0e6a2b2 | |||
794e1292e5 | |||
ee79f9ab7c | |||
107bc3b50b | |||
97982976c8 | |||
fe60f88746 | |||
252a987344 | |||
677d30563f | |||
9aa747b5d4 |
63
changelog.md
63
changelog.md
@ -1,5 +1,68 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-03-16 - 4.1.8 - fix(ConnectionHandler/tls)
|
||||||
|
Change the TLS alert sent when a ClientHello lacks SNI: use the close_notify alert instead of handshake_failure to prompt immediate retry with SNI.
|
||||||
|
|
||||||
|
- Replaced the previously sent handshake_failure alert (code 0x28) with a close_notify alert (code 0x00) in the TLS session resumption handling in ConnectionHandler.
|
||||||
|
- This change encourages clients to immediately retry and include SNI when allowSessionTicket is false.
|
||||||
|
|
||||||
|
## 2025-03-16 - 4.1.7 - fix(classes.pp.connectionhandler)
|
||||||
|
Improve TLS alert handling in ClientHello when SNI is missing and session tickets are disallowed
|
||||||
|
|
||||||
|
- Replace the unrecognized_name alert with a handshake_failure alert to ensure better client behavior.
|
||||||
|
- Refactor the alert sending mechanism using cork/uncork and add a safety timeout for the drain event.
|
||||||
|
- Enhance logging for debugging TLS handshake failures when SNI is absent.
|
||||||
|
|
||||||
|
## 2025-03-16 - 4.1.6 - fix(tls)
|
||||||
|
Refine TLS ClientHello handling when allowSessionTicket is false by replacing extensive alert timeout logic with a concise warning alert and short delay, encouraging immediate client retry with proper SNI
|
||||||
|
|
||||||
|
- Update the TLS alert sending mechanism to use cork/uncork and a short, fixed delay instead of long timeouts
|
||||||
|
- Remove redundant event listeners and excessive cleanup logic after sending the alert
|
||||||
|
- Improve code clarity and encourage clients (e.g., Chrome) to retry handshake with SNI more responsively
|
||||||
|
|
||||||
|
## 2025-03-16 - 4.1.5 - fix(TLS/ConnectionHandler)
|
||||||
|
Improve handling of TLS session resumption without SNI by sending an 'unrecognized_name' alert instead of immediately terminating the connection. This change adds a grace period for the client to retry the handshake with proper SNI and cleans up the connection if no valid response is received.
|
||||||
|
|
||||||
|
- Send a TLS warning (unrecognized_name alert, code 112) when a ClientHello is received without SNI and session tickets are disallowed.
|
||||||
|
- Utilize socket cork/uncork to ensure the alert is sent as a single packet.
|
||||||
|
- Add a 5-second alert timeout and a subsequent 30-second grace period to allow clients to initiate a new handshake with SNI.
|
||||||
|
- Clean up and terminate the connection if no valid SNI is provided after the grace period, logging appropriate termination reasons.
|
||||||
|
|
||||||
|
## 2025-03-15 - 4.1.4 - fix(ConnectionHandler)
|
||||||
|
Refactor ConnectionHandler code formatting for improved readability and consistency in log messages and whitespace handling
|
||||||
|
|
||||||
|
- Standardized indentation and spacing in method signatures and log statements
|
||||||
|
- Aligned inline comments and string concatenations for clarity
|
||||||
|
- Minor refactoring of parameter formatting without changing functionality
|
||||||
|
|
||||||
|
## 2025-03-15 - 4.1.3 - fix(connectionhandler)
|
||||||
|
Improve handling of TLS ClientHello messages when allowSessionTicket is disabled and no SNI is provided by sending a warning alert (unrecognized_name, code 0x70) with a proper callback and delay to ensure the alert is transmitted before closing the connection.
|
||||||
|
|
||||||
|
- Replace the fatal alert (0x02/0x40) with a warning alert (0x01/0x70) to notify clients to send SNI.
|
||||||
|
- Use socket.write callback to wait 100ms after sending the alert before terminating the connection.
|
||||||
|
- Remove the previous short (50ms) delay in favor of a more reliable delay mechanism before cleanup.
|
||||||
|
|
||||||
|
## 2025-03-15 - 4.1.2 - fix(connectionhandler)
|
||||||
|
Send proper TLS alert before terminating connections when SNI is missing and session tickets are disallowed.
|
||||||
|
|
||||||
|
- Added logic to transmit a fatal TLS alert (Handshake Failure) before closing the connection when no SNI is present with allowSessionTicket=false.
|
||||||
|
- Introduced a slight 50ms delay after sending the alert to ensure the client receives the alert properly.
|
||||||
|
- Applied these changes both for the initial ClientHello and when handling subsequent TLS data.
|
||||||
|
|
||||||
|
## 2025-03-15 - 4.1.1 - fix(tls)
|
||||||
|
Enforce strict SNI handling in TLS connections by terminating ClientHello messages lacking SNI when session tickets are disallowed and removing legacy session cache code.
|
||||||
|
|
||||||
|
- In classes.pp.connectionhandler.ts, if allowSessionTicket is false and no SNI is extracted from a ClientHello, the connection is terminated to force a new handshake with SNI.
|
||||||
|
- In classes.pp.snihandler.ts, removed session cache and related cleanup functions used for tab reactivation, simplifying SNI extraction logic.
|
||||||
|
- Improved logging in TLS processing to aid in diagnosing handshake and session resumption issues.
|
||||||
|
|
||||||
|
## 2025-03-14 - 4.1.0 - feat(SniHandler)
|
||||||
|
Enhance SNI extraction to support session caching and tab reactivation by adding session cache initialization, cleanup and helper methods. Update processTlsPacket to use cached SNI for session resumption and connection racing scenarios.
|
||||||
|
|
||||||
|
- Introduce initSessionCacheCleanup, cleanupSessionCache, createClientKey, cacheSession, and getCachedSession methods to manage SNI information.
|
||||||
|
- Cache SNI based on client IP and client random to improve handling of fragmented ClientHello messages and tab reactivation.
|
||||||
|
- Update processTlsPacket to leverage cached SNI when standard extraction fails, reducing redundant extraction and enhancing connection racing behavior.
|
||||||
|
|
||||||
## 2025-03-14 - 4.0.0 - BREAKING CHANGE(core)
|
## 2025-03-14 - 4.0.0 - BREAKING CHANGE(core)
|
||||||
refactor: reorganize internal module structure to use 'classes.pp.*' modules
|
refactor: reorganize internal module structure to use 'classes.pp.*' modules
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "4.0.0",
|
"version": "4.1.8",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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.",
|
"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.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '4.0.0',
|
version: '4.1.8',
|
||||||
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.'
|
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.'
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import * as plugins from './plugins.js';
|
import * as plugins from './plugins.js';
|
||||||
import type { IConnectionRecord, IDomainConfig, IPortProxySettings } from './classes.pp.interfaces.js';
|
import type {
|
||||||
|
IConnectionRecord,
|
||||||
|
IDomainConfig,
|
||||||
|
IPortProxySettings,
|
||||||
|
} from './classes.pp.interfaces.js';
|
||||||
import { ConnectionManager } from './classes.pp.connectionmanager.js';
|
import { ConnectionManager } from './classes.pp.connectionmanager.js';
|
||||||
import { SecurityManager } from './classes.pp.securitymanager.js';
|
import { SecurityManager } from './classes.pp.securitymanager.js';
|
||||||
import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
|
import { DomainConfigManager } from './classes.pp.domainconfigmanager.js';
|
||||||
@ -94,7 +98,10 @@ export class ConnectionHandler {
|
|||||||
/**
|
/**
|
||||||
* Handle a connection that should be forwarded to NetworkProxy
|
* Handle a connection that should be forwarded to NetworkProxy
|
||||||
*/
|
*/
|
||||||
private handleNetworkProxyConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
|
private handleNetworkProxyConnection(
|
||||||
|
socket: plugins.net.Socket,
|
||||||
|
record: IConnectionRecord
|
||||||
|
): void {
|
||||||
const connectionId = record.id;
|
const connectionId = record.id;
|
||||||
let initialDataReceived = false;
|
let initialDataReceived = false;
|
||||||
|
|
||||||
@ -159,8 +166,8 @@ export class ConnectionHandler {
|
|||||||
if (this.tlsManager.isTlsHandshake(chunk)) {
|
if (this.tlsManager.isTlsHandshake(chunk)) {
|
||||||
record.isTLS = true;
|
record.isTLS = true;
|
||||||
|
|
||||||
// Check session tickets if they're disabled
|
// Check for ClientHello to extract SNI - but don't enforce it for NetworkProxy
|
||||||
if (this.settings.allowSessionTicket === false && this.tlsManager.isClientHello(chunk)) {
|
if (this.tlsManager.isClientHello(chunk)) {
|
||||||
// Create connection info for SNI extraction
|
// Create connection info for SNI extraction
|
||||||
const connInfo = {
|
const connInfo = {
|
||||||
sourceIp: record.remoteIP,
|
sourceIp: record.remoteIP,
|
||||||
@ -169,14 +176,14 @@ export class ConnectionHandler {
|
|||||||
destPort: socket.localPort || 0,
|
destPort: socket.localPort || 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract SNI for domain-specific NetworkProxy handling
|
// Extract SNI for domain-specific NetworkProxy handling if available
|
||||||
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
|
const serverName = this.tlsManager.extractSNI(chunk, connInfo);
|
||||||
|
|
||||||
|
// For NetworkProxy connections, we'll allow session tickets even without SNI
|
||||||
|
// We'll only use the serverName if available to determine the specific NetworkProxy port
|
||||||
if (serverName) {
|
if (serverName) {
|
||||||
// If we got an SNI, check for domain-specific NetworkProxy settings
|
|
||||||
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
|
|
||||||
|
|
||||||
// Save domain config and SNI in connection record
|
// Save domain config and SNI in connection record
|
||||||
|
const domainConfig = this.domainConfigManager.findDomainConfig(serverName);
|
||||||
record.domainConfig = domainConfig;
|
record.domainConfig = domainConfig;
|
||||||
record.lockedDomain = serverName;
|
record.lockedDomain = serverName;
|
||||||
|
|
||||||
@ -201,6 +208,14 @@ export class ConnectionHandler {
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
this.settings.allowSessionTicket === false &&
|
||||||
|
this.settings.enableDetailedLogging
|
||||||
|
) {
|
||||||
|
// Log that we're allowing a session resumption without SNI for NetworkProxy
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] Allowing session resumption without SNI for NetworkProxy forwarding`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,14 +230,10 @@ export class ConnectionHandler {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// If not TLS, use normal direct connection
|
// If not TLS, use normal direct connection
|
||||||
console.log(`[${connectionId}] Non-TLS connection on NetworkProxy port ${record.localPort}`);
|
console.log(
|
||||||
this.setupDirectConnection(
|
`[${connectionId}] Non-TLS connection on NetworkProxy port ${record.localPort}`
|
||||||
socket,
|
|
||||||
record,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
chunk
|
|
||||||
);
|
);
|
||||||
|
this.setupDirectConnection(socket, record, undefined, undefined, chunk);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -340,14 +351,13 @@ export class ConnectionHandler {
|
|||||||
record.domainConfig = domainConfig;
|
record.domainConfig = domainConfig;
|
||||||
|
|
||||||
// Check if this domain should use NetworkProxy (domain-specific setting)
|
// Check if this domain should use NetworkProxy (domain-specific setting)
|
||||||
if (domainConfig &&
|
if (
|
||||||
|
domainConfig &&
|
||||||
this.domainConfigManager.shouldUseNetworkProxy(domainConfig) &&
|
this.domainConfigManager.shouldUseNetworkProxy(domainConfig) &&
|
||||||
this.networkProxyBridge.getNetworkProxy()) {
|
this.networkProxyBridge.getNetworkProxy()
|
||||||
|
) {
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(
|
console.log(`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`);
|
||||||
`[${connectionId}] Domain ${serverName} is configured to use NetworkProxy`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
|
const networkProxyPort = this.domainConfigManager.getNetworkProxyPort(domainConfig);
|
||||||
@ -373,19 +383,20 @@ export class ConnectionHandler {
|
|||||||
// Skip IP validation if allowedIPs is empty
|
// Skip IP validation if allowedIPs is empty
|
||||||
if (
|
if (
|
||||||
domainConfig.allowedIPs.length > 0 &&
|
domainConfig.allowedIPs.length > 0 &&
|
||||||
!this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)
|
!this.securityManager.isIPAuthorized(
|
||||||
|
record.remoteIP,
|
||||||
|
ipRules.allowedIPs,
|
||||||
|
ipRules.blockedIPs
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
return rejectIncomingConnection(
|
return rejectIncomingConnection(
|
||||||
'rejected',
|
'rejected',
|
||||||
`Connection rejected: IP ${record.remoteIP} not allowed for domain ${domainConfig.domains.join(
|
`Connection rejected: IP ${
|
||||||
', '
|
record.remoteIP
|
||||||
)}`
|
} not allowed for domain ${domainConfig.domains.join(', ')}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (this.settings.defaultAllowedIPs && this.settings.defaultAllowedIPs.length > 0) {
|
||||||
this.settings.defaultAllowedIPs &&
|
|
||||||
this.settings.defaultAllowedIPs.length > 0
|
|
||||||
) {
|
|
||||||
if (
|
if (
|
||||||
!this.securityManager.isIPAuthorized(
|
!this.securityManager.isIPAuthorized(
|
||||||
record.remoteIP,
|
record.remoteIP,
|
||||||
@ -456,9 +467,17 @@ export class ConnectionHandler {
|
|||||||
if (forcedDomain) {
|
if (forcedDomain) {
|
||||||
const ipRules = this.domainConfigManager.getEffectiveIPRules(forcedDomain);
|
const ipRules = this.domainConfigManager.getEffectiveIPRules(forcedDomain);
|
||||||
|
|
||||||
if (!this.securityManager.isIPAuthorized(record.remoteIP, ipRules.allowedIPs, ipRules.blockedIPs)) {
|
if (
|
||||||
|
!this.securityManager.isIPAuthorized(
|
||||||
|
record.remoteIP,
|
||||||
|
ipRules.allowedIPs,
|
||||||
|
ipRules.blockedIPs
|
||||||
|
)
|
||||||
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Connection from ${record.remoteIP} rejected: IP not allowed for domain ${forcedDomain.domains.join(
|
`[${connectionId}] Connection from ${
|
||||||
|
record.remoteIP
|
||||||
|
} rejected: IP not allowed for domain ${forcedDomain.domains.join(
|
||||||
', '
|
', '
|
||||||
)} on port ${localPort}.`
|
)} on port ${localPort}.`
|
||||||
);
|
);
|
||||||
@ -468,9 +487,9 @@ export class ConnectionHandler {
|
|||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Port-based connection from ${record.remoteIP} on port ${localPort} matched domain ${forcedDomain.domains.join(
|
`[${connectionId}] Port-based connection from ${
|
||||||
', '
|
record.remoteIP
|
||||||
)}.`
|
} on port ${localPort} matched domain ${forcedDomain.domains.join(', ')}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,6 +550,107 @@ export class ConnectionHandler {
|
|||||||
|
|
||||||
// Extract SNI
|
// Extract SNI
|
||||||
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
|
serverName = this.tlsManager.extractSNI(chunk, connInfo) || '';
|
||||||
|
|
||||||
|
// If allowSessionTicket is false and this is a ClientHello with no SNI, terminate the connection
|
||||||
|
if (
|
||||||
|
this.settings.allowSessionTicket === false &&
|
||||||
|
this.tlsManager.isClientHello(chunk) &&
|
||||||
|
!serverName
|
||||||
|
) {
|
||||||
|
// Block ClientHello without SNI when allowSessionTicket is false
|
||||||
|
console.log(
|
||||||
|
`[${connectionId}] No SNI detected in ClientHello and allowSessionTicket=false. ` +
|
||||||
|
`Sending warning unrecognized_name alert to encourage immediate retry with SNI.`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the termination reason first
|
||||||
|
if (record.incomingTerminationReason === null) {
|
||||||
|
record.incomingTerminationReason = 'session_ticket_blocked_no_sni';
|
||||||
|
this.connectionManager.incrementTerminationStat(
|
||||||
|
'incoming',
|
||||||
|
'session_ticket_blocked_no_sni'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a warning-level alert for unrecognized_name
|
||||||
|
// This encourages Chrome to retry immediately with SNI
|
||||||
|
const serverNameUnknownAlertData = Buffer.from([
|
||||||
|
0x15, // Alert record type
|
||||||
|
0x03,
|
||||||
|
0x03, // TLS 1.2 version
|
||||||
|
0x00,
|
||||||
|
0x02, // Length
|
||||||
|
0x01, // Warning alert level (not fatal)
|
||||||
|
0x70, // unrecognized_name alert (code 112)
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Send a handshake_failure alert instead of unrecognized_name
|
||||||
|
const sslHandshakeFailureAlertData = Buffer.from([
|
||||||
|
0x15, // Alert record type
|
||||||
|
0x03,
|
||||||
|
0x03, // TLS 1.2 version
|
||||||
|
0x00,
|
||||||
|
0x02, // Length
|
||||||
|
0x01, // Warning alert level (not fatal)
|
||||||
|
0x28, // handshake_failure alert (40) instead of unrecognized_name (112)
|
||||||
|
]);
|
||||||
|
|
||||||
|
const closeNotifyAlert = Buffer.from([
|
||||||
|
0x15, // Alert record type
|
||||||
|
0x03,
|
||||||
|
0x03, // TLS 1.2 version
|
||||||
|
0x00,
|
||||||
|
0x02, // Length
|
||||||
|
0x01, // Warning alert level (1)
|
||||||
|
0x00, // close_notify alert (0)
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use cork/uncork to ensure the alert is sent as a single packet
|
||||||
|
socket.cork();
|
||||||
|
const writeSuccessful = socket.write(closeNotifyAlert);
|
||||||
|
socket.uncork();
|
||||||
|
|
||||||
|
// Function to handle the clean socket termination
|
||||||
|
const finishConnection = () => {
|
||||||
|
// First call end() to initiate a graceful close (sends FIN)
|
||||||
|
socket.end();
|
||||||
|
|
||||||
|
// Allow a short delay for the alert and FIN to be transmitted
|
||||||
|
// before we fully close the socket
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!socket.destroyed) {
|
||||||
|
socket.destroy();
|
||||||
|
}
|
||||||
|
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||||
|
}, 150); // Short delay, but longer than the standard TCP ACK timeout
|
||||||
|
};
|
||||||
|
|
||||||
|
if (writeSuccessful) {
|
||||||
|
// If the data was successfully written to the kernel buffer,
|
||||||
|
// we can finish the connection after a short delay to ensure transmission
|
||||||
|
setTimeout(finishConnection, 50);
|
||||||
|
} else {
|
||||||
|
// If the kernel buffer was full, wait for the drain event
|
||||||
|
socket.once('drain', () => {
|
||||||
|
setTimeout(finishConnection, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set a safety timeout in case drain never happens
|
||||||
|
setTimeout(() => {
|
||||||
|
socket.removeAllListeners('drain');
|
||||||
|
finishConnection();
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// If we can't send the alert, fall back to immediate termination
|
||||||
|
console.log(`[${connectionId}] Error sending TLS alert: ${err.message}`);
|
||||||
|
socket.end();
|
||||||
|
this.connectionManager.cleanupConnection(record, 'session_ticket_blocked_no_sni');
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lock the connection to the negotiated SNI.
|
// Lock the connection to the negotiated SNI.
|
||||||
@ -584,9 +704,7 @@ export class ConnectionHandler {
|
|||||||
: this.settings.targetIP!;
|
: this.settings.targetIP!;
|
||||||
|
|
||||||
// Determine target port
|
// Determine target port
|
||||||
const targetPort = overridePort !== undefined
|
const targetPort = overridePort !== undefined ? overridePort : this.settings.toPort;
|
||||||
? overridePort
|
|
||||||
: this.settings.toPort;
|
|
||||||
|
|
||||||
// Setup connection options
|
// Setup connection options
|
||||||
const connectionOptions: plugins.net.NetConnectOpts = {
|
const connectionOptions: plugins.net.NetConnectOpts = {
|
||||||
@ -859,7 +977,9 @@ export class ConnectionHandler {
|
|||||||
const combinedData = Buffer.concat(record.pendingData);
|
const combinedData = Buffer.concat(record.pendingData);
|
||||||
|
|
||||||
if (this.settings.enableDetailedLogging) {
|
if (this.settings.enableDetailedLogging) {
|
||||||
console.log(`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`);
|
console.log(
|
||||||
|
`[${connectionId}] Forwarding ${combinedData.length} bytes of initial data to target`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write pending data immediately
|
// Write pending data immediately
|
||||||
@ -957,15 +1077,12 @@ export class ConnectionHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set connection timeout
|
// Set connection timeout
|
||||||
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(
|
record.cleanupTimer = this.timeoutManager.setupConnectionTimeout(record, (record, reason) => {
|
||||||
record,
|
|
||||||
(record, reason) => {
|
|
||||||
console.log(
|
console.log(
|
||||||
`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`
|
`[${connectionId}] Connection from ${record.remoteIP} exceeded max lifetime, forcing cleanup.`
|
||||||
);
|
);
|
||||||
this.connectionManager.initiateCleanupOnce(record, reason);
|
this.connectionManager.initiateCleanupOnce(record, reason);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
// Mark TLS handshake as complete for TLS connections
|
// Mark TLS handshake as complete for TLS connections
|
||||||
if (record.isTLS) {
|
if (record.isTLS) {
|
||||||
|
@ -22,27 +22,6 @@ export class SniHandler {
|
|||||||
private static fragmentedBuffers: Map<string, Buffer> = new Map();
|
private static fragmentedBuffers: Map<string, Buffer> = new Map();
|
||||||
private static fragmentTimeout: number = 1000; // ms to wait for fragments before cleanup
|
private static fragmentTimeout: number = 1000; // ms to wait for fragments before cleanup
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the client random value from a ClientHello message
|
|
||||||
*
|
|
||||||
* @param buffer - The buffer containing the ClientHello
|
|
||||||
* @returns The 32-byte client random or undefined if extraction fails
|
|
||||||
*/
|
|
||||||
private static extractClientRandom(buffer: Buffer): Buffer | undefined {
|
|
||||||
try {
|
|
||||||
if (!this.isClientHello(buffer) || buffer.length < 46) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In a ClientHello message, the client random starts at position 11
|
|
||||||
// after record header (5 bytes), handshake type (1 byte),
|
|
||||||
// handshake length (3 bytes), and client version (2 bytes)
|
|
||||||
return buffer.slice(11, 11 + 32);
|
|
||||||
} catch (error) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a buffer contains a TLS handshake message (record type 22)
|
* Checks if a buffer contains a TLS handshake message (record type 22)
|
||||||
* @param buffer - The buffer to check
|
* @param buffer - The buffer to check
|
||||||
@ -1174,7 +1153,7 @@ export class SniHandler {
|
|||||||
*
|
*
|
||||||
* The method uses connection tracking to handle fragmented ClientHello
|
* The method uses connection tracking to handle fragmented ClientHello
|
||||||
* messages and various TLS 1.3 behaviors, including Chrome's connection
|
* messages and various TLS 1.3 behaviors, including Chrome's connection
|
||||||
* racing patterns.
|
* racing patterns and tab reactivation behaviors.
|
||||||
*
|
*
|
||||||
* @param buffer - The buffer containing TLS data
|
* @param buffer - The buffer containing TLS data
|
||||||
* @param connectionInfo - Connection metadata (IPs and ports)
|
* @param connectionInfo - Connection metadata (IPs and ports)
|
||||||
@ -1217,7 +1196,7 @@ export class SniHandler {
|
|||||||
|
|
||||||
// Handle application data with cached SNI (for connection racing)
|
// Handle application data with cached SNI (for connection racing)
|
||||||
if (this.isTlsApplicationData(buffer)) {
|
if (this.isTlsApplicationData(buffer)) {
|
||||||
// First check if explicit cachedSni was provided
|
// If explicit cachedSni was provided, use it
|
||||||
if (cachedSni) {
|
if (cachedSni) {
|
||||||
log(`Using provided cached SNI for application data: ${cachedSni}`);
|
log(`Using provided cached SNI for application data: ${cachedSni}`);
|
||||||
return cachedSni;
|
return cachedSni;
|
||||||
|
Reference in New Issue
Block a user