Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa9166be4b | |||
c5efee3bfe | |||
47508eb1eb |
@ -1,5 +1,13 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-06-01 - 19.5.19 - fix(smartproxy)
|
||||||
|
Fix connection handling and improve route matching edge cases
|
||||||
|
|
||||||
|
- Enhanced cleanup logic to prevent connection accumulation under rapid retry scenarios
|
||||||
|
- Improved matching for wildcard domains and path parameters in the route configuration
|
||||||
|
- Minor refactoring in async utilities and internal socket handling for better performance
|
||||||
|
- Updated test suites and documentation for clearer configuration examples
|
||||||
|
|
||||||
## 2025-05-29 - 19.5.3 - fix(smartproxy)
|
## 2025-05-29 - 19.5.3 - fix(smartproxy)
|
||||||
Fix route security configuration location and improve ACME timing tests and socket mock implementations
|
Fix route security configuration location and improve ACME timing tests and socket mock implementations
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@push.rocks/smartproxy",
|
"name": "@push.rocks/smartproxy",
|
||||||
"version": "19.5.18",
|
"version": "19.5.19",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
|
"description": "A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
@ -640,4 +640,25 @@ export function setupBidirectionalForwarding(
|
|||||||
- **Neutral**: Half-open connections still available when needed (opt-in)
|
- **Neutral**: Half-open connections still available when needed (opt-in)
|
||||||
|
|
||||||
### Migration Notes
|
### Migration Notes
|
||||||
No configuration changes needed. The fix applies to all proxy chains automatically.
|
No configuration changes needed. The fix applies to all proxy chains automatically.
|
||||||
|
|
||||||
|
## Socket Cleanup Handler Deprecation (v19.5.15+)
|
||||||
|
|
||||||
|
### Issue
|
||||||
|
The deprecated `createSocketCleanupHandler()` function was still being used in forwarding handlers, despite being marked as deprecated.
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
Updated all forwarding handlers to use the new centralized socket utilities:
|
||||||
|
1. **Replaced `createSocketCleanupHandler()`** with `setupBidirectionalForwarding()` in:
|
||||||
|
- `https-terminate-to-https-handler.ts`
|
||||||
|
- `https-terminate-to-http-handler.ts`
|
||||||
|
2. **Removed deprecated function** from `socket-utils.ts`
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
- Consistent socket handling across all handlers
|
||||||
|
- Proper cleanup in proxy chains (no half-open connections by default)
|
||||||
|
- Better backpressure handling with the centralized implementation
|
||||||
|
- Reduced code duplication
|
||||||
|
|
||||||
|
### Migration Notes
|
||||||
|
No user-facing changes. All forwarding handlers now use the same robust socket handling as the main SmartProxy connection handler.
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '19.5.3',
|
version: '19.5.19',
|
||||||
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
|
||||||
}
|
}
|
||||||
|
@ -67,37 +67,6 @@ export function cleanupSocket(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a cleanup handler for paired sockets (client and server)
|
|
||||||
* @param clientSocket The client socket
|
|
||||||
* @param serverSocket The server socket (optional)
|
|
||||||
* @param onCleanup Optional callback when cleanup is done
|
|
||||||
* @returns A cleanup function that can be called multiple times safely
|
|
||||||
* @deprecated Use createIndependentSocketHandlers for better half-open support
|
|
||||||
*/
|
|
||||||
export function createSocketCleanupHandler(
|
|
||||||
clientSocket: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
||||||
serverSocket?: plugins.net.Socket | plugins.tls.TLSSocket | null,
|
|
||||||
onCleanup?: (reason: string) => void
|
|
||||||
): (reason: string) => void {
|
|
||||||
let cleanedUp = false;
|
|
||||||
|
|
||||||
return (reason: string) => {
|
|
||||||
if (cleanedUp) return;
|
|
||||||
cleanedUp = true;
|
|
||||||
|
|
||||||
// Cleanup both sockets (old behavior - too aggressive)
|
|
||||||
cleanupSocket(clientSocket, 'client', { immediate: true });
|
|
||||||
if (serverSocket) {
|
|
||||||
cleanupSocket(serverSocket, 'server', { immediate: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call cleanup callback if provided
|
|
||||||
if (onCleanup) {
|
|
||||||
onCleanup(reason);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create independent cleanup handlers for paired sockets that support half-open connections
|
* Create independent cleanup handlers for paired sockets that support half-open connections
|
||||||
@ -278,19 +247,6 @@ export function setupBidirectionalForwarding(
|
|||||||
return { cleanupClient, cleanupServer };
|
return { cleanupClient, cleanupServer };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pipe two sockets together with proper cleanup on either end
|
|
||||||
* @param socket1 First socket
|
|
||||||
* @param socket2 Second socket
|
|
||||||
*/
|
|
||||||
export function pipeSockets(
|
|
||||||
socket1: plugins.net.Socket | plugins.tls.TLSSocket,
|
|
||||||
socket2: plugins.net.Socket | plugins.tls.TLSSocket
|
|
||||||
): void {
|
|
||||||
socket1.pipe(socket2);
|
|
||||||
socket2.pipe(socket1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a socket with immediate error handling to prevent crashes
|
* Create a socket with immediate error handling to prevent crashes
|
||||||
* @param options Socket creation options
|
* @param options Socket creation options
|
||||||
|
@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
import { createSocketCleanupHandler, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
import { setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTPS termination with HTTP backend
|
* Handler for HTTPS termination with HTTP backend
|
||||||
@ -100,19 +100,30 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|||||||
let backendSocket: plugins.net.Socket | null = null;
|
let backendSocket: plugins.net.Socket | null = null;
|
||||||
let dataBuffer = Buffer.alloc(0);
|
let dataBuffer = Buffer.alloc(0);
|
||||||
let connectionEstablished = false;
|
let connectionEstablished = false;
|
||||||
|
let forwardingSetup = false;
|
||||||
|
|
||||||
// Create cleanup handler for all sockets
|
// Set up initial error handling for TLS socket
|
||||||
const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
const tlsCleanupHandler = (reason: string) => {
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
if (!forwardingSetup) {
|
||||||
remoteAddress,
|
// If forwarding not set up yet, emit disconnected and cleanup
|
||||||
reason
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
});
|
remoteAddress,
|
||||||
dataBuffer = Buffer.alloc(0);
|
reason
|
||||||
connectionEstablished = false;
|
});
|
||||||
});
|
dataBuffer = Buffer.alloc(0);
|
||||||
|
connectionEstablished = false;
|
||||||
|
|
||||||
|
if (!tlsSocket.destroyed) {
|
||||||
|
tlsSocket.destroy();
|
||||||
|
}
|
||||||
|
if (backendSocket && !backendSocket.destroyed) {
|
||||||
|
backendSocket.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If forwarding is setup, setupBidirectionalForwarding will handle cleanup
|
||||||
|
};
|
||||||
|
|
||||||
// Set up error handling with our cleanup utility
|
setupSocketHandlers(tlsSocket, tlsCleanupHandler, undefined, 'tls');
|
||||||
setupSocketHandlers(tlsSocket, handleClose, undefined, 'tls');
|
|
||||||
|
|
||||||
// Set timeout
|
// Set timeout
|
||||||
const timeout = this.getTimeout();
|
const timeout = this.getTimeout();
|
||||||
@ -123,7 +134,7 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: 'TLS connection timeout'
|
error: 'TLS connection timeout'
|
||||||
});
|
});
|
||||||
handleClose('timeout');
|
tlsCleanupHandler('timeout');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle TLS data
|
// Handle TLS data
|
||||||
@ -172,30 +183,33 @@ export class HttpsTerminateToHttpHandler extends ForwardingHandler {
|
|||||||
dataBuffer = Buffer.alloc(0);
|
dataBuffer = Buffer.alloc(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up bidirectional data flow
|
// Now set up bidirectional forwarding with proper cleanup
|
||||||
tlsSocket.pipe(backendSocket!);
|
forwardingSetup = true;
|
||||||
backendSocket!.pipe(tlsSocket);
|
setupBidirectionalForwarding(tlsSocket, backendSocket!, {
|
||||||
|
onCleanup: (reason) => {
|
||||||
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
|
remoteAddress,
|
||||||
|
reason
|
||||||
|
});
|
||||||
|
dataBuffer = Buffer.alloc(0);
|
||||||
|
connectionEstablished = false;
|
||||||
|
forwardingSetup = false;
|
||||||
|
},
|
||||||
|
enableHalfOpen: false // Close both when one closes
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update the cleanup handler with the backend socket
|
// Additional error logging for backend socket
|
||||||
const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
||||||
remoteAddress,
|
|
||||||
reason
|
|
||||||
});
|
|
||||||
dataBuffer = Buffer.alloc(0);
|
|
||||||
connectionEstablished = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up handlers for backend socket
|
|
||||||
setupSocketHandlers(backendSocket, newHandleClose, undefined, 'backend');
|
|
||||||
|
|
||||||
backendSocket.on('error', (error) => {
|
backendSocket.on('error', (error) => {
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
if (!connectionEstablished) {
|
||||||
remoteAddress,
|
// Connection failed during setup
|
||||||
error: `Target connection error: ${error.message}`
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
});
|
remoteAddress,
|
||||||
|
error: `Target connection error: ${error.message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// If connected, setupBidirectionalForwarding handles cleanup
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import * as plugins from '../../plugins.js';
|
|||||||
import { ForwardingHandler } from './base-handler.js';
|
import { ForwardingHandler } from './base-handler.js';
|
||||||
import type { IForwardConfig } from '../config/forwarding-types.js';
|
import type { IForwardConfig } from '../config/forwarding-types.js';
|
||||||
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
import { ForwardingHandlerEvents } from '../config/forwarding-types.js';
|
||||||
import { createSocketCleanupHandler, setupSocketHandlers, createSocketWithErrorHandler } from '../../core/utils/socket-utils.js';
|
import { setupSocketHandlers, createSocketWithErrorHandler, setupBidirectionalForwarding } from '../../core/utils/socket-utils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler for HTTPS termination with HTTPS backend
|
* Handler for HTTPS termination with HTTPS backend
|
||||||
@ -96,17 +96,26 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
|
|
||||||
// Variable to track backend socket
|
// Variable to track backend socket
|
||||||
let backendSocket: plugins.tls.TLSSocket | null = null;
|
let backendSocket: plugins.tls.TLSSocket | null = null;
|
||||||
|
let isConnectedToBackend = false;
|
||||||
|
|
||||||
// Create cleanup handler for both sockets
|
// Set up initial error handling for TLS socket
|
||||||
const handleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
const tlsCleanupHandler = (reason: string) => {
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
if (!isConnectedToBackend) {
|
||||||
remoteAddress,
|
// If backend not connected yet, just emit disconnected event
|
||||||
reason
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
});
|
remoteAddress,
|
||||||
});
|
reason
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cleanup TLS socket if needed
|
||||||
|
if (!tlsSocket.destroyed) {
|
||||||
|
tlsSocket.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If connected to backend, setupBidirectionalForwarding will handle cleanup
|
||||||
|
};
|
||||||
|
|
||||||
// Set up error handling with our cleanup utility
|
setupSocketHandlers(tlsSocket, tlsCleanupHandler, undefined, 'tls');
|
||||||
setupSocketHandlers(tlsSocket, handleClose, undefined, 'tls');
|
|
||||||
|
|
||||||
// Set timeout
|
// Set timeout
|
||||||
const timeout = this.getTimeout();
|
const timeout = this.getTimeout();
|
||||||
@ -117,7 +126,7 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: 'TLS connection timeout'
|
error: 'TLS connection timeout'
|
||||||
});
|
});
|
||||||
handleClose('timeout');
|
tlsCleanupHandler('timeout');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get the target from configuration
|
// Get the target from configuration
|
||||||
@ -131,44 +140,55 @@ export class HttpsTerminateToHttpsHandler extends ForwardingHandler {
|
|||||||
// In a real implementation, we would configure TLS options
|
// In a real implementation, we would configure TLS options
|
||||||
rejectUnauthorized: false // For testing only, never use in production
|
rejectUnauthorized: false // For testing only, never use in production
|
||||||
}, () => {
|
}, () => {
|
||||||
|
isConnectedToBackend = true;
|
||||||
|
|
||||||
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
||||||
direction: 'outbound',
|
direction: 'outbound',
|
||||||
target: `${target.host}:${target.port}`,
|
target: `${target.host}:${target.port}`,
|
||||||
tls: true
|
tls: true
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up bidirectional data flow
|
// Set up bidirectional forwarding with proper cleanup
|
||||||
tlsSocket.pipe(backendSocket!);
|
setupBidirectionalForwarding(tlsSocket, backendSocket!, {
|
||||||
backendSocket!.pipe(tlsSocket);
|
onCleanup: (reason) => {
|
||||||
});
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
|
remoteAddress,
|
||||||
// Update the cleanup handler with the backend socket
|
reason
|
||||||
const newHandleClose = createSocketCleanupHandler(tlsSocket, backendSocket, (reason) => {
|
});
|
||||||
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
},
|
||||||
remoteAddress,
|
enableHalfOpen: false // Close both when one closes
|
||||||
reason
|
});
|
||||||
|
|
||||||
|
// Set timeout for backend socket
|
||||||
|
backendSocket!.setTimeout(timeout);
|
||||||
|
|
||||||
|
backendSocket!.on('timeout', () => {
|
||||||
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
|
remoteAddress,
|
||||||
|
error: 'Backend connection timeout'
|
||||||
|
});
|
||||||
|
// Let setupBidirectionalForwarding handle the cleanup
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set up handlers for backend socket
|
// Handle backend connection errors
|
||||||
setupSocketHandlers(backendSocket, newHandleClose, undefined, 'backend');
|
|
||||||
|
|
||||||
backendSocket.on('error', (error) => {
|
backendSocket.on('error', (error) => {
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: `Backend connection error: ${error.message}`
|
error: `Backend connection error: ${error.message}`
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
if (!isConnectedToBackend) {
|
||||||
// Set timeout for backend socket
|
// Connection failed, clean up TLS socket
|
||||||
backendSocket.setTimeout(timeout);
|
if (!tlsSocket.destroyed) {
|
||||||
|
tlsSocket.destroy();
|
||||||
backendSocket.on('timeout', () => {
|
}
|
||||||
this.emit(ForwardingHandlerEvents.ERROR, {
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
||||||
remoteAddress,
|
remoteAddress,
|
||||||
error: 'Backend connection timeout'
|
reason: `backend_connection_failed: ${error.message}`
|
||||||
});
|
});
|
||||||
newHandleClose('backend_timeout');
|
}
|
||||||
|
// If connected, let setupBidirectionalForwarding handle cleanup
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user