UNPKG

@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.

205 lines 15.7 kB
import * as plugins from '../../plugins.js'; import { logger } from '../../core/utils/logger.js'; /** * Server that receives UDP datagrams from Rust via Unix stream socket * and dispatches them to TypeScript datagramHandler callbacks. * * Protocol: length-prefixed JSON frames over a persistent Unix stream socket. * - Rust→TS: { type: "datagram", routeKey, sourceIp, sourcePort, destPort, payloadBase64 } * - TS→Rust: { type: "reply", sourceIp, sourcePort, destPort, payloadBase64 } */ export class DatagramHandlerServer { static MAX_BUFFER_SIZE = 50 * 1024 * 1024; // 50 MB server = null; connection = null; socketPath; preprocessor; readBuffer = Buffer.alloc(0); constructor(socketPath, preprocessor) { this.socketPath = socketPath; this.preprocessor = preprocessor; } /** * Start listening on the Unix socket. */ 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.handleConnection(socket); }); this.server.on('error', (err) => { logger.log('error', `DatagramHandlerServer error: ${err.message}`); reject(err); }); this.server.listen(this.socketPath, () => { logger.log('info', `DatagramHandlerServer listening on ${this.socketPath}`); resolve(); }); }); } /** * Stop the server and clean up. */ async stop() { if (this.connection) { this.connection.destroy(); this.connection = null; } if (this.server) { await new Promise((resolve) => { this.server.close(() => resolve()); }); this.server = null; } try { await plugins.fs.promises.unlink(this.socketPath); } catch { // Ignore } } /** * Handle a new connection from Rust. * Only one connection at a time (Rust maintains a persistent connection). */ handleConnection(socket) { if (this.connection) { logger.log('warn', 'DatagramHandlerServer: replacing existing connection'); this.connection.destroy(); } this.connection = socket; this.readBuffer = Buffer.alloc(0); socket.on('data', (chunk) => { this.readBuffer = Buffer.concat([this.readBuffer, chunk]); if (this.readBuffer.length > DatagramHandlerServer.MAX_BUFFER_SIZE) { logger.log('error', `DatagramHandlerServer: buffer exceeded ${DatagramHandlerServer.MAX_BUFFER_SIZE} bytes, resetting`); this.readBuffer = Buffer.alloc(0); return; } this.processFrames(); }); socket.on('error', (err) => { logger.log('error', `DatagramHandlerServer connection error: ${err.message}`); }); socket.on('close', () => { if (this.connection === socket) { this.connection = null; } }); logger.log('info', 'DatagramHandlerServer: Rust relay connected'); } /** * Process length-prefixed frames from the read buffer. */ processFrames() { while (this.readBuffer.length >= 4) { const frameLen = this.readBuffer.readUInt32BE(0); // Safety: reject absurdly large frames if (frameLen > 10 * 1024 * 1024) { logger.log('error', `DatagramHandlerServer: frame too large (${frameLen} bytes), resetting`); this.readBuffer = Buffer.alloc(0); return; } if (this.readBuffer.length < 4 + frameLen) { // Incomplete frame, wait for more data return; } const frameData = this.readBuffer.subarray(4, 4 + frameLen); this.readBuffer = this.readBuffer.subarray(4 + frameLen); try { const msg = JSON.parse(frameData.toString('utf8')); this.handleMessage(msg); } catch (err) { logger.log('error', `DatagramHandlerServer: failed to parse frame: ${err}`); } } } /** * Handle a received datagram message from Rust. */ handleMessage(msg) { if (msg.type !== 'datagram') { return; } const originalRoute = this.preprocessor.getOriginalRoute(msg.routeKey); if (!originalRoute) { logger.log('warn', `DatagramHandlerServer: no handler for route '${msg.routeKey}'`); return; } const handler = originalRoute.action.datagramHandler; if (!handler) { logger.log('warn', `DatagramHandlerServer: route '${msg.routeKey}' has no datagramHandler`); return; } const datagram = Buffer.from(msg.payloadBase64, 'base64'); const context = { port: msg.destPort, domain: undefined, clientIp: msg.sourceIp, serverIp: '0.0.0.0', path: undefined, isTls: false, tlsVersion: undefined, routeName: originalRoute.name, routeId: originalRoute.id, timestamp: Date.now(), connectionId: `udp-${msg.sourceIp}:${msg.sourcePort}-${Date.now()}`, }; const info = { sourceIp: msg.sourceIp, sourcePort: msg.sourcePort, destPort: msg.destPort, context, }; const reply = (data) => { this.sendReply({ type: 'reply', routeKey: msg.routeKey, sourceIp: msg.sourceIp, sourcePort: msg.sourcePort, destPort: msg.destPort, payloadBase64: data.toString('base64'), }); }; try { const result = handler(datagram, info, reply); if (result && typeof result.catch === 'function') { result.catch((err) => { logger.log('error', `DatagramHandler error for route '${msg.routeKey}': ${err}`); }); } } catch (err) { logger.log('error', `DatagramHandler threw for route '${msg.routeKey}': ${err}`); } } /** * Send a reply frame back to Rust. */ sendReply(msg) { if (!this.connection || this.connection.destroyed) { logger.log('warn', 'DatagramHandlerServer: cannot send reply, no connection'); return; } const json = JSON.stringify(msg); const payload = Buffer.from(json, 'utf8'); const header = Buffer.alloc(4); header.writeUInt32BE(payload.length, 0); this.connection.write(Buffer.concat([header, payload])); } /** * Get the socket path for passing to Rust via IPC. */ getSocketPath() { return this.socketPath; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YWdyYW0taGFuZGxlci1zZXJ2ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L2RhdGFncmFtLWhhbmRsZXItc2VydmVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBa0JwRDs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxPQUFPLHFCQUFxQjtJQUN4QixNQUFNLENBQVUsZUFBZSxHQUFHLEVBQUUsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsUUFBUTtJQUU1RCxNQUFNLEdBQThCLElBQUksQ0FBQztJQUN6QyxVQUFVLEdBQThCLElBQUksQ0FBQztJQUM3QyxVQUFVLENBQVM7SUFDbkIsWUFBWSxDQUFvQjtJQUNoQyxVQUFVLEdBQVcsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUU3QyxZQUFZLFVBQWtCLEVBQUUsWUFBK0I7UUFDN0QsSUFBSSxDQUFDLFVBQVUsR0FBRyxVQUFVLENBQUM7UUFDN0IsSUFBSSxDQUFDLFlBQVksR0FBRyxZQUFZLENBQUM7SUFDbkMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsNkJBQTZCO1FBQzdCLElBQUksQ0FBQztZQUNILE1BQU0sT0FBTyxDQUFDLEVBQUUsQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNwRCxDQUFDO1FBQUMsTUFBTSxDQUFDO1lBQ1AsMEJBQTBCO1FBQzVCLENBQUM7UUFFRCxPQUFPLElBQUksT0FBTyxDQUFDLENBQUMsT0FBTyxFQUFFLE1BQU0sRUFBRSxFQUFFO1lBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLEdBQUcsQ0FBQyxZQUFZLENBQUMsQ0FBQyxNQUFN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