118 lines
3.7 KiB
JavaScript
118 lines
3.7 KiB
JavaScript
'use strict';
|
|
|
|
import assert from 'assert';
|
|
import { sanitize } from './util.js';
|
|
import { DIRECT_PROXY } from './settings.js';
|
|
|
|
const textEncoder = new TextEncoder();
|
|
const textDecoder = new TextDecoder();
|
|
const CONNECTION_ESTABLISHED_REPLY = textEncoder.encode('HTTP/1.0 200 Connection Established\r\nProxy-agent: Apache/2.4.41 (Ubuntu)\r\n\r\n');
|
|
|
|
// Not sure what this does, but it seems irrelevant.
|
|
const GEOIP_RESPONSE = `HTTP/1.1 200 OK
|
|
Server: nginx/1.24.0
|
|
Date: %NOW%
|
|
Content-Type: application/json
|
|
Content-Length: 19
|
|
Connection: keep-alive
|
|
Cache-Control: max-age=604800, private
|
|
Access-Control-Allow-Origin: *
|
|
|
|
{"continent":"NA"}
|
|
`;
|
|
|
|
const LIST_RESPONSE = `HTTP/1.1 200 OK
|
|
Server: nginx/1.24.0
|
|
Date: %NOW%
|
|
Content-Type: application/json
|
|
Content-Length: %LENGTH%
|
|
Last-Modified: %NOW%
|
|
Connection: keep-alive
|
|
Access-Control-Allow-Origin: *
|
|
|
|
%PAYLOAD%
|
|
`;
|
|
|
|
const PAYLOAD = {
|
|
'total': { 'servers': DIRECT_PROXY.length, 'clients': 0 },
|
|
'total_max': { 'server': DIRECT_PROXY.length, 'clients': 0 },
|
|
'list': DIRECT_PROXY.map(([vip, ip, port]) => {
|
|
return {
|
|
'address': vip,
|
|
'ip': vip,
|
|
'port': port,
|
|
'proto_min': 37,
|
|
'proto_max': 42,
|
|
}}),
|
|
};
|
|
|
|
// Fake a CONNECT proxy to simulate servers.minetest.net response
|
|
export class ConnectProxy {
|
|
constructor(client) {
|
|
this.client = client;
|
|
this.firstLine = true;
|
|
this.conn = null;
|
|
}
|
|
|
|
forward(data) {
|
|
if (this.firstLine) {
|
|
this.firstLine = false;
|
|
this.handle_handshake(data);
|
|
return;
|
|
}
|
|
if (!(data instanceof ArrayBuffer)) {
|
|
throw new Error("ConnectProxy received non-binary messages");
|
|
}
|
|
data = textDecoder.decode(data);
|
|
assert(data.endsWith('\r\n\r\n'));
|
|
let lines = data.split('\r\n');
|
|
assert(lines.length >= 1);
|
|
let tokens = lines[0].split(' ');
|
|
assert(tokens[0] == 'GET');
|
|
let url = sanitize(tokens[1]);
|
|
const now = (new Date()).toUTCString();
|
|
let response;
|
|
if (url.startsWith('/geoip')) {
|
|
response = GEOIP_RESPONSE.replace(/%NOW%/g, now);
|
|
} else if (url.startsWith('/list')) {
|
|
const payload = JSON.stringify(PAYLOAD);
|
|
response = LIST_RESPONSE.replace(/%NOW%/g, now).replace('%LENGTH%', payload.length + 1).replace('%PAYLOAD%', payload);
|
|
this.client.log("Sending virtual server list")
|
|
} else {
|
|
this.client.log(`Invalid GET request for ${url}`);
|
|
this.client.close();
|
|
return;
|
|
}
|
|
this.client.send(textEncoder.encode(response));
|
|
}
|
|
|
|
handle_handshake(data) {
|
|
// The CONNECT line and it's headers could be split among several packets.
|
|
// In a real server, this would aggregate data until it sees \r\n\r\n
|
|
// But minetest-wasm always sends it as one packet, so just assume that.
|
|
data = textDecoder.decode(data);
|
|
assert(data.endsWith('\r\n\r\n'));
|
|
let lines = data.split('\r\n');
|
|
assert(lines.length >= 1);
|
|
let tokens = lines[0].split(' ');
|
|
assert.strictEqual(tokens.length, 3);
|
|
assert.strictEqual(tokens[0], 'CONNECT');
|
|
assert.strictEqual(tokens[2], 'HTTP/1.1');
|
|
let host_port = tokens[1].split(':');
|
|
assert.strictEqual(host_port.length, 2);
|
|
let host = host_port[0];
|
|
let port = parseInt(host_port[1]);
|
|
if (host != 'servers.minetest.net' || port != 80) {
|
|
this.client.log(`Ignoring request to proxy to ${host}:${port}`);
|
|
this.client.close();
|
|
return;
|
|
}
|
|
this.client.log('Connected for server list');
|
|
this.client.send(CONNECTION_ESTABLISHED_REPLY);
|
|
}
|
|
|
|
close() {
|
|
this.client.close();
|
|
}
|
|
}
|