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
text/typescript
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}`);
}
}