48 lines
2.4 KiB
JavaScript
48 lines
2.4 KiB
JavaScript
import http from "node:http";
|
|
import net from "node:net";
|
|
import { lookup } from "node:dns/promises";
|
|
import { pathToFileURL } from "node:url";
|
|
export function publicIPv4(ip) {
|
|
if (net.isIP(ip) !== 4) return false;
|
|
const [a,b,c] = ip.split(".").map(Number);
|
|
return !(a === 0 || a === 10 || a === 127 || a >= 224 ||
|
|
(a === 100 && b >= 64 && b <= 127) || (a === 169 && b === 254) ||
|
|
(a === 172 && b >= 16 && b <= 31) || (a === 192 && (b === 168 || b === 0 || (b === 88 && c === 99))) ||
|
|
(a === 198 && (b === 18 || b === 19 || (b === 51 && c === 100))) || (a === 203 && b === 0 && c === 113));
|
|
}
|
|
export async function targetFor(authority, hosts, resolve = lookup) {
|
|
const match = /^([a-z0-9.-]+):443$/i.exec(authority ?? "");
|
|
if (!match || !hosts.has(match[1].toLowerCase()) || net.isIP(match[1])) throw new Error("HOST_DENIED");
|
|
const addresses = await resolve(match[1], { family: 4, all: true });
|
|
if (!addresses.length || addresses.some(a => !publicIPv4(a.address))) throw new Error("ADDRESS_DENIED");
|
|
return addresses[0].address; // Connect to the checked address, not a second DNS lookup.
|
|
}
|
|
export function startProxy() {
|
|
const hosts = new Set((process.env.EGRESS_HOSTS ?? "").split(",").map(s => s.trim().toLowerCase()).filter(Boolean));
|
|
const server = http.createServer((_req,res) => res.writeHead(403).end());
|
|
server.maxConnections = 64;
|
|
server.headersTimeout = 10000;
|
|
server.on("connect", async (req, client, head) => {
|
|
client.on("error", () => client.destroy());
|
|
client.setTimeout(120000, () => client.destroy());
|
|
let upstream;
|
|
try {
|
|
const ip = await targetFor(req.url, hosts);
|
|
if (client.destroyed) return;
|
|
upstream = net.connect({ host: ip, port: 443 });
|
|
upstream.setTimeout(120000, () => upstream.destroy());
|
|
upstream.on("error", () => client.destroy());
|
|
client.on("close", () => upstream.destroy());
|
|
upstream.on("close", () => client.destroy());
|
|
upstream.once("connect", () => {
|
|
client.write("HTTP/1.1 200 Connection Established\r\n\r\n");
|
|
if (head.length) upstream.write(head);
|
|
client.pipe(upstream); upstream.pipe(client);
|
|
});
|
|
} catch { upstream?.destroy(); client.end("HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n"); }
|
|
});
|
|
server.listen(3128, "0.0.0.0");
|
|
return server;
|
|
}
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) startProxy();
|