somratpro commited on
Commit
cd25f84
·
1 Parent(s): 2543611

chore: align networking configuration with HuggingClaw

Browse files
Files changed (2) hide show
  1. Dockerfile +1 -1
  2. dns-fix.js +93 -110
Dockerfile CHANGED
@@ -32,7 +32,7 @@ COPY --chown=node:node health-server.js /home/node/app/health-server.js
32
  COPY --chown=node:node dns-fix.js /opt/dns-fix.js
33
 
34
  # Set NODE_OPTIONS after dns-fix.js is copied so it doesn't break npm install during build
35
- ENV NODE_OPTIONS="--dns-result-order=ipv4first --require /opt/dns-fix.js"
36
  COPY --chown=node:node n8n-sync.py /home/node/app/n8n-sync.py
37
  COPY --chown=node:node setup-uptimerobot.sh /home/node/app/setup-uptimerobot.sh
38
  COPY --chown=node:node start.sh /home/node/app/start.sh
 
32
  COPY --chown=node:node dns-fix.js /opt/dns-fix.js
33
 
34
  # Set NODE_OPTIONS after dns-fix.js is copied so it doesn't break npm install during build
35
+ ENV NODE_OPTIONS="--require /opt/dns-fix.js"
36
  COPY --chown=node:node n8n-sync.py /home/node/app/n8n-sync.py
37
  COPY --chown=node:node setup-uptimerobot.sh /home/node/app/setup-uptimerobot.sh
38
  COPY --chown=node:node start.sh /home/node/app/start.sh
dns-fix.js CHANGED
@@ -1,125 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
1
  "use strict";
2
- console.error("[DNS-FIX] Loaded — DoH resolver + tls/net logging active.");
3
- const http = require("http");
4
- const https = require("https");
5
- const { PassThrough } = require("stream");
6
 
7
- const blockedDomains = ["api.telegram.org", "discord.com", "discordapp.com", "web.whatsapp.com"];
 
8
 
9
- function isBlocked(hostname) {
10
- return hostname && blockedDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`));
11
- }
12
 
13
- // Monkey-patch http.request and https.request
14
- const _origHttpRequest = http.request;
15
- const _origHttpsRequest = https.request;
 
 
 
 
16
 
17
- // Robust fetch-like wrapper using original (unpatched) http/https modules
18
- function originalRequest(protocol, url, options, body) {
19
- const parsedUrl = new URL(url);
20
- const origFn = protocol === "https:" ? _origHttpsRequest : _origHttpRequest;
21
-
22
- return new Promise((resolve, reject) => {
23
- const req = origFn(parsedUrl, options, (res) => {
24
- resolve(res);
25
- });
26
- req.on("error", reject);
27
- if (body) req.write(body);
28
- req.end();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  });
30
  }
31
 
32
- function transparentProxyRequest(protocol, origFn, ...args) {
33
- let options = args[0];
34
- let callback = args[1];
35
 
36
- if (typeof options === "string") {
37
- try {
38
- options = new URL(options);
39
- } catch {
40
- return origFn.apply(this, args);
41
- }
42
- } else if (options instanceof URL) {
43
- // Already a URL
44
- } else {
45
- // Object options
46
- options = { ...options };
47
  }
48
-
49
- // RECURSION GUARD: Check for a custom property we'll set
50
- if (options && options.__isProxyRequest) {
51
- return origFn.apply(this, args);
52
  }
53
-
54
- if (typeof callback !== "function" && typeof args[2] === "function") {
55
- callback = args[2];
56
- }
57
-
58
- const hostname = options.hostname || options.host;
59
- const path = options.path || options.pathname || "/";
60
-
61
- if (isBlocked(hostname)) {
62
- console.error(`[DNS-FIX] Transparently proxying ${protocol}//${hostname}${path}`);
63
-
64
- const requestUrl = `${protocol}//${hostname}${path}`;
65
- const requestHeaders = { ...options.headers };
66
-
67
- const reqStream = new PassThrough();
68
-
69
- let requestBody = Buffer.alloc(0);
70
- reqStream.on("data", (chunk) => {
71
- requestBody = Buffer.concat([requestBody, chunk]);
72
- });
73
-
74
- reqStream.on("finish", async () => {
75
- try {
76
- const fetchRes = await originalRequest(protocol, requestUrl, {
77
- method: options.method || "GET",
78
- headers: requestHeaders,
79
- __isProxyRequest: true, // Internal flag to prevent recursion
80
- }, options.method !== "GET" && options.method !== "HEAD" ? requestBody : undefined);
81
-
82
- if (callback) callback(fetchRes);
83
- reqStream.emit("response", fetchRes);
84
- // fetchRes is already an IncomingMessage (readable stream)
85
- } catch (err) {
86
- console.error(`[DNS-FIX] Proxy error for ${requestUrl}: ${err.message}`);
87
- reqStream.emit("error", err);
88
- }
89
- });
90
-
91
- // Mock ClientRequest methods
92
- reqStream.abort = () => reqStream.destroy();
93
- reqStream.end = (chunk) => {
94
- if (chunk) reqStream.write(chunk);
95
- reqStream.end();
96
- };
97
- reqStream.setTimeout = (ms, cb) => { if (cb) setTimeout(cb, ms); return reqStream; };
98
- reqStream.setNoDelay = () => reqStream;
99
- reqStream.setSocketKeepAlive = () => reqStream;
100
-
101
- return reqStream;
102
  }
103
 
104
- return origFn.apply(this, args);
105
- }
106
-
107
- http.request = function (...args) {
108
- return transparentProxyRequest("http:", _origHttpRequest, ...args);
109
- };
110
-
111
- https.request = function (...args) {
112
- return transparentProxyRequest("https:", _origHttpsRequest, ...args);
113
- };
114
-
115
- http.get = function (...args) {
116
- const req = http.request(...args);
117
- req.end();
118
- return req;
119
- };
120
 
121
- https.get = function (...args) {
122
- const req = https.request(...args);
123
- req.end();
124
- return req;
 
 
 
 
 
 
 
 
 
 
 
 
125
  };
 
1
+ /**
2
+ * DNS fix preload script for HF Spaces.
3
+ *
4
+ * Patches Node.js dns.lookup to:
5
+ * 1. Try system DNS first
6
+ * 2. Fall back to DNS-over-HTTPS (Cloudflare) if system DNS fails
7
+ * (This is needed because HF Spaces intercepts/blocks some domains like
8
+ * WhatsApp web or Telegram API via standard UDP DNS).
9
+ *
10
+ * Loaded via: NODE_OPTIONS="--require /opt/dns-fix.js"
11
+ */
12
  "use strict";
 
 
 
 
13
 
14
+ const dns = require("dns");
15
+ const https = require("https");
16
 
17
+ // In-memory cache for runtime DoH resolutions
18
+ const runtimeCache = new Map(); // hostname -> { ip, expiry }
 
19
 
20
+ // DNS-over-HTTPS resolver
21
+ function dohResolve(hostname, callback) {
22
+ // Check runtime cache
23
+ const cached = runtimeCache.get(hostname);
24
+ if (cached && cached.expiry > Date.now()) {
25
+ return callback(null, cached.ip);
26
+ }
27
 
28
+ const url = `https://1.1.1.1/dns-query?name=${encodeURIComponent(hostname)}&type=A`;
29
+ const req = https.get(
30
+ url,
31
+ { headers: { Accept: "application/dns-json" }, timeout: 15000 },
32
+ (res) => {
33
+ let body = "";
34
+ res.on("data", (c) => (body += c));
35
+ res.on("end", () => {
36
+ try {
37
+ const data = JSON.parse(body);
38
+ const aRecords = (data.Answer || []).filter((a) => a.type === 1);
39
+ if (aRecords.length === 0) {
40
+ return callback(new Error(`DoH: no A record for ${hostname}`));
41
+ }
42
+ const ip = aRecords[0].data;
43
+ const ttl = Math.max((aRecords[0].TTL || 300) * 1000, 60000);
44
+ runtimeCache.set(hostname, { ip, expiry: Date.now() + ttl });
45
+ callback(null, ip);
46
+ } catch (e) {
47
+ callback(new Error(`DoH parse error: ${e.message}`));
48
+ }
49
+ });
50
+ }
51
+ );
52
+ req.on("error", (e) => callback(new Error(`DoH request failed: ${e.message}`)));
53
+ req.on("timeout", () => {
54
+ req.destroy();
55
+ callback(new Error("DoH request timed out"));
56
  });
57
  }
58
 
59
+ // Monkey-patch dns.lookup
60
+ const origLookup = dns.lookup;
 
61
 
62
+ dns.lookup = function patchedLookup(hostname, options, callback) {
63
+ // Normalize arguments (options is optional, can be number or object)
64
+ if (typeof options === "function") {
65
+ callback = options;
66
+ options = {};
 
 
 
 
 
 
67
  }
68
+ if (typeof options === "number") {
69
+ options = { family: options };
 
 
70
  }
71
+ options = options || {};
72
+
73
+ // Skip patching for localhost, IPs, and internal domains
74
+ if (
75
+ !hostname ||
76
+ hostname === "localhost" ||
77
+ hostname === "0.0.0.0" ||
78
+ hostname === "127.0.0.1" ||
79
+ hostname === "::1" ||
80
+ /^\d+\.\d+\.\d+\.\d+$/.test(hostname) ||
81
+ /^::/.test(hostname)
82
+ ) {
83
+ return origLookup.call(dns, hostname, options, callback);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  }
85
 
86
+ // 1) Try system DNS first
87
+ origLookup.call(dns, hostname, options, (err, address, family) => {
88
+ if (!err && address) {
89
+ return callback(null, address, family);
90
+ }
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ // 2) System DNS failed with ENOTFOUND or EAI_AGAIN — fall back to DoH
93
+ if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
94
+ dohResolve(hostname, (dohErr, ip) => {
95
+ if (dohErr || !ip) {
96
+ return callback(err); // Return original error
97
+ }
98
+ if (options.all) {
99
+ return callback(null, [{ address: ip, family: 4 }]);
100
+ }
101
+ callback(null, ip, 4);
102
+ });
103
+ } else {
104
+ // Other DNS errors — pass through
105
+ callback(err, address, family);
106
+ }
107
+ });
108
  };