99 lines
2.9 KiB
JavaScript
99 lines
2.9 KiB
JavaScript
'use strict';
|
|
|
|
import assert from 'assert';
|
|
import { randint, inet_ntop, inet_pton } from './util.js';
|
|
import { randomBytes } from 'crypto';
|
|
|
|
function rand_vpn_code() {
|
|
return randomBytes(6).toString("hex").toUpperCase();
|
|
}
|
|
|
|
const vpns = new Map();
|
|
|
|
class VPN {
|
|
constructor() {
|
|
this.serverCode = rand_vpn_code();
|
|
this.clientCode = rand_vpn_code();
|
|
this.game = null; // not tracked
|
|
this.targets = new Map();
|
|
vpns.set(this.serverCode, this);
|
|
vpns.set(this.clientCode, this);
|
|
}
|
|
|
|
route(ip, port) {
|
|
let addr = `${ip}:${port}`;
|
|
if (this.targets.has(addr)) {
|
|
return this.targets.get(addr);
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export function vpn_make(game) {
|
|
const vpn = new VPN();
|
|
return [vpn.serverCode, vpn.clientCode];
|
|
}
|
|
|
|
export function vpn_connect(client, code, bindport) {
|
|
if (!vpns.has(code)) return null;
|
|
const vpn = vpns.get(code);
|
|
return new VPNTarget(vpn, client, code, bindport);
|
|
}
|
|
|
|
class VPNTarget {
|
|
constructor(vpn, client, code, bindport) {
|
|
this.vpn = vpn;
|
|
this.client = client;
|
|
this.bindport = bindport;
|
|
if (code == vpn.serverCode) {
|
|
this.ip = '172.16.0.1';
|
|
} else if (code == vpn.clientCode) {
|
|
const b = randint(16, 32);
|
|
const c = randint(1, 254);
|
|
const d = randint(1, 254);
|
|
this.ip = `172.${b}.${c}.${d}`;
|
|
} else {
|
|
throw new Error('Invalid code');
|
|
}
|
|
this.addr = `${this.ip}:${this.bindport}`;
|
|
vpn.targets.set(this.addr, this);
|
|
client.log(`VPN connect to ${this.addr}`);
|
|
}
|
|
|
|
// Forward a message from the client
|
|
forward(data) {
|
|
// Data is encapsulated with a 12 byte header.
|
|
// Magic - 4 bytes 0x778B4CF3
|
|
// Dest IP - 4 bytes 0xAABBCCDD for AA.BB.CC.DD
|
|
// Dest Port - 2 bytes
|
|
// Packet Len - 2 bytes
|
|
const EP_MAGIC = 0x778B4CF3;
|
|
assert(data instanceof ArrayBuffer);
|
|
const view = new DataView(data);
|
|
assert(data.byteLength >= 12);
|
|
assert(view.getUint32(0) == EP_MAGIC);
|
|
const dest_ip = inet_ntop(data.slice(4, 8));
|
|
const dest_port = view.getUint16(8);
|
|
const pktlen = view.getUint16(10);
|
|
assert(data.byteLength == 12 + pktlen);
|
|
const remote = this.vpn.route(dest_ip, dest_port);
|
|
if (!remote) {
|
|
// Packet is dropped
|
|
this.client.log(`${this.addr} -> ${dest_ip}:${dest_port} (dropped)`);
|
|
return;
|
|
} else {
|
|
this.client.log(`${this.addr} -> ${remote.addr}`);
|
|
}
|
|
|
|
// Rewrite the header to contain source ip/port
|
|
(new Uint8Array(data, 4, 4)).set(new Uint8Array(inet_pton(this.ip)));
|
|
view.setUint16(8, this.bindport);
|
|
remote.client.send(data);
|
|
}
|
|
|
|
close() {
|
|
this.vpn.targets.delete(this.addr);
|
|
this.client.close();
|
|
}
|
|
}
|