| |
| |
| |
| |
| |
| |
| |
| |
|
|
| "use strict";
|
|
|
| const dns = require("dns");
|
| const https = require("https");
|
| const fs = require("fs");
|
|
|
|
|
| let preResolved = {};
|
| try {
|
| const raw = fs.readFileSync("/tmp/dns-resolved.json", "utf8");
|
| preResolved = JSON.parse(raw);
|
| const count = Object.keys(preResolved).length;
|
| if (count > 0) {
|
| console.log(`[dns-fix] Loaded ${count} pre-resolved domains`);
|
| }
|
| } catch {
|
|
|
| }
|
|
|
|
|
| const runtimeCache = new Map();
|
|
|
|
|
| function dohResolve(hostname, callback) {
|
|
|
| const cached = runtimeCache.get(hostname);
|
| if (cached && cached.expiry > Date.now()) {
|
| return callback(null, cached.ip);
|
| }
|
|
|
| const url = `https://1.1.1.1/dns-query?name=${encodeURIComponent(hostname)}&type=A`;
|
| const req = https.get(
|
| url,
|
| { headers: { Accept: "application/dns-json" }, timeout: 15000 },
|
| (res) => {
|
| let body = "";
|
| res.on("data", (c) => (body += c));
|
| res.on("end", () => {
|
| try {
|
| const data = JSON.parse(body);
|
| const aRecords = (data.Answer || []).filter((a) => a.type === 1);
|
| if (aRecords.length === 0) {
|
| return callback(new Error(`DoH: no A record for ${hostname}`));
|
| }
|
| const ip = aRecords[0].data;
|
| const ttl = Math.max((aRecords[0].TTL || 300) * 1000, 60000);
|
| runtimeCache.set(hostname, { ip, expiry: Date.now() + ttl });
|
| callback(null, ip);
|
| } catch (e) {
|
| callback(new Error(`DoH parse error: ${e.message}`));
|
| }
|
| });
|
| }
|
| );
|
| req.on("error", (e) => callback(new Error(`DoH request failed: ${e.message}`)));
|
| req.on("timeout", () => {
|
| req.destroy();
|
| callback(new Error("DoH request timed out"));
|
| });
|
| }
|
|
|
|
|
| const origLookup = dns.lookup;
|
|
|
| dns.lookup = function patchedLookup(hostname, options, callback) {
|
|
|
| if (typeof options === "function") {
|
| callback = options;
|
| options = {};
|
| }
|
| if (typeof options === "number") {
|
| options = { family: options };
|
| }
|
| options = options || {};
|
|
|
|
|
| if (
|
| !hostname ||
|
| hostname === "localhost" ||
|
| hostname === "0.0.0.0" ||
|
| hostname === "127.0.0.1" ||
|
| hostname === "::1" ||
|
| /^\d+\.\d+\.\d+\.\d+$/.test(hostname) ||
|
| /^::/.test(hostname)
|
| ) {
|
| return origLookup.call(dns, hostname, options, callback);
|
| }
|
|
|
|
|
| if (preResolved[hostname]) {
|
| const ip = preResolved[hostname];
|
| if (options.all) {
|
| return process.nextTick(() => callback(null, [{ address: ip, family: 4 }]));
|
| }
|
| return process.nextTick(() => callback(null, ip, 4));
|
| }
|
|
|
|
|
| origLookup.call(dns, hostname, options, (err, address, family) => {
|
| if (!err && address) {
|
| return callback(null, address, family);
|
| }
|
|
|
|
|
| if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
|
| dohResolve(hostname, (dohErr, ip) => {
|
| if (dohErr || !ip) {
|
| return callback(err);
|
| }
|
| if (options.all) {
|
| return callback(null, [{ address: ip, family: 4 }]);
|
| }
|
| callback(null, ip, 4);
|
| });
|
| } else {
|
|
|
| callback(err, address, family);
|
| }
|
| });
|
| };
|
|
|