From 7282098ec016c8a50361880ee568ac9dbc94fc98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Wed, 5 Mar 2025 21:39:03 +0800 Subject: [PATCH] =?UTF-8?q?feature:=201=EF=BC=89=E6=96=B0=E5=A2=9E=20`UDP`?= =?UTF-8?q?=20=E5=92=8C=20`TCP`=20=E7=B1=BB=E5=9E=8B=E7=9A=84DNS=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=EF=BC=9B2=EF=BC=89=E4=BF=AE=E5=A4=8D=20`TLS`=20?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E7=9A=84DNS=E6=9C=8D=E5=8A=A1=E5=9C=B0?= =?UTF-8?q?=E5=9D=80=E9=85=8D=E7=BD=AE=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9B3=EF=BC=89=E5=88=A0=E9=99=A4=20`ipad?= =?UTF-8?q?dress`=20=E7=B1=BB=E5=9E=8B=E7=9A=84DNS=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/dns/base.js | 68 +++++- packages/mitmproxy/src/lib/dns/https.js | 56 +---- packages/mitmproxy/src/lib/dns/index.js | 77 +++++-- packages/mitmproxy/src/lib/dns/ipaddress.js | 38 ---- packages/mitmproxy/src/lib/dns/lookup.js | 4 - packages/mitmproxy/src/lib/dns/preset.js | 33 +-- packages/mitmproxy/src/lib/dns/tcp.js | 51 +++++ packages/mitmproxy/src/lib/dns/tls.js | 25 ++- packages/mitmproxy/src/lib/dns/udp.js | 44 ++++ .../proxy/mitmproxy/createConnectHandler.js | 4 +- .../proxy/mitmproxy/createRequestHandler.js | 6 +- .../src/lib/proxy/mitmproxy/dnsLookup.js | 6 +- .../mitmproxy/src/lib/speed/SpeedTester.js | 2 +- packages/mitmproxy/test/dnsSpeedTest.js | 5 - packages/mitmproxy/test/dnsTest.mjs | 209 ++++++++++++++---- 15 files changed, 414 insertions(+), 214 deletions(-) delete mode 100644 packages/mitmproxy/src/lib/dns/ipaddress.js delete mode 100644 packages/mitmproxy/src/lib/dns/lookup.js create mode 100644 packages/mitmproxy/src/lib/dns/tcp.js create mode 100644 packages/mitmproxy/src/lib/dns/udp.js diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index d44b1a5..fb44844 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -1,8 +1,19 @@ const LRUCache = require('lru-cache') const log = require('../../utils/util.log.server') +const matchUtil = require('../../utils/util.match') const { DynamicChoice } = require('../choice/index') -const cacheSize = 1024 +function mapToList (ipMap) { + const ipList = [] + for (const key in ipMap) { + if (ipMap[key]) { // 配置为 ture 时才生效 + ipList.push(key) + } + } + return ipList +} + +const defaultCacheSize = 1024 class IpCache extends DynamicChoice { constructor (hostname) { @@ -22,10 +33,12 @@ class IpCache extends DynamicChoice { } module.exports = class BaseDNS { - constructor (dnsName) { + constructor (dnsName, dnsType, cacheSize, preSetIpList) { this.dnsName = dnsName + this.dnsType = dnsType + this.preSetIpList = preSetIpList this.cache = new LRUCache({ - maxSize: cacheSize, + maxSize: (cacheSize > 0 ? cacheSize : defaultCacheSize), sizeCalculation: () => { return 1 }, @@ -53,7 +66,7 @@ module.exports = class BaseDNS { } const t = new Date() - let ipList = await this._lookup(hostname) + let ipList = await this._lookupInternal(hostname) if (ipList == null) { // 没有获取到ipv4地址 ipList = [] @@ -61,12 +74,55 @@ module.exports = class BaseDNS { ipList.push(hostname) // 把原域名加入到统计里去 ipCache.setBackupList(ipList) - log.info(`[DNS '${this.dnsName}']: ${hostname} ➜ ${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache)) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache)) return ipCache.value } catch (error) { - log.error(`[DNS '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error) + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error) return hostname } } + + async _lookupInternal (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 (hostnamePreSetIpList.length > 0) { + hostnamePreSetIpList.isPreSet = true + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`) + return hostnamePreSetIpList + } + } + + return await this._lookup(hostname) + } + + async _lookup (hostname) { + const start = Date.now() + try { + const response = await this._doDnsQuery(hostname) + const cost = Date.now() - start + if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) { + // 说明没有获取到ip + log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response) + return [] + } + const ret = response.answers.filter(item => item.type === 'A').map(item => item.data) + if (ret.length === 0) { + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`) + } else { + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`) + } + return ret + } catch (e) { + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS query error, hostname: ${hostname}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}, cost: ${Date.now() - start} ms, error:`, e) + return [] + } + } } diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index 1031af0..3ab889c 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -1,64 +1,16 @@ const { promisify } = require('node:util') const doh = require('dns-over-http') -const log = require('../../utils/util.log.server') -const matchUtil = require('../../utils/util.match') const BaseDNS = require('./base') const dohQueryAsync = promisify(doh.query) -function mapToList (ipMap) { - const ipList = [] - for (const key in ipMap) { - if (ipMap[key]) { // 配置为 ture 时才生效 - ipList.push(key) - } - } - return ipList -} - module.exports = class DNSOverHTTPS extends BaseDNS { - constructor (dnsName, dnsServer, preSetIpList) { - super(dnsName) + constructor (dnsName, cacheSize, preSetIpList, dnsServer) { + super(dnsName, 'HTTPS', cacheSize, preSetIpList) this.dnsServer = dnsServer - this.preSetIpList = preSetIpList } - async _lookup (hostname) { - // 获取当前域名的预设IP列表 - let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, 'matched preSetIpList') - 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 - return hostnamePreSetIpList - } - } - - // 未预设当前域名的IP列表,则从dns服务器获取 - const start = new Date() - try { - const result = await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) - const cost = new Date() - start - if (result.answers.length === 0) { - // 说明没有获取到ip - log.info(`DNS '${this.dnsName}' 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`) - return [] - } - const ret = result.answers.filter(item => item.type === 'A').map(item => item.data) - if (ret.length === 0) { - log.info(`DNS '${this.dnsName}' 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`) - } else { - log.info(`DNS '${this.dnsName}' 获取到该域名的IPv4地址: ${hostname} ${JSON.stringify(ret)}, cost: ${cost} ms`) - } - return ret - } catch (e) { - log.warn(`DNS query error: ${hostname}, dns: ${this.dnsName}, dnsServer: ${this.dnsServer}, cost: ${new Date() - start} ms, error:`, e) - return [] - } + async _doDnsQuery (hostname) { + return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) } } diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index e153a0c..ee3e40c 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -1,8 +1,9 @@ const matchUtil = require('../../utils/util.match') -const DNSOverHTTPS = require('./https.js') -const DNSOverIpAddress = require('./ipaddress.js') const DNSOverPreSetIpList = require('./preset.js') +const DNSOverHTTPS = require('./https.js') const DNSOverTLS = require('./tls.js') +const DNSOverTCP = require('./tcp.js') +const DNSOverUDP = require('./udp.js') module.exports = { initDNS (dnsProviders, preSetIpList) { @@ -12,17 +13,65 @@ module.exports = { for (const provider in dnsProviders) { const conf = dnsProviders[provider] - if (conf.type === 'ipaddress') { - dnsMap[provider] = new DNSOverIpAddress(provider) - } else if (conf.type === 'https') { - dnsMap[provider] = new DNSOverHTTPS(provider, conf.server, preSetIpList) - } else { - dnsMap[provider] = new DNSOverTLS(provider) + // 获取DNS服务器 + let server = conf.server || conf.host + if (server != null) { + server = server.replace(/\s+/, '') + } + if (!server) { + continue } - // 设置DNS名称到name属性中 - dnsMap[provider].name = provider - dnsMap[provider].type = conf.type + // 获取DNS类型 + let type = conf.type + if (type == null) { + if (server.startsWith('https://') || server.startsWith('http://')) { + type = 'https' + } else if (server.startsWith('tls://')) { + type = 'tls' + } else if (server.startsWith('tcp://')) { + type = 'tcp' + } else if (server.includes('://') && !server.startsWith('udp://')) { + throw new Error(`Unknown type DNS: ${server}, provider: ${provider}`) + } else { + type = 'udp' + } + } else { + type = type.replace(/\s+/, '').toLowerCase() + } + + // 创建DNS对象 + if (type === 'https' || type === 'doh' || type === 'dns-over-https') { + if (!server.includes('/')) { + server = `https://${server}/dns-query` + } + + // 基于 https + dnsMap[provider] = new DNSOverHTTPS(provider, conf.cacheSize, preSetIpList, server) + } else { + // 获取DNS端口 + let port = conf.port + + // 处理带协议的DNS服务地址 + if (server.includes('://')) { + server = server.split('://')[1] + } + // 处理带端口的DNS服务地址 + if (port == null && server.includes(':')) { + [server, port] = server.split(':') + } + + if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') { + // 基于 tls + dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername) + } else if (type === 'tcp' || type === 'dns-over-tcp') { + // 基于 tcp + dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port) + } else { + // 基于 udp + dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port) + } + } } // 创建预设IP的DNS @@ -31,16 +80,14 @@ module.exports = { return dnsMap }, hasDnsLookup (dnsConfig, hostname) { - let providerName = null - // 先匹配 预设IP配置 - const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList') + const hostnamePreSetIpList = matchUtil.matchHostname(dnsConfig.preSetIpList, hostname, 'matched preSetIpList(hasDnsLookup)') if (hostnamePreSetIpList) { return dnsConfig.dnsMap.PreSet } // 再匹配 DNS映射配置 - providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName') + const providerName = matchUtil.matchHostname(dnsConfig.mapping, hostname, 'get dns providerName') // 由于DNS中的usa已重命名为cloudflare,所以做以下处理,为了向下兼容 if (providerName === 'usa' && dnsConfig.dnsMap.usa == null && dnsConfig.dnsMap.cloudflare != null) { diff --git a/packages/mitmproxy/src/lib/dns/ipaddress.js b/packages/mitmproxy/src/lib/dns/ipaddress.js deleted file mode 100644 index ab4d85d..0000000 --- a/packages/mitmproxy/src/lib/dns/ipaddress.js +++ /dev/null @@ -1,38 +0,0 @@ -const axios = require('axios') -const log = require('../../utils/util.log.server') -const BaseDNS = require('./base') - -module.exports = class DNSOverIpAddress extends BaseDNS { - async _lookup (hostname) { - const url = `https://${hostname}.ipaddress.com` - - // const res = fs.readFileSync(path.resolve(__dirname, './data.txt')).toString() - const res = await axios.get(url) - if (res.status !== 200 && res.status !== 201) { - log.error(`[dns] get ${hostname} ipaddress: error: ${res}`) - return - } - const ret = res.data - - const regexp = /IP Address<\/th>