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