fix(routing): unify route based architecture

This commit is contained in:
2025-05-13 12:48:41 +00:00
parent fe632bde67
commit fcc8cf9caa
46 changed files with 7943 additions and 1332 deletions

View File

@ -8,7 +8,8 @@ import {
} from './models/interfaces.js';
import type {
IRouteConfig,
IRouteAction
IRouteAction,
IRouteContext
} from './models/route-types.js';
import { ConnectionManager } from './connection-manager.js';
import { SecurityManager } from './security-manager.js';
@ -24,6 +25,9 @@ import type { ForwardingHandler } from '../../forwarding/handlers/base-handler.j
export class RouteConnectionHandler {
private settings: ISmartProxyOptions;
// Cache for route contexts to avoid recreation
private routeContextCache: Map<string, IRouteContext> = new Map();
constructor(
settings: ISmartProxyOptions,
private connectionManager: ConnectionManager,
@ -36,6 +40,47 @@ export class RouteConnectionHandler {
this.settings = settings;
}
/**
* Create a route context object for port and host mapping functions
*/
private createRouteContext(options: {
connectionId: string;
port: number;
domain?: string;
clientIp: string;
serverIp: string;
isTls: boolean;
tlsVersion?: string;
routeName?: string;
routeId?: string;
path?: string;
query?: string;
headers?: Record<string, string>;
}): IRouteContext {
return {
// Connection information
port: options.port,
domain: options.domain,
clientIp: options.clientIp,
serverIp: options.serverIp,
path: options.path,
query: options.query,
headers: options.headers,
// TLS information
isTls: options.isTls,
tlsVersion: options.tlsVersion,
// Route information
routeName: options.routeName,
routeId: options.routeId,
// Additional properties
timestamp: Date.now(),
connectionId: options.connectionId
};
}
/**
* Handle a new incoming connection
*/
@ -325,7 +370,7 @@ export class RouteConnectionHandler {
): void {
const connectionId = record.id;
const action = route.action;
// We should have a target configuration for forwarding
if (!action.target) {
console.log(`[${connectionId}] Forward action missing target configuration`);
@ -333,24 +378,82 @@ export class RouteConnectionHandler {
this.connectionManager.cleanupConnection(record, 'missing_target');
return;
}
// Create the routing context for this connection
const routeContext = this.createRouteContext({
connectionId: record.id,
port: record.localPort,
domain: record.lockedDomain,
clientIp: record.remoteIP,
serverIp: socket.localAddress || '',
isTls: record.isTLS || false,
tlsVersion: record.tlsVersion,
routeName: route.name,
routeId: route.id
});
// Cache the context for potential reuse
this.routeContextCache.set(connectionId, routeContext);
// Determine host using function or static value
let targetHost: string | string[];
if (typeof action.target.host === 'function') {
try {
targetHost = action.target.host(routeContext);
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Dynamic host resolved to: ${Array.isArray(targetHost) ? targetHost.join(', ') : targetHost}`);
}
} catch (err) {
console.log(`[${connectionId}] Error in host mapping function: ${err}`);
socket.end();
this.connectionManager.cleanupConnection(record, 'host_mapping_error');
return;
}
} else {
targetHost = action.target.host;
}
// If an array of hosts, select one randomly for load balancing
const selectedHost = Array.isArray(targetHost)
? targetHost[Math.floor(Math.random() * targetHost.length)]
: targetHost;
// Determine port using function or static value
let targetPort: number;
if (typeof action.target.port === 'function') {
try {
targetPort = action.target.port(routeContext);
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Dynamic port mapping: ${record.localPort} -> ${targetPort}`);
}
// Store the resolved target port in the context for potential future use
routeContext.targetPort = targetPort;
} catch (err) {
console.log(`[${connectionId}] Error in port mapping function: ${err}`);
socket.end();
this.connectionManager.cleanupConnection(record, 'port_mapping_error');
return;
}
} else if (action.target.preservePort) {
// Use incoming port if preservePort is true
targetPort = record.localPort;
} else {
// Use static port from configuration
targetPort = action.target.port;
}
// Store the resolved host in the context
routeContext.targetHost = selectedHost;
// Determine if this needs TLS handling
if (action.tls) {
switch (action.tls.mode) {
case 'passthrough':
// For TLS passthrough, just forward directly
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Using TLS passthrough to ${action.target.host}`);
console.log(`[${connectionId}] Using TLS passthrough to ${selectedHost}:${targetPort}`);
}
// Allow for array of hosts
const targetHost = Array.isArray(action.target.host)
? action.target.host[Math.floor(Math.random() * action.target.host.length)]
: action.target.host;
// Determine target port - either target port or preserve incoming port
const targetPort = action.target.preservePort ? record.localPort : action.target.port;
return this.setupDirectConnection(
socket,
record,
@ -358,7 +461,7 @@ export class RouteConnectionHandler {
record.lockedDomain,
initialChunk,
undefined,
targetHost,
selectedHost,
targetPort
);
@ -402,14 +505,36 @@ export class RouteConnectionHandler {
console.log(`[${connectionId}] Using basic forwarding to ${action.target.host}:${action.target.port}`);
}
// Allow for array of hosts
const targetHost = Array.isArray(action.target.host)
? action.target.host[Math.floor(Math.random() * action.target.host.length)]
: action.target.host;
// Determine target port - either target port or preserve incoming port
const targetPort = action.target.preservePort ? record.localPort : action.target.port;
// Get the appropriate host value
let targetHost: string;
if (typeof action.target.host === 'function') {
// For function-based host, use the same routeContext created earlier
const hostResult = action.target.host(routeContext);
targetHost = Array.isArray(hostResult)
? hostResult[Math.floor(Math.random() * hostResult.length)]
: hostResult;
} else {
// For static host value
targetHost = Array.isArray(action.target.host)
? action.target.host[Math.floor(Math.random() * action.target.host.length)]
: action.target.host;
}
// Determine port - either function-based, static, or preserve incoming port
let targetPort: number;
if (typeof action.target.port === 'function') {
targetPort = action.target.port(routeContext);
} else if (action.target.preservePort) {
targetPort = record.localPort;
} else {
targetPort = action.target.port;
}
// Update the connection record and context with resolved values
record.targetHost = targetHost;
record.targetPort = targetPort;
return this.setupDirectConnection(
socket,
record,
@ -552,13 +677,23 @@ export class RouteConnectionHandler {
// Determine target host and port if not provided
const finalTargetHost = targetHost ||
record.targetHost ||
(this.settings.defaults?.target?.host || 'localhost');
// Determine target port
const finalTargetPort = targetPort ||
record.targetPort ||
(overridePort !== undefined ? overridePort :
(this.settings.defaults?.target?.port || 443));
// Update record with final target information
record.targetHost = finalTargetHost;
record.targetPort = finalTargetPort;
if (this.settings.enableDetailedLogging) {
console.log(`[${connectionId}] Setting up direct connection to ${finalTargetHost}:${finalTargetPort}`);
}
// Setup connection options
const connectionOptions: plugins.net.NetConnectOpts = {
host: finalTargetHost,