UNPKG

ws-to-socket-proxy

Version:

A proxy server which can be used to proxy an web socket connection to tcp or udp

384 lines (316 loc) 13.4 kB
#!/usr/bin/env node import * as dgram from 'dgram'; import * as websocket from 'websocket'; import * as http from 'http'; import * as chalk from 'chalk'; import * as net from 'net'; import WebSocketServer = websocket.server; interface ClientConnection { send: (data: any) => void; remoteAddress: string; port: number; } // chalk instance used for coloring const font = new chalk.constructor({ enabled: true }); // this is the port used by the client to connect to this proxy const wsListeningPort: number = 8778; const verbose:boolean = process.argv.indexOf("-v") != -1; // TCP SERVER START var server = net.createServer().listen(5000, "0.0.0.0", undefined, function () { console.log('server listening to %j', server.address()); log(`${font.blue("TCP Server")} > Server listneing on ${JSON.stringify(server.address())}`); }); server.on('connection', handleConnection); function stringToABuffer(str) { var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char var bufView = new Uint16Array(buf); for (var i = 0, strLen = str.length; i < strLen; i++) { bufView[ i ] = str.charCodeAt(i); } return bufView; } function aBufferToString(buf) { return String.fromCharCode.apply(null, new Uint16Array(buf)); } function handleConnection(conn: net.Socket) { var remoteAddress = conn.remoteAddress + ':' + conn.remotePort; log(`${font.blue("TCP Server")} > New client connection from ${remoteAddress}`); // conn.write(Buffer.from([ 0x62, 0x75, 0x66, 0x66, 0x65, 0x72 ])); if (!serverConnection) { serverConnection = createServerConnectionTCP('192.168.5.205', 5001, { send: conn.write.bind(conn), remoteAddress: conn.remoteAddress, port: conn.remotePort }); } conn.on('data', onConnData); conn.once('close', onConnClose); conn.on('error', onConnError); function onConnData(data) { log(`${font.blue("TCP Server")} > Received data from the client sending to server.`); if (serverConnection) { serverConnection.sendBinary(data); } } function onConnClose() { log(`${font.blue("TCP Server")} > Client ${remoteAddress} socket closed.`); } function onConnError(err) { log(`${font.red("TCP Server")} > Client ${remoteAddress} error: ${err.message}}.`); } } // TCP SERVER END // WS SERVER START // set up http server which is needed by the ws var httpServer = http.createServer((request, response) => { log(`${font.blue("WS httpServer")} > Received request for ${font.green(request.url)}`); response.writeHead(404); response.end(); }); httpServer.on('clientError', (err, socket) => { console.log(`HTTP server error ${err} on ${JSON.stringify(socket)}`); }); httpServer.on('error', (err) => { console.log(`HTTP server error ${err}`); }); httpServer.listen(wsListeningPort, () => { log(`${font.blue("WS httpServer")} > Listening on port ${font.green(wsListeningPort.toString())}`); }); // this is the web socket server instance used to listen for incoming client connections var proxyServer = new WebSocketServer({ httpServer: httpServer, autoAcceptConnections: false }); var serverConnection: SocketWrapper; //The configuration needed so a socket could be opened to the tcp or udp server interface IServerConnectionConfig { type: "tcp" | "udp", ip: string, port: number } // when there is incoming connection this method will be executed proxyServer.on('request', (request) => { var clientConnection = request.accept('echo-protocol', request.origin); log(`${font.blue("WS")} > Connection accepted from ${font.green(request.socket.remoteAddress)}:${font.green(request.socket.remotePort.toString())}`); clientConnection.on('message', (message) => { if (message.type === 'utf8') { // log(`${font.blue("WS")} > Received String from ${font.green(request.socket.remoteAddress)}:${font.green(request.socket.remotePort.toString())} string: "${font.green(message.utf8Data)}"`); // we expect the first message received by the client to be an json file which this format as described by the Config type. type Config = { ip: string, port: number, type: "udp" | "tcp" }; var config: { type: string, ip: string, port: number }; //if a config file has been sent we configure/reconfigure the new connection config = tryParseConfigFile(message.utf8Data); if (config) { if (serverConnection) { serverConnection.close(); } // depending on the type property of the config we are either going to open tcp or udp connection to the requested remote server if (config && config.type == "udp") { // opening an UDP connection serverConnection = createServerConnectionUDP(config.ip, config.port, { send: clientConnection.send.bind(clientConnection), remoteAddress: clientConnection.socket.remoteAddress, port: clientConnection.socket.remotePort }); } else if (config && config.type == "tcp") { // opening an TCP connection serverConnection = createServerConnectionTCP(config.ip, config.port, { send: clientConnection.send.bind(clientConnection), remoteAddress: clientConnection.socket.remoteAddress, port: clientConnection.socket.remotePort }); } else { log(`${font.blue("WS")} > no server connection type ${font.red(config && config.type)}`); } } else { //if a config file has not been sent if (!serverConnection || !serverConnection.isOpened()) { log(`${font.blue("WS")} > ${font.red("There is no server connection that has been configured.")} > Please send a string message with config data with type ${font.yellow("{type: \"udp\" | \"tcp\", ip: string, port: number}")}")}`); } else { serverConnection.sendUTF(message.utf8Data); } } } else if (message.type === 'binary') { // binary messages are redirected to the remote server. // log(`${font.blue("WS")} > Received Binary data from ${font.green(request.socket.remoteAddress)}:${font.green(request.socket.remotePort.toString())} size: ${font.green(message.binaryData.length.toString())}`); if (!serverConnection || !serverConnection.isOpened()) { log(`${font.blue("WS")} > ${font.red("There is no server connection that has been configured.")} > Please send a string message with config data with type ${font.yellow("{type: \"udp\" | \"tcp\", ip: string, port: number}")}")}`); } else { serverConnection.sendBinary(message.binaryData); } } }); clientConnection.on('close', function (reasonCode, description) { log(`${font.blue("WS")} > Peer ${font.green(request.socket.remoteAddress)}:${font.green(request.socket.remotePort.toString())} disconnected`); }); }); function tryParseConfigFile(msg: string): { type: string, ip: string, port: number } { var config = JSON.parse(msg); return config.ip && config.port && config.type ? config : null; } /** * This interface defines an simple object which has an sendBinary method. * An object of this type is returned by the crete udp and tcp functions */ interface SocketWrapper { /** * Is the connection with the socket opened. Use that to check if it is possible to send data through the socket to the tcp|udp server * * @type {boolean} * @memberOf SocketWrapper */ isOpened(): boolean, /** * Sends binary data through the connection * * @param {Buffer} data * * @memberOf SocketWrapper */ sendBinary(data: Buffer): void, /** * Sends string data through the connection * * @param {string} data * * @memberOf SocketWrapper */ sendUTF(data: string): void, /** * Closes the connection between the server and the proxy * * @memberOf SocketWrapper */ close(): void } // WS SERVER END /** * Creates and udp connection and returns and wrapper object for sending data to this connection. * * @param ip the ip of the remote server * @param port the port f the remote * @param wsConnection the WS socket of the connected client. will be used to automatically redirect messages from the udp server to the client. * @returns a wrapper object for sending data to this connection */ function createServerConnectionUDP(ip: string, port: number, clientConnection: ClientConnection): SocketWrapper { log(`${font.blue("Server Connection UDP")} > create ${font.green(ip)}:${font.green(port.toString())}`); var bindAddress = '0.0.0.0'; var udpSocket = dgram.createSocket('udp4'); var socketOpened: boolean = false; try { udpSocket.bind(port, bindAddress, start); } catch (err) { log(`${font.blue("Server Connection UDP")} > Socket bind error ${font.red(err.toString())}`); } // this callback will be called when the binding is done function start() { log(`${font.blue("Server Connection UDP")} > bind on ${font.green(bindAddress)} ${font.green(port.toString())} done`); var broadcast = true; udpSocket.setBroadcast(broadcast) log(`${font.blue("Server Connection UDP")} > broadcast ${font.green(broadcast.toString())}`); } // error handler udpSocket.on('error', (err: Error) => { log(`${font.blue("Server Connection UDP")} > Error ${font.red(err.toString())}`); udpSocket.close(); }); // handles incomming messages form the udp server and redirects them to the websocket client. udpSocket.on('message', (msg, rinfo) => { // log(`${font.blue("Server Connection UDP")} > Message "${font.green(msg.toString())}" from ${font.green(rinfo.address)} ${font.green(rinfo.port.toString())}`); // log(`${font.blue("Server Connection UDP")} > resending to ${font.green(clientConnection.remoteAddress)}:${font.green(clientConnection.port.toString())}`); clientConnection && clientConnection.send(msg); }); udpSocket.on('listening', () => { log(`${font.blue("Server Connection UDP")} > Listening`); socketOpened = true; }); udpSocket.on("close", () => { socketOpened = false; log(`${font.blue("Server Connection UDP")} > Close`); }); // callback called when a message us send to the udp server. function sendCallback(err: Error) { if (err) { log(`${font.blue("Server Connection UDP")} > Send error ${err}`); console.log(err) } } return { isOpened: () => { return socketOpened; }, sendBinary: (data: Buffer) => { log(`${font.blue("Server Connection UDP")} > Send data to ${font.green(ip)}:${font.green(port.toString())} , data length ${font.green(data.length.toString())}`); udpSocket.send(data, port, ip, sendCallback); }, sendUTF: (data: string) => { log(`${font.blue("Server Connection UDP")} > Sending string data to ${font.green(ip)}:${font.green(port.toString())}}`); udpSocket.send(data, port, ip, sendCallback); }, close: function () { udpSocket.close(); } }; } /** * Creates and TCP connection and returns and wrapper object for sending data to this connection. * * @param ip the ip of the remote server * @param port the port f the remote * @param wsConnection the WS socket of the connected client. will be used to automatically redirect messages from the udp server to the client. * @returns a wrapper object for sending data to this connection */ function createServerConnectionTCP(ip: string, port: number, clientConnection: ClientConnection): SocketWrapper { log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > create`); var tcpSocket: net.Socket = new net.Socket(); tcpSocket.connect(port, ip, start); // callback executed on successful connect function start() { log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > connected`); } // error handler tcpSocket.on('error', (err: Error) => { log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > error ${font.red(err.toString())}`); }); // handler for incoming server data. It will be redirected to the ws client tcpSocket.on('data', (data: Buffer) => { // log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > data received, length ${font.green(data.length.toString())} ${data}`); // log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > resending to ${font.green(clientConnection.remoteAddress)}:${font.green(clientConnection.port.toString())}`); clientConnection && clientConnection.send(data); }); // connection close handler tcpSocket.on("close", (hadError: boolean) => { let color = hadError ? font.red : font.green; log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > closed, because of an error: ${color(hadError.toString())}`); }); return { isOpened: () => { return tcpSocket.writable; }, sendBinary: (data: Buffer) => { // log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > Send data, data length ${font.green(data.length.toString())}`); tcpSocket.write(data); }, sendUTF: (data: string) => { log(`${font.blue("Server Connection TCP")} > ${font.green(ip)}:${font.green(port.toString())} > Sending string data }`); tcpSocket.write(data, 'UTF8'); }, close: () => { tcpSocket.end(); } }; } /** * Utility function for logging messages in the console. * Will write each message with a timestamp in front will have this format [HH:MM:SS] : msg * * @param msg the message to be displayed */ function log(msg: string): void { if (verbose){ console.log(`[${font.gray(new Date().toTimeString().substr(0, 8))}] : ${msg}`); } }