156 lines
4.5 KiB
JavaScript
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;
|