@push.rocks/smartproxy
Version:
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.
288 lines • 24.5 kB
JavaScript
import * as plugins from '../../plugins.js';
import { logger } from '../../core/utils/logger.js';
/**
* Unix domain socket server that receives relayed connections from the Rust proxy.
*
* When Rust encounters a route of type `socket-handler`, it connects to this
* Unix socket, sends a JSON metadata line, then proxies the raw TCP bytes.
* This server reads the metadata, finds the original JS handler, builds an
* IRouteContext, and hands the socket to the handler.
*/
export class SocketHandlerServer {
server = null;
socketPath;
preprocessor;
activeSockets = new Set();
constructor(preprocessor) {
this.preprocessor = preprocessor;
this.socketPath = `/tmp/smartproxy-relay-${process.pid}.sock`;
}
/**
* The Unix socket path this server listens on.
*/
getSocketPath() {
return this.socketPath;
}
/**
* Start listening for relayed connections from Rust.
*/
async start() {
// Clean up stale socket file
try {
await plugins.fs.promises.unlink(this.socketPath);
}
catch {
// Ignore if doesn't exist
}
return new Promise((resolve, reject) => {
this.server = plugins.net.createServer((socket) => {
this.activeSockets.add(socket);
socket.on('close', () => this.activeSockets.delete(socket));
this.handleConnection(socket);
});
this.server.on('error', (err) => {
logger.log('error', `SocketHandlerServer error: ${err.message}`, { component: 'socket-handler-server' });
});
this.server.listen(this.socketPath, () => {
logger.log('info', `SocketHandlerServer listening on ${this.socketPath}`, { component: 'socket-handler-server' });
resolve();
});
this.server.on('error', reject);
});
}
/**
* Stop the server and clean up.
*/
async stop() {
// Destroy all active connections first
for (const socket of this.activeSockets) {
socket.destroy();
}
this.activeSockets.clear();
if (this.server) {
return new Promise((resolve) => {
this.server.close(() => {
this.server = null;
// Clean up socket file
plugins.fs.unlink(this.socketPath, () => resolve());
});
});
}
}
/**
* Handle an incoming relayed connection from Rust.
*
* Protocol: Rust sends a single JSON line with metadata, then raw bytes follow.
* JSON format: { "routeKey": "my-route", "remoteIP": "1.2.3.4", "remotePort": 12345,
* "localPort": 443, "isTLS": true, "domain": "example.com" }
*/
handleConnection(socket) {
let metadataBuffer = Buffer.alloc(0);
let metadataParsed = false;
// 10s timeout for metadata parsing phase — if Rust connects but never
// sends the JSON metadata line, don't hold the socket open indefinitely.
socket.setTimeout(10_000);
socket.on('timeout', () => {
if (!metadataParsed) {
logger.log('warn', 'Socket handler metadata timeout, closing', { component: 'socket-handler-server' });
socket.destroy();
}
});
const onData = (chunk) => {
if (metadataParsed)
return;
metadataBuffer = metadataBuffer.length === 0
? chunk
: Buffer.concat([metadataBuffer, chunk], metadataBuffer.length + chunk.length);
const newlineIndex = metadataBuffer.indexOf(0x0a);
if (newlineIndex === -1) {
// Haven't received full metadata line yet
if (metadataBuffer.length > 8192) {
logger.log('error', 'Socket handler metadata too large, closing', { component: 'socket-handler-server' });
socket.destroy();
}
return;
}
metadataParsed = true;
socket.setTimeout(0); // Clear metadata timeout
socket.removeListener('data', onData);
socket.pause(); // Prevent data loss between handler removal and pipe setup
const metadataJson = metadataBuffer.subarray(0, newlineIndex).toString('utf8');
const remainingData = metadataBuffer.subarray(newlineIndex + 1);
let metadata;
try {
metadata = JSON.parse(metadataJson);
}
catch {
logger.log('error', `Invalid socket handler metadata JSON: ${metadataJson.slice(0, 200)}`, { component: 'socket-handler-server' });
socket.destroy();
return;
}
this.dispatchToHandler(socket, metadata, remainingData);
};
socket.on('data', onData);
socket.on('error', (err) => {
logger.log('error', `Socket handler relay error: ${err.message}`, { component: 'socket-handler-server' });
});
}
/**
* Dispatch a relayed connection to the appropriate JS handler.
*/
dispatchToHandler(socket, metadata, remainingData) {
const routeKey = metadata.routeKey;
if (!routeKey) {
logger.log('error', 'Socket handler relay missing routeKey', { component: 'socket-handler-server' });
socket.destroy();
return;
}
const originalRoute = this.preprocessor.getOriginalRoute(routeKey);
if (!originalRoute) {
logger.log('error', `No handler found for route: ${routeKey}`, { component: 'socket-handler-server' });
socket.destroy();
return;
}
// Build route context
const context = {
port: metadata.localPort || 0,
domain: metadata.domain,
clientIp: metadata.remoteIP || 'unknown',
serverIp: '0.0.0.0',
vpn: metadata.vpn,
path: metadata.path,
isTls: metadata.isTLS || false,
tlsVersion: metadata.tlsVersion,
routeName: originalRoute.name,
routeId: originalRoute.id,
timestamp: Date.now(),
connectionId: metadata.connectionId || `relay-${Date.now()}`,
};
// If there was remaining data after the metadata line, push it back
if (remainingData.length > 0) {
socket.unshift(remainingData);
}
const handler = originalRoute.action.socketHandler;
if (handler) {
// Route has an explicit socket handler callback
try {
const result = handler(socket, context);
// If the handler is async, wait for it to finish setup before resuming.
// This prevents data loss when async handlers need to do work before
// attaching their `data` listeners.
if (result && typeof result.then === 'function') {
result.then(() => {
socket.resume();
}).catch((err) => {
logger.log('error', `Async socket handler rejected for route ${routeKey}: ${err.message}`, { component: 'socket-handler-server' });
socket.destroy();
});
}
else {
// Synchronous handler — listeners are already attached, safe to resume.
socket.resume();
}
}
catch (err) {
logger.log('error', `Socket handler threw for route ${routeKey}: ${err.message}`, { component: 'socket-handler-server' });
socket.destroy();
}
return;
}
// Route has dynamic host/port functions - resolve and forward
if (originalRoute.action.targets && originalRoute.action.targets.length > 0) {
this.forwardDynamicRoute(socket, originalRoute, context);
return;
}
logger.log('error', `Route ${routeKey} has no socketHandler and no targets`, { component: 'socket-handler-server' });
socket.destroy();
}
/**
* Forward a connection to a dynamically resolved target.
* Used for routes with function-based host/port that Rust cannot handle.
*/
forwardDynamicRoute(socket, route, context) {
const targets = route.action.targets;
// Pick a target (round-robin would be ideal, but simple random for now)
const target = targets[Math.floor(Math.random() * targets.length)];
// Resolve host
let host;
if (typeof target.host === 'function') {
try {
const result = target.host(context);
host = Array.isArray(result) ? result[Math.floor(Math.random() * result.length)] : result;
}
catch (err) {
logger.log('error', `Dynamic host function failed: ${err.message}`, { component: 'socket-handler-server' });
socket.destroy();
return;
}
}
else if (typeof target.host === 'string') {
host = target.host;
}
else if (Array.isArray(target.host)) {
host = target.host[Math.floor(Math.random() * target.host.length)];
}
else {
host = 'localhost';
}
// Resolve port
let port;
if (typeof target.port === 'function') {
try {
port = target.port(context);
}
catch (err) {
logger.log('error', `Dynamic port function failed: ${err.message}`, { component: 'socket-handler-server' });
socket.destroy();
return;
}
}
else if (typeof target.port === 'number') {
port = target.port;
}
else {
port = context.port;
}
logger.log('debug', `Dynamic forward: ${context.clientIp} -> ${host}:${port}`, { component: 'socket-handler-server' });
// Connect to the resolved target
const backend = plugins.net.connect(port, host, () => {
// Connection established — set idle timeout on both sides (5 min)
socket.setTimeout(300_000);
backend.setTimeout(300_000);
// Pipe bidirectionally
socket.pipe(backend);
backend.pipe(socket);
});
// Track backend socket for cleanup on stop()
this.activeSockets.add(backend);
backend.on('close', () => {
this.activeSockets.delete(backend);
});
// Connect timeout: if backend doesn't connect within 30s, destroy both
backend.setTimeout(30_000);
backend.on('timeout', () => {
logger.log('warn', `Dynamic forward timeout to ${host}:${port}`, { component: 'socket-handler-server' });
backend.destroy();
socket.destroy();
});
socket.on('timeout', () => {
logger.log('debug', `Dynamic forward client idle timeout`, { component: 'socket-handler-server' });
socket.destroy();
backend.destroy();
});
backend.on('error', (err) => {
logger.log('error', `Dynamic forward backend error: ${err.message}`, { component: 'socket-handler-server' });
socket.destroy();
});
socket.on('error', () => {
backend.destroy();
});
socket.on('close', () => {
backend.destroy();
});
backend.on('close', () => {
socket.destroy();
});
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic29ja2V0LWhhbmRsZXItc2VydmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHMvcHJveGllcy9zbWFydC1wcm94eS9zb2NrZXQtaGFuZGxlci1zZXJ2ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxrQkFBa0IsQ0FBQztBQUM1QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFJcEQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0sT0FBTyxtQkFBbUI7SUFDdEIsTUFBTSxHQUE4QixJQUFJLENBQUM7SUFDekMsVUFBVSxDQUFTO0lBQ25CLFlBQVksQ0FBb0I7SUFDaEMsYUFBYSxHQUFHLElBQUksR0FBRyxFQUFzQixDQUFDO0lBRXRELFlBQVksWUFBK0I7UUFDekMsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUM7UUFDakMsSUFBSSxDQUFDLFVBQVUsR0FBRyx5QkFBeUIsT0FBTyxDQUFDLEdBQUcsT0FBTyxDQUFDO0lBQ2hFLENBQUM7SUFFRDs7T0FFRztJQUNJLGFBQWE7UUFDbEIsT0FBTyxJQUFJLENBQUMsVUFBVSxDQUFDO0lBQ3pCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLDZCQUE2QjtRQUM3QixJQUFJLENBQUM7WUFDSCxNQUFNLE9BQU8sQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDcEQsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLDBCQUEwQjtRQUM1QixDQUFDO1FBRUQsT0FBTyxJQUFJLE9BQU8sQ0FBTyxDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUMzQyxJQUFJLENBQUMsTUFBTSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLENBQUMsTUFBTSxFQUFFLEVBQUU7Z0JBQ2hELElBQUksQ0FBQyxhQUFhLENBQUMs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