Spaces:
Runtime error
Runtime error
File size: 4,027 Bytes
e7ab5f1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | /**
* DNS fix preload script for HF Spaces.
*
* Patches Node.js dns.lookup to:
* 1. Check pre-resolved domains from /tmp/dns-resolved.json (populated by dns-resolve.py)
* 2. Fall back to DNS-over-HTTPS (Cloudflare) for any other unresolvable domain
*
* Loaded via: NODE_OPTIONS="--require /path/to/dns-fix.cjs"
*/
"use strict";
const dns = require("dns");
const https = require("https");
const fs = require("fs");
// ββ Pre-resolved domains (populated by entrypoint.sh via dns-resolve.py) ββ
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 {
// File not found or parse error β proceed without pre-resolved cache
}
// ββ In-memory cache for runtime DoH resolutions ββ
const runtimeCache = new Map(); // hostname -> { ip, expiry }
// ββ DNS-over-HTTPS resolver ββ
function dohResolve(hostname, callback) {
// Check runtime cache
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"));
});
}
// ββ Monkey-patch dns.lookup ββ
const origLookup = dns.lookup;
dns.lookup = function patchedLookup(hostname, options, callback) {
// Normalize arguments (options is optional, can be number or object)
if (typeof options === "function") {
callback = options;
options = {};
}
if (typeof options === "number") {
options = { family: options };
}
options = options || {};
// Skip patching for localhost, IPs, and internal domains
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);
}
// 1) Check pre-resolved cache
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));
}
// 2) Try system DNS
origLookup.call(dns, hostname, options, (err, address, family) => {
if (!err && address) {
return callback(null, address, family);
}
// 3) System DNS failed with ENOTFOUND β fall back to DoH
if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
dohResolve(hostname, (dohErr, ip) => {
if (dohErr || !ip) {
return callback(err); // Return original error
}
if (options.all) {
return callback(null, [{ address: ip, family: 4 }]);
}
callback(null, ip, 4);
});
} else {
// Other DNS errors β pass through
callback(err, address, family);
}
});
};
|