182 lines
5.2 KiB
TypeScript
182 lines
5.2 KiB
TypeScript
import * as plugins from '../../plugins.js';
|
|
import { ForwardingHandler } from './forwarding.handler.js';
|
|
import type { IForwardConfig } from '../types/forwarding.types.js';
|
|
import { ForwardingHandlerEvents } from '../types/forwarding.types.js';
|
|
|
|
/**
|
|
* Handler for HTTPS passthrough (SNI forwarding without termination)
|
|
*/
|
|
export class HttpsPassthroughHandler extends ForwardingHandler {
|
|
/**
|
|
* Create a new HTTPS passthrough handler
|
|
* @param config The forwarding configuration
|
|
*/
|
|
constructor(config: IForwardConfig) {
|
|
super(config);
|
|
|
|
// Validate that this is an HTTPS passthrough configuration
|
|
if (config.type !== 'https-passthrough') {
|
|
throw new Error(`Invalid configuration type for HttpsPassthroughHandler: ${config.type}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a TLS/SSL socket connection by forwarding it without termination
|
|
* @param clientSocket The incoming socket from the client
|
|
*/
|
|
public handleConnection(clientSocket: plugins.net.Socket): void {
|
|
// Get the target from configuration
|
|
const target = this.getTargetFromConfig();
|
|
|
|
// Log the connection
|
|
const remoteAddress = clientSocket.remoteAddress || 'unknown';
|
|
const remotePort = clientSocket.remotePort || 0;
|
|
|
|
this.emit(ForwardingHandlerEvents.CONNECTED, {
|
|
remoteAddress,
|
|
remotePort,
|
|
target: `${target.host}:${target.port}`
|
|
});
|
|
|
|
// Create a connection to the target server
|
|
const serverSocket = plugins.net.connect(target.port, target.host);
|
|
|
|
// Handle errors on the server socket
|
|
serverSocket.on('error', (error) => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress,
|
|
error: `Target connection error: ${error.message}`
|
|
});
|
|
|
|
// Close the client socket if it's still open
|
|
if (!clientSocket.destroyed) {
|
|
clientSocket.destroy();
|
|
}
|
|
});
|
|
|
|
// Handle errors on the client socket
|
|
clientSocket.on('error', (error) => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress,
|
|
error: `Client connection error: ${error.message}`
|
|
});
|
|
|
|
// Close the server socket if it's still open
|
|
if (!serverSocket.destroyed) {
|
|
serverSocket.destroy();
|
|
}
|
|
});
|
|
|
|
// Track data transfer for logging
|
|
let bytesSent = 0;
|
|
let bytesReceived = 0;
|
|
|
|
// Forward data from client to server
|
|
clientSocket.on('data', (data) => {
|
|
bytesSent += data.length;
|
|
|
|
// Check if server socket is writable
|
|
if (serverSocket.writable) {
|
|
const flushed = serverSocket.write(data);
|
|
|
|
// Handle backpressure
|
|
if (!flushed) {
|
|
clientSocket.pause();
|
|
serverSocket.once('drain', () => {
|
|
clientSocket.resume();
|
|
});
|
|
}
|
|
}
|
|
|
|
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
direction: 'outbound',
|
|
bytes: data.length,
|
|
total: bytesSent
|
|
});
|
|
});
|
|
|
|
// Forward data from server to client
|
|
serverSocket.on('data', (data) => {
|
|
bytesReceived += data.length;
|
|
|
|
// Check if client socket is writable
|
|
if (clientSocket.writable) {
|
|
const flushed = clientSocket.write(data);
|
|
|
|
// Handle backpressure
|
|
if (!flushed) {
|
|
serverSocket.pause();
|
|
clientSocket.once('drain', () => {
|
|
serverSocket.resume();
|
|
});
|
|
}
|
|
}
|
|
|
|
this.emit(ForwardingHandlerEvents.DATA_FORWARDED, {
|
|
direction: 'inbound',
|
|
bytes: data.length,
|
|
total: bytesReceived
|
|
});
|
|
});
|
|
|
|
// Handle connection close
|
|
const handleClose = () => {
|
|
if (!clientSocket.destroyed) {
|
|
clientSocket.destroy();
|
|
}
|
|
|
|
if (!serverSocket.destroyed) {
|
|
serverSocket.destroy();
|
|
}
|
|
|
|
this.emit(ForwardingHandlerEvents.DISCONNECTED, {
|
|
remoteAddress,
|
|
bytesSent,
|
|
bytesReceived
|
|
});
|
|
};
|
|
|
|
// Set up close handlers
|
|
clientSocket.on('close', handleClose);
|
|
serverSocket.on('close', handleClose);
|
|
|
|
// Set timeouts
|
|
const timeout = this.getTimeout();
|
|
clientSocket.setTimeout(timeout);
|
|
serverSocket.setTimeout(timeout);
|
|
|
|
// Handle timeouts
|
|
clientSocket.on('timeout', () => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress,
|
|
error: 'Client connection timeout'
|
|
});
|
|
handleClose();
|
|
});
|
|
|
|
serverSocket.on('timeout', () => {
|
|
this.emit(ForwardingHandlerEvents.ERROR, {
|
|
remoteAddress,
|
|
error: 'Server connection timeout'
|
|
});
|
|
handleClose();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Handle an HTTP request - HTTPS passthrough doesn't support HTTP
|
|
* @param req The HTTP request
|
|
* @param res The HTTP response
|
|
*/
|
|
public handleHttpRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
|
|
// HTTPS passthrough doesn't support HTTP requests
|
|
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
res.end('HTTP not supported for this domain');
|
|
|
|
this.emit(ForwardingHandlerEvents.HTTP_RESPONSE, {
|
|
statusCode: 404,
|
|
headers: { 'Content-Type': 'text/plain' },
|
|
size: 'HTTP not supported for this domain'.length
|
|
});
|
|
}
|
|
} |