-
Notifications
You must be signed in to change notification settings - Fork 285
Expand file tree
/
Copy pathdns_doh.js
More file actions
257 lines (231 loc) · 9.91 KB
/
dns_doh.js
File metadata and controls
257 lines (231 loc) · 9.91 KB
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
/**
* DNS over HTTPS (DOH) Utility Module
*
* Uses 'dns-over-http-resolver' package for robust DOH resolution.
* Reads configuration from config/player.json.
* Automatically detects system proxy (Env vars or Windows Registry) to bypass local DNS pollution.
*/
import DnsOverHttpResolver from 'dns-over-http-resolver';
import axios from 'axios';
import https from 'https';
import fs from 'fs';
import path from 'path';
import {fileURLToPath} from 'url';
import {HttpsProxyAgent} from 'https-proxy-agent';
import {exec} from 'child_process';
import util from 'util';
import {ENV} from './env.js'; // Import ENV utility
const execAsync = util.promisify(exec);
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// DOH Configuration and Resolver Lazy Init
let dohServers = null;
let resolver = null;
const configPath = path.resolve(__dirname, '../config/player.json');
// Initialize Resolver Lazy
function getResolver() {
// Check if DOH is enabled via ENV (default: 0/false)
const enableDoh = ENV.get('enable_doh', '0') === '1' || ENV.get('enable_doh') === 'true';
if (!enableDoh) {
// console.log('[DOH] DOH is disabled via ENV.');
return null;
}
if (resolver) return resolver;
try {
// Load config if not loaded
if (!dohServers) {
try {
if (fs.existsSync(configPath)) {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
if (config.doh && Array.isArray(config.doh) && config.doh.length > 0) {
dohServers = config.doh.map(server => server.url);
console.log(`[DOH] Loaded ${dohServers.length} DOH servers from config.`);
}
}
} catch (e) {
console.error('[DOH] Failed to load DOH config:', e.message);
}
}
resolver = new DnsOverHttpResolver({
maxCache: 1000,
request: customRequest
});
if (dohServers && dohServers.length > 0) {
resolver.setServers(dohServers);
}
} catch (e) {
console.error('[DOH] Init failed:', e.message);
return null;
}
return resolver;
}
// Proxy Detection Logic
let cachedProxy = null;
let lastCheckTime = 0;
let checkPromise = null;
const PROXY_CACHE_TTL = 60000; // 60 seconds cache
export function getSystemProxy() {
// Check if system proxy detection is enabled via ENV (default: 1/true)
const enableProxy = ENV.get('enable_system_proxy', '1') === '1' || ENV.get('enable_system_proxy') === 'true';
if (!enableProxy) {
// console.log('[DOH] System proxy detection is disabled via ENV.');
return Promise.resolve(null);
}
const now = Date.now();
// 1. If cache is valid (checked within 60s), return immediately
if (lastCheckTime > 0 && (now - lastCheckTime < PROXY_CACHE_TTL)) {
return Promise.resolve(cachedProxy);
}
// 2. If a check is already in progress, join it (prevent concurrent spawning)
if (checkPromise) {
return checkPromise;
}
// 3. Start a new check
checkPromise = (async () => {
// Timeout option for exec commands
const execOpts = {timeout: 300};
let detectedProxy = null;
try {
// ... (Checks) ...
// 1. Check Environment Variables
const envProxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy;
if (envProxy) {
// console.log(`[DOH] Detected proxy from env: ${envProxy}`);
detectedProxy = envProxy;
}
// 2. Check Windows Registry
else if (process.platform === 'win32') {
try {
const {stdout: enableOut} = await execAsync('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable', execOpts);
if (/0x1/.test(enableOut)) {
const {stdout: serverOut} = await execAsync('reg query "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer', execOpts);
const match = serverOut.match(/ProxyServer\s+REG_SZ\s+(.*)/i);
if (match && match[1]) {
let proxyStr = match[1].trim();
if (proxyStr.includes('=')) {
const parts = proxyStr.split(';');
for (const part of parts) {
if (part.startsWith('https=')) {
proxyStr = part.substring(6);
break;
}
}
}
if (!proxyStr.startsWith('http')) {
proxyStr = 'http://' + proxyStr;
}
// console.log(`[DOH] Detected system proxy: ${proxyStr}`);
detectedProxy = proxyStr;
}
}
} catch (e) {
}
}
// 3. Check Android/Linux global http_proxy
else if (process.platform === 'android' || process.platform === 'linux') {
try {
const {stdout} = await execAsync('settings get global http_proxy', execOpts);
const proxyStr = stdout ? stdout.trim() : '';
if (proxyStr && proxyStr !== 'null' && proxyStr !== ':0') {
const finalProxy = proxyStr.startsWith('http') ? proxyStr : `http://${proxyStr}`;
// console.log(`[DOH] Detected Android/Linux system proxy: ${finalProxy}`);
detectedProxy = finalProxy;
}
} catch (e) {
}
}
// 4. Check Linux GNOME
if (!detectedProxy && process.platform === 'linux') {
try {
const {stdout: mode} = await execAsync('gsettings get org.gnome.system.proxy mode', execOpts);
if (mode && mode.trim().replace(/'/g, '') === 'manual') {
const {stdout: host} = await execAsync('gsettings get org.gnome.system.proxy.http host', execOpts);
const {stdout: port} = await execAsync('gsettings get org.gnome.system.proxy.http port', execOpts);
const hostStr = host ? host.trim().replace(/'/g, '') : '';
const portStr = port ? port.trim() : '';
if (hostStr && portStr && portStr !== '0') {
detectedProxy = `http://${hostStr}:${portStr}`;
// console.log(`[DOH] Detected GNOME proxy: ${detectedProxy}`);
}
}
} catch (e) {
}
}
// 5. Check macOS
if (!detectedProxy && process.platform === 'darwin') {
try {
const {stdout} = await execAsync('scutil --proxy', execOpts);
if (/HTTPEnable\s*:\s*1/.test(stdout)) {
const hostMatch = stdout.match(/HTTPProxy\s*:\s*([^\s]+)/);
const portMatch = stdout.match(/HTTPPort\s*:\s*(\d+)/);
if (hostMatch && hostMatch[1]) {
detectedProxy = `http://${hostMatch[1]}:${portMatch && portMatch[1] ? portMatch[1] : '80'}`;
// console.log(`[DOH] Detected macOS proxy: ${detectedProxy}`);
}
}
} catch (e) {
}
}
} catch (e) {
console.error('[DOH] Error detecting proxy:', e.message);
} finally {
// Update cache
if (detectedProxy !== cachedProxy) {
if (detectedProxy) console.log(`[DOH] System proxy updated: ${detectedProxy}`);
else if (cachedProxy) console.log(`[DOH] System proxy cleared`);
cachedProxy = detectedProxy;
}
lastCheckTime = Date.now();
checkPromise = null;
}
return cachedProxy;
})();
return checkPromise;
}
// Custom request function using axios
const customRequest = async (resource, signal) => {
try {
const proxy = await getSystemProxy();
const config = {
headers: {
'Accept': 'application/dns-json'
},
signal: signal,
timeout: 5000
};
if (proxy) {
config.httpsAgent = new HttpsProxyAgent(proxy);
config.proxy = false; // Disable axios internal proxy handling to use agent
} else {
config.httpsAgent = new https.Agent({rejectUnauthorized: false});
}
const response = await axios.get(resource, config);
return response.data;
} catch (error) {
throw error;
}
};
// Initialize Resolver - REMOVED top-level init
// const resolver = new DnsOverHttpResolver({ ... });
// if (dohServers.length > 0) { resolver.setServers(dohServers); }
/**
* Resolve domain using DOH
* @param {string} domain
* @returns {Promise<string|null>} Resolved IP or null
*/
export async function resolveDoh(domain) {
// Return immediately if it's already an IP
if (!domain || /^(\d{1,3}\.){3}\d{1,3}$|^\[[\da-fA-F:]+\]$/.test(domain)) return domain;
// Skip localhost
if (domain === 'localhost' || domain === '127.0.0.1') return domain;
try {
const resolver = getResolver();
if (!resolver) return null;
const ips = await resolver.resolve(domain, 'A');
if (ips && ips.length > 0) {
return ips[0];
}
} catch (e) {
// console.error(`[DOH] Failed to resolve ${domain}:`, e.message);
}
return null;
}