somratpro commited on
Commit
556dbe3
·
1 Parent(s): 94c2797

feat: implement transparent application-level proxy via fetch for blocked domains

Browse files
Files changed (1) hide show
  1. dns-fix.js +112 -104
dns-fix.js CHANGED
@@ -1,121 +1,129 @@
1
  "use strict";
2
  console.error("[DNS-FIX] Loaded — DoH resolver + tls/net logging active.");
3
- const dns = require("dns");
4
  const https = require("https");
5
- const tls = require("tls");
6
- const net = require("net");
7
-
8
- const _origTlsConnect = tls.connect;
9
- tls.connect = function (...args) {
10
- let options = {};
11
- if (typeof args[0] === 'object') {
12
- options = args[0];
13
- } else if (typeof args[1] === 'object') {
14
- options = args[1];
15
- }
16
- const host = options.host || options.servername;
17
- console.error(`[DNS-FIX] tls.connect -> host: ${host}, ip: ${options.host || 'unknown'}, port: ${options.port}`);
18
- const socket = _origTlsConnect.apply(this, args);
19
- socket.on('secureConnect', () => console.error(`[DNS-FIX] TLS connected ✓ ${host}`));
20
- socket.on('error', err => console.error(`[DNS-FIX] TLS error ✗ ${host} - ${err.code}: ${err.message}`));
21
- return socket;
22
- };
23
 
24
- const _origNetConnect = net.connect;
25
- net.connect = function (...args) {
26
- let options = {};
27
- if (typeof args[0] === 'object') options = args[0];
28
- console.error(`[DNS-FIX] net.connect -> host: ${options.host}, port: ${options.port}`);
29
- return _origNetConnect.apply(this, args);
30
- };
31
 
32
- const runtimeCache = new Map();
33
- function dohResolve(hostname, callback) {
34
- const cached = runtimeCache.get(hostname);
35
- if (cached && cached.expiry > Date.now()) return callback(null, cached.ip);
36
- const url = `https://1.1.1.1/dns-query?name=${encodeURIComponent(hostname)}&type=A`;
37
- const req = https.get(url, { headers: { Accept: "application/dns-json" }, timeout: 15000 }, (res) => {
38
- let body = "";
39
- res.on("data", (c) => (body += c));
40
- res.on("end", () => {
41
- try {
42
- const data = JSON.parse(body);
43
- const aRecords = (data.Answer || []).filter((a) => a.type === 1);
44
- if (aRecords.length === 0) return callback(new Error(`DoH: no A record for ${hostname}`));
45
- const ip = aRecords[0].data;
46
- const ttl = Math.max((aRecords[0].TTL || 300) * 1000, 60000);
47
- runtimeCache.set(hostname, { ip, expiry: Date.now() + ttl });
48
- callback(null, ip);
49
- } catch (e) {
50
- callback(new Error(`DoH parse error: ${e.message}`));
51
- }
52
- });
53
- });
54
- req.on("error", (e) => callback(new Error(`DoH request failed: ${e.message}`)));
55
- req.on("timeout", () => { req.destroy(); callback(new Error("DoH request timed out")); });
56
  }
57
 
58
- const origLookup = dns.lookup;
59
- dns.lookup = function patchedLookup(hostname, options, callback) {
60
- if (typeof options === "function") {
61
- callback = options;
62
- options = {};
63
- }
64
- if (typeof options === "number") {
65
- options = { family: options };
 
 
 
 
 
 
 
 
 
 
 
66
  }
67
- options = options || {};
68
-
69
- if (
70
- !hostname ||
71
- hostname === "localhost" ||
72
- hostname === "0.0.0.0" ||
73
- hostname === "127.0.0.1" ||
74
- hostname === "::1" ||
75
- /^\d+\.\d+\.\d+\.\d+$/.test(hostname) ||
76
- /^::/.test(hostname)
77
- ) {
78
- return origLookup.call(dns, hostname, options, callback);
79
  }
80
 
81
- // FORCE DoH for known blocked domains on HF Spaces
82
- const blockedDomains = ["api.telegram.org", "discord.com", "discordapp.com"];
83
- const isBlocked = blockedDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`));
84
 
85
- if (isBlocked) {
86
- console.error(`[DNS-FIX] Forcing DoH bypass for blocked domain: ${hostname}`);
87
- return dohResolve(hostname, (dohErr, ip) => {
88
- if (dohErr || !ip) {
89
- return origLookup.call(dns, hostname, options, callback); // Last resort fallback
90
- }
91
- if (options.all) {
92
- return callback(null, [{ address: ip, family: 4 }]);
93
- }
94
- callback(null, ip, 4);
 
 
 
 
95
  });
96
- }
97
 
98
- // 1) Try system DNS first for everything else
99
- origLookup.call(dns, hostname, options, (err, address, family) => {
100
- if (!err && address) {
101
- return callback(null, address, family);
102
- }
 
 
 
103
 
104
- // 2) System DNS failed with ENOTFOUND or EAI_AGAIN — fall back to DoH
105
- if (err && (err.code === "ENOTFOUND" || err.code === "EAI_AGAIN")) {
106
- console.error(`[DNS-FIX] Fallback DoH triggered for ${hostname}`);
107
- dohResolve(hostname, (dohErr, ip) => {
108
- if (dohErr || !ip) {
109
- return callback(err); // Return original error
 
110
  }
111
- if (options.all) {
112
- return callback(null, [{ address: ip, family: 4 }]);
 
 
 
 
 
 
 
 
 
113
  }
114
- callback(null, ip, 4);
115
- });
116
- } else {
117
- // Other DNS errors — pass through
118
- callback(err, address, family);
119
- }
120
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  };
 
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
+ function transparentProxyRequest(protocol, origFn, ...args) {
18
+ let options = args[0];
19
+ let callback = args[1];
20
+
21
+ if (typeof options === "string") {
22
+ try {
23
+ options = new URL(options);
24
+ } catch {
25
+ return origFn.apply(this, args);
26
+ }
27
+ } else if (options instanceof URL) {
28
+ // Already a URL
29
+ } else {
30
+ // Object options
31
+ options = { ...options };
32
  }
33
+
34
+ if (typeof callback !== "function" && typeof args[2] === "function") {
35
+ callback = args[2];
 
 
 
 
 
 
 
 
 
36
  }
37
 
38
+ const hostname = options.hostname || options.host;
39
+ const port = options.port || (protocol === "https:" ? 443 : 80);
40
+ const path = options.path || options.pathname || "/";
41
 
42
+ if (isBlocked(hostname)) {
43
+ console.error(`[DNS-FIX] Transparently proxying ${protocol}//${hostname}${path} via fetch()`);
44
+
45
+ const requestUrl = `${protocol}//${hostname}${path}`;
46
+ const requestHeaders = { ...options.headers };
47
+
48
+ // We need to return a ClientRequest-like object (a Writable stream)
49
+ const reqStream = new PassThrough();
50
+ const resStream = new PassThrough();
51
+
52
+ // Buffer the request body
53
+ let requestBody = Buffer.alloc(0);
54
+ reqStream.on("data", (chunk) => {
55
+ requestBody = Buffer.concat([requestBody, chunk]);
56
  });
 
57
 
58
+ reqStream.on("finish", async () => {
59
+ try {
60
+ const fetchRes = await fetch(requestUrl, {
61
+ method: options.method || "GET",
62
+ headers: requestHeaders,
63
+ body: options.method !== "GET" && options.method !== "HEAD" ? requestBody : undefined,
64
+ redirect: "manual",
65
+ });
66
 
67
+ // Construct an IncomingMessage-like object
68
+ resStream.statusCode = fetchRes.status;
69
+ resStream.statusMessage = fetchRes.statusText;
70
+ resStream.headers = Object.fromEntries(fetchRes.headers.entries());
71
+ resStream.rawHeaders = [];
72
+ for (const [k, v] of fetchRes.headers.entries()) {
73
+ resStream.rawHeaders.push(k, v);
74
  }
75
+
76
+ if (callback) callback(resStream);
77
+ reqStream.emit("response", resStream);
78
+
79
+ if (fetchRes.body) {
80
+ const reader = fetchRes.body.getReader();
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ if (done) break;
84
+ resStream.write(value);
85
+ }
86
  }
87
+ resStream.end();
88
+ } catch (err) {
89
+ console.error(`[DNS-FIX] Proxy error for ${requestUrl}: ${err.message}`);
90
+ reqStream.emit("error", err);
91
+ }
92
+ });
93
+
94
+ // Mock ClientRequest methods
95
+ reqStream.abort = () => reqStream.destroy();
96
+ reqStream.end = (chunk) => {
97
+ if (chunk) reqStream.write(chunk);
98
+ reqStream.end();
99
+ };
100
+ reqStream.setTimeout = (ms, cb) => { if (cb) setTimeout(cb, ms); return reqStream; };
101
+ reqStream.setNoDelay = () => reqStream;
102
+ reqStream.setSocketKeepAlive = () => reqStream;
103
+
104
+ return reqStream;
105
+ }
106
+
107
+ return origFn.apply(this, args);
108
+ }
109
+
110
+ http.request = function (...args) {
111
+ return transparentProxyRequest("http:", _origHttpRequest, ...args);
112
+ };
113
+
114
+ https.request = function (...args) {
115
+ return transparentProxyRequest("https:", _origHttpsRequest, ...args);
116
+ };
117
+
118
+ // Also patch http.get and https.get as they often bypass request
119
+ http.get = function (...args) {
120
+ const req = http.request(...args);
121
+ req.end();
122
+ return req;
123
+ };
124
+
125
+ https.get = function (...args) {
126
+ const req = https.request(...args);
127
+ req.end();
128
+ return req;
129
  };