1
0
mirror of synced 2024-11-15 03:57:36 +01:00
shadowtenpo/ccrelay.js
2022-02-27 14:56:44 -08:00

156 lines
4.5 KiB
JavaScript

const net = require('net');
const dgram = require('dgram');
// Note: src and dst addresses in this file are full IP strings only.
function CCRelay(host) {
console.info("Relay started for " + host);
this.host = host;
this.sockets = {};
this.servers = {};
this.onReceive = function(sport, dport, type, msg) {}
}
CCRelay.prototype.bindUDP = function(port) {
if (this.sockets['udp'+port]) {
console.warn('Attempted to bind more than once on '+this.host+':'+this.port);
return this.sockets['udp'+port];
}
var socket = dgram.createSocket('udp4');
socket.on('error', (e) => {
console.error('udp '+this.host+':'+port+' error', e);
socket.close();
this.sockets['udp'+port] = null;
this.bindUDP(port);
});
socket.on('message', (msg, rinfo) => {
this.onReceive(rinfo.port, port, 'udp', msg);
});
var bindpromise = new Promise((resolve) => {
socket.bind({address: this.host, port: port}, () => {
this.sockets['udp'+port] = socket;
resolve(socket);
});
});
this.sockets['udp'+port] = bindpromise;
return bindpromise;
};
CCRelay.prototype.sendUDP = function(dst, sport, dport, msg) {
var socket = this.sockets['udp'+sport];
if (!socket) {
socket = this.bindUDP(sport);
}
if (socket instanceof Promise) {
return socket.then(() => {
return this.sendUDP(dst, sport, dport, msg);
});
}
return new Promise((resolve) => {
socket.send(msg, dport, dst, resolve);
});
};
CCRelay.prototype._setupTCPSocket = function(socket, gameport, relayport) {
socket.on('data', (msg) => {
this.onReceive(gameport, relayport, 'tcp', msg);
});
socket.on('end', () => {
if (this.sockets['tcp'+gameport+'-'+relayport]) {
console.info('tcp'+gameport+'-'+relayport + ' fin\'ed');
this.onReceive(gameport, relayport, 'tcpfin');
}
});
socket.on('close', () => {
if (this.sockets['tcp'+gameport+'-'+relayport]) {
console.info('tcp'+gameport+'-'+relayport + ' closed');
this.sockets['tcp'+gameport+'-'+relayport] = null;
this.onReceive(gameport, relayport, 'tcpclose');
}
});
socket.on('error', e => {
if (this.sockets['tcp'+gameport+'-'+relayport]) {
console.info('tcp'+gameport+'-'+relayport + 'error', e);
this.sockets['tcp'+gameport+'-'+relayport] = null;
this.onReceive(gameport, relayport, 'tcpclose');
}
});
}
CCRelay.prototype.listenTCP = function(port) {
if (this.servers['tcp'+port]) {
console.warn('Attempted to listen more than once on '+this.host+':'+this.port);
return this.servers['tcp'+port];
}
var server = net.createServer();
server.on('error', (e) => {
console.error('tcpserver '+this.host+':'+port+' error', e);
server.close();
this.servers['tcp'+port] = null;
this.listenTCP(port);
});
server.on('connection', (socket) => {
console.info('tcp'+socket.remotePort+'-'+port + ' created by accept');
this._setupTCPSocket(socket, socket.remotePort, port);
this.sockets['tcp'+socket.remotePort+'-'+port] = socket;
});
var listenpromise = new Promise((resolve) => {
server.listen({host: this.host, port: port}, () => {
this.servers['tcp'+port] = server;
resolve(server);
});
});
this.servers['tcp'+port] = listenpromise;
return listenpromise;
};
CCRelay.prototype.sendTCP = function(dst, sport, dport, msg) {
var socket = this.sockets['tcp'+dport+'-'+sport];
if (!socket) {
socket = new Promise((resolve) => {
console.info('tcp'+dport+'-'+sport + 'created by connect');
var newsocket = new net.Socket();
this._setupTCPSocket(newsocket, dport, sport);
newsocket.connect({
port: dport, host: dst,
localPort: sport, localAddress: this.host
}, () => {
this.sockets['tcp'+dport+'-'+sport] = newsocket;
resolve(newsocket);
});
});
this.sockets['tcp'+dport+'-'+sport] = socket;
}
if (socket instanceof Promise) {
return socket.then(() => {
return this.sendTCP(dst, sport, dport, msg);
});
}
return new Promise((resolve) => {
socket.write(msg, null, resolve);
});
};
CCRelay.prototype.endTCP = function(sport, dport) {
var socket = this.sockets['tcp'+dport+'-'+sport];
if (!socket) {
console.warn('Requested to end nonexistent socket ' + 'tcp'+dport+'-'+sport);
return;
}
console.info('FIN on' + 'tcp'+dport+'-'+sport);
socket.end();
};
CCRelay.prototype.closeTCP = function(sport, dport) {
var socket = this.sockets['tcp'+dport+'-'+sport];
if (!socket) {
console.warn('Requested to close nonexistent socket ' + 'tcp'+dport+'-'+sport);
return;
}
this.sockets['tcp'+dport+'-'+sport] = null;
console.info('Close on' + 'tcp'+dport+'-'+sport);
socket.destroy();
};
module.exports = CCRelay;