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();