File size: 3,675 Bytes
cd25f84 bf816b7 b6bb093 cd25f84 bf816b7 cd25f84 bf816b7 cd25f84 556dbe3 83aef87 96b5930 83aef87 cd25f84 83aef87 96b5930 cd25f84 2543611 6851577 cd25f84 5c33724 556dbe3 cd25f84 5c33724 cd25f84 94c2797 cd25f84 2543611 cd25f84 5c33724 cd25f84 556dbe3 cd25f84 556dbe3 5c33724 cd25f84 5c33724 cd25f84 5c33724 cd25f84 bf816b7 | 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 | /**
* DNS fix preload script for HF Spaces.
*
* Patches Node.js dns.lookup to:
* 1. Try system DNS first
* 2. Fall back to DNS-over-HTTPS (Cloudflare) if system DNS fails
* (This is needed because HF Spaces intercepts/blocks some domains like
* WhatsApp web or Telegram API via standard UDP DNS).
*
* Loaded via: NODE_OPTIONS="--require /opt/dns-fix.js"
*/
"use strict";
const dns = require("dns");
const https = require("https");
// 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);
}
// Use Cloudflare DNS-over-HTTPS via direct IP to avoid DNS lookup for the resolver itself
const options = {
hostname: "1.1.1.1",
port: 443,
path: `/dns-query?name=${encodeURIComponent(hostname)}&type=A`,
method: "GET",
headers: { Accept: "application/dns-json" },
timeout: 10000,
servername: "cloudflare-dns.com", // Set SNI
};
const req = https.get(options, (res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => {
try {
if (res.statusCode !== 200) {
return callback(
new Error(`DoH: server returned status ${res.statusCode}`),
);
}
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;
let isResolving = false;
dns.lookup = function patchedLookup(hostname, options, callback) {
// Normalize arguments
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) ||
isResolving // RECURSION GUARD
) {
return origLookup.call(dns, hostname, options, callback);
}
// 1) Try system DNS first
origLookup.call(dns, hostname, options, (err, address, family) => {
if (!err && address) {
return callback(null, address, family);
}
// 2) System DNS failed — fall back to DoH
if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
isResolving = true; // Enter guard
dohResolve(hostname, (dohErr, ip) => {
isResolving = false; // Exit guard
if (dohErr || !ip) {
return callback(err); // Return original error
}
if (options.all) {
return callback(null, [{ address: ip, family: 4 }]);
}
callback(null, ip, 4);
});
} else {
callback(err, address, family);
}
});
};
|