From 4a3cae392fb86ea9d632216305cb251a56f8a922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Thu, 15 May 2025 15:44:58 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20DoH=E7=B1=BB=E5=9E=8B=E7=9A=84DNS?= =?UTF-8?q?=EF=BC=8C=E5=B7=B2=E6=94=AF=E6=8C=81SNI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/dns/base.js | 30 +++++--- packages/mitmproxy/src/lib/dns/https.js | 35 ++++++++- packages/mitmproxy/src/lib/dns/index.js | 4 +- packages/mitmproxy/src/lib/dns/tcp.js | 2 +- packages/mitmproxy/src/lib/dns/tls.js | 2 +- packages/mitmproxy/src/lib/dns/udp.js | 2 +- .../mitmproxy/test/dnsTest-abroad-doh-sni.mjs | 77 +++++++++++++++++++ 7 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index 855af2d..0a987c0 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -103,19 +103,21 @@ module.exports = class BaseDNS { } async _lookupWithPreSetIpList (hostname) { - // 获取当前域名的预设IP列表 - let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`) - if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) { - if (hostnamePreSetIpList.length > 0) { - hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖 - } else { - hostnamePreSetIpList = mapToList(hostnamePreSetIpList) - } + if (this.preSetIpList) { + // 获取当前域名的预设IP列表 + let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`) + if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) { + if (hostnamePreSetIpList.length > 0) { + hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖 + } else { + hostnamePreSetIpList = mapToList(hostnamePreSetIpList) + } - if (hostnamePreSetIpList.length > 0) { - hostnamePreSetIpList.isPreSet = true - log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`) - return hostnamePreSetIpList + if (hostnamePreSetIpList.length > 0) { + hostnamePreSetIpList.isPreSet = true + log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`) + return hostnamePreSetIpList + } } } @@ -159,6 +161,10 @@ module.exports = class BaseDNS { } _doDnsQuery (hostname, type = 'A', start) { + if (start == null) { + start = Date.now() + } + return new Promise((resolve, reject) => { // 设置超时任务 let isOver = false diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index 5ce03b7..d5e2d76 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -1,16 +1,45 @@ const { promisify } = require('node:util') const doh = require('dns-over-http') const BaseDNS = require('./base') +const HttpsAgent = require('../proxy/common/ProxyHttpsAgent') +const Agent = require('../proxy/common/ProxyHttpAgent') const dohQueryAsync = promisify(doh.query) +function createAgent (dnsServer) { + return new (dnsServer.startsWith('https:') ? HttpsAgent : Agent)({ + keepAlive: true, + timeout: 20000, + }) +} + module.exports = class DNSOverHTTPS extends BaseDNS { - constructor (dnsName, cacheSize, preSetIpList, dnsServer) { + constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerName) { super(dnsName, 'HTTPS', cacheSize, preSetIpList) - this.dnsServer = dnsServer + this.dnsServer = dnsServer.replace(/\s+/, '') + this.dnsServerName = dnsServerName } _dnsQueryPromise (hostname, type = 'A') { - return dohQueryAsync({ url: this.dnsServer }, [{ type, name: hostname }]) + // 请求参数 + const options = { + url: this.dnsServer, + agent: createAgent(this.dnsServer), + } + if (this.dnsServerName) { + // 设置SNI + options.servername = this.dnsServerName + options.rejectUnauthorized = false + } + + // DNS查询参数 + const questions = [ + { + type, + name: hostname, + }, + ] + + return dohQueryAsync(options, questions) } } diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index 611dc6c..7a36d6f 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -48,7 +48,7 @@ module.exports = { } // 基于 https - dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server) + dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server, conf.sni || conf.servername) } else { // 获取DNS端口 let port = conf.port @@ -64,7 +64,7 @@ module.exports = { if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') { // 基于 tls - dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername || conf.sni) + dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername) } else if (type === 'tcp') { // 基于 tcp dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port) diff --git a/packages/mitmproxy/src/lib/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js index 8b48afc..f3e5f5c 100644 --- a/packages/mitmproxy/src/lib/dns/tcp.js +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -9,7 +9,7 @@ const defaultPort = 53 // TCP类型的DNS服务默认端口号 module.exports = class DNSOverTCP extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { super(dnsName, 'TCP', cacheSize, preSetIpList) - this.dnsServer = dnsServer + this.dnsServer = dnsServer.replace(/\s+/, '') this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort } diff --git a/packages/mitmproxy/src/lib/dns/tls.js b/packages/mitmproxy/src/lib/dns/tls.js index 0530dbd..dfb7024 100644 --- a/packages/mitmproxy/src/lib/dns/tls.js +++ b/packages/mitmproxy/src/lib/dns/tls.js @@ -6,7 +6,7 @@ const defaultPort = 853 module.exports = class DNSOverTLS extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort, dnsServerName) { super(dnsName, 'TLS', cacheSize, preSetIpList) - this.dnsServer = dnsServer + this.dnsServer = dnsServer.replace(/\s+/, '') this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort this.dnsServerName = dnsServerName } diff --git a/packages/mitmproxy/src/lib/dns/udp.js b/packages/mitmproxy/src/lib/dns/udp.js index 87265e4..cd97a7d 100644 --- a/packages/mitmproxy/src/lib/dns/udp.js +++ b/packages/mitmproxy/src/lib/dns/udp.js @@ -8,7 +8,7 @@ const defaultPort = 53 // UDP类型的DNS服务默认端口号 module.exports = class DNSOverUDP extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { super(dnsName, 'UDP', cacheSize, preSetIpList) - this.dnsServer = dnsServer + this.dnsServer = dnsServer.replace(/\s+/, '') this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort this.isIPv6 = dnsServer.includes(':') && dnsServer.includes('[') && dnsServer.includes(']') diff --git a/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs new file mode 100644 index 0000000..9c98873 --- /dev/null +++ b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs @@ -0,0 +1,77 @@ +import DNSOverHTTPS from "../src/lib/dns/https.js"; + +// 境外DNS的DoH配置sni测试 +const servers = [ + 'https://dns.quad9.net/dns-query', + 'https://max.rethinkdns.com/dns-query', + 'https://sky.rethinkdns.com/dns-query', + 'https://doh.opendns.com/dns-query', + 'https://1.1.1.1/dns-query', + 'https://dns.cloudflare.com/dns-query', + 'https://cloudflare-dns.com/dns-query', + 'https://dns.google/dns-query', + 'https://dns.bebasid.com/unfiltered', + 'https://0ms.dev/dns-query', + 'https://dns.decloudus.com/dns-query', + 'https://wikimedia-dns.org/dns-query', + 'https://doh.applied-privacy.net/query', + 'https://private.canadianshield.cira.ca/dns-query', + // 'https://dns.controld.com/comss', // 可直连,无需SNI + 'https://kaitain.restena.lu/dns-query', + 'https://doh.libredns.gr/dns-query', + 'https://doh.libredns.gr/ads', + 'https://dns.switch.ch/dns-query', + 'https://doh.nl.ahadns.net/dns-query', + 'https://doh.la.ahadns.net/dns-query', + 'https://dns.dnswarden.com/uncensored', + 'https://doh.ffmuc.net/dns-query', + 'https://dns.oszx.co/dns-query', + 'https://doh.tiarap.org/dns-query', + 'https://jp.tiarap.org/dns-query', + 'https://dns.adguard.com/dns-query', + 'https://rubyfish.cn/dns-query', + 'https://i.233py.com/dns-query' + +] + +const hostname1 = 'github.com' +const sni = 'baidu.com' + +console.log(`\n--------------- 测试DoH的SNI功能:共 ${servers.length} 个DoH服务 ---------------\n`) + +let n = 0 +let success = 0 +let error = 0 +const arr = [] + +function count (isSuccess, i, doh, result) { + n++ + if (isSuccess) { + success++ + arr[i] = `${doh.dnsServer} : ${hostname1} -> ${result.answers[0].data}`; + } else error++ + + if (n === servers.length) { + console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length}, 成功:${success},失败:${error}`); + for (const item of arr) { + if (item) { + console.info(item); + } + } + console.info('=============================================================================\n\n') + } +} + +for (let i = 0; i < servers.length; i++) { + const n = i; + const doh = new DNSOverHTTPS(`dns${i}`, null, null, servers[i], sni) + doh._doDnsQuery(hostname1) + .then((result) => { + // console.info(`===> test testDoH '${doh.dnsServer}': ${hostname1} ->`, result.answers, '\n\n') + count(true, n, doh, result) + }) + .catch((e) => { + // console.error(`===> test testDoH '${doh.dnsServer}': ${hostname1} 失败:`, e, '\n\n') + count(false) + }) +}