UNPKG

wsproxy-ng

Version:

A websocket to tcp proxy, written in node.js, rewritten for performance and readability.

177 lines (154 loc) 4.52 kB
import { connect } from "net"; import { WebSocket } from "ws"; import { IncomingMessage } from "http"; /** * The configuration for the proxy * @typedef {Object} ProxyConfig * @property {boolean} failOnError - Whether to close the connection on errors. */ /** * This class will handle the proxying of tcp over websockets for a given connection */ class SocketProxy { ws; tcp; from; to; config; /** * Constructor for Proxy * * @param {WebSocket} ws * @param {IncomingMessage} req * @param {ProxyConfig} config */ constructor(ws, req, config) { this.ws = ws; this.config = config; this.from = req.socket.remoteAddress; this.to = req.url.substring(1); /** * Special config display mode * This will display the config to the client, without * proxying anything. It can be accessed by using the * destination "!cfg" (e.g. ws://localhost:5999/!cfg) * The reason that the exclamation mark is used is to * prevent any conflicts domain names. */ if (this.to === "!cfg") { let safeConfig = { port: config.port, ssl_enabled: config.ssl, modules: {}, }; // Only show the presence of ssl keys if ssl is enabled // Even if ssl is enabled, don't show the keys themselves if (config.ssl) { safeConfig.ssl_key = "[hidden]"; safeConfig.ssl_cert = "[hidden]"; } // Populate the config display with the modules that are enabled Object.keys(this.config.modules.configs).forEach((module_name) => { let module = this.config.modules.configs[module_name]; if (!module.hide) { safeConfig.modules[module_name] = module.config; } }); this.ws.on("message", () => { this.ws.send(JSON.stringify(safeConfig)); }); return; // Don't do anything else, we're just displaying the config } // Bind data this.ws.on("message", this.clientData.bind(this)); this.ws.on("close", this.close.bind(this)); this.ws.on("error", this.close.bind(this)); // Initialize proxy var args = this.to.split(":"); // Connect to server this.config.logger.info( "Requested connection from '%s' to '%s' [ACCEPTED].", this.from, this.to ); this.tcp = connect(args[1], args[0]); // Disable nagle algorithm this.tcp.setTimeout(0); this.tcp.setNoDelay(true); this.tcp.on("data", this.serverData.bind(this)); this.tcp.on("close", this.close.bind(this)); this.tcp.on("error", function (error) { console.log(error); }); this.tcp.on("connect", this.connectAccept.bind(this)); } /** * OnClientData * Client -> Server */ clientData(data) { if (!this.tcp) { throw new Error( "TCP socket does not exist. Have you initialized the proxy?" ); } try { this.tcp.write(data); } catch (e) { throw new Error("TCP socket write error: " + e); } } /** * OnServerData * Server -> Client */ serverData(data) { if (this.ws.protocol == "binary") this.ws.send(data, function (error) { /* if (error !== null) { OnClose(); } */ }); else this.ws.send(data.toString("base64"), function (error) { /* if (error !== null) { OnClose(); } */ }); } /** * This function is called when the connection is closed. * It will close the tcp and websocket connections, and * clean up the listeners. */ close() { if (this.tcp) { this.config.logger.info("Connection closed from '%s'.", this.to); this.tcp.removeListener("close", this.close.bind(this)); this.tcp.removeListener("error", this.close.bind(this)); this.tcp.removeListener("data", this.serverData.bind(this)); this.tcp.end(); } if (this.ws) { this.config.logger.info("Connection closed from '%s'.", this.from); this.ws.removeListener("close", this.close.bind(this)); this.ws.removeListener("error", this.close.bind(this)); this.ws.removeListener("message", this.clientData.bind(this)); this.ws.close(); } } /** * This function is called when the connection to the server is accepted. */ connectAccept() { this.config.logger.status("Connection accepted from '%s'.", this.to); } } /** * Export SocketProxy */ export default SocketProxy;