From cfc0fc90d118de66b0c845ec93dbe1703f3486d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 7 Mar 2025 15:59:40 +0800 Subject: [PATCH 01/13] =?UTF-8?q?optimize:=20=E5=88=97=E8=A1=A8=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E7=82=B9=E5=87=BB=E6=B7=BB=E5=8A=A0=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E8=87=AA=E5=8A=A8=E8=8E=B7=E5=8F=96=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=A1=86=E7=9A=84=E7=84=A6=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/gui/src/view/mixins/plugin.js | 13 +++++++++++++ packages/gui/src/view/pages/plugin/git.vue | 3 ++- packages/gui/src/view/pages/plugin/overwall.vue | 12 ++++++++---- packages/gui/src/view/pages/proxy.vue | 3 ++- packages/gui/src/view/pages/server.vue | 9 ++++++--- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/packages/gui/src/view/mixins/plugin.js b/packages/gui/src/view/mixins/plugin.js index 7142dd0..81797e2 100644 --- a/packages/gui/src/view/mixins/plugin.js +++ b/packages/gui/src/view/mixins/plugin.js @@ -142,6 +142,19 @@ export default { const dir = await this.$api.info.getLogDir() this.$api.ipc.openPath(dir) }, + async focusFirst (ref) { + if (ref && ref.length != null) { + setTimeout(() => { + if (ref.length > 0) { + try { + ref[0].$el.querySelector('.ant-input').focus() + } catch (e) { + console.error('获取输入框焦点失败:', e) + } + } + }, 100) + } + }, handleHostname (hostname) { if (this.isNotHostname(hostname)) { return '' diff --git a/packages/gui/src/view/pages/plugin/git.vue b/packages/gui/src/view/pages/plugin/git.vue index 9f65ba3..03b2518 100644 --- a/packages/gui/src/view/pages/plugin/git.vue +++ b/packages/gui/src/view/pages/plugin/git.vue @@ -50,6 +50,7 @@ export default { }, addNoProxyUrl () { this.noProxyUrls.unshift({ key: '' }) + this.focusFirst(this.$refs.noProxyUrls) }, delNoProxyUrl (item, index) { this.noProxyUrls.splice(index, 1) @@ -108,7 +109,7 @@ export default { - + diff --git a/packages/gui/src/view/pages/plugin/overwall.vue b/packages/gui/src/view/pages/plugin/overwall.vue index d9a1387..c69db7e 100644 --- a/packages/gui/src/view/pages/plugin/overwall.vue +++ b/packages/gui/src/view/pages/plugin/overwall.vue @@ -60,6 +60,7 @@ export default { }, addTarget () { this.targets.unshift({ key: '', value: 'true' }) + this.focusFirst(this.$refs.targets) }, deleteTarget (item, index) { this.targets.splice(index, 1) @@ -88,14 +89,17 @@ export default { }) } if (this.servers.length === 0) { - this.addServer() + this.addServer(false) } }, deleteServer (item, index) { this.servers.splice(index, 1) }, - addServer () { + addServer (needFocus = true) { this.servers.unshift({ key: '', value: { type: 'path' } }) + if (needFocus) { + this.focusFirst(this.$refs.servers) + } }, submitServer () { const map = {} @@ -169,7 +173,7 @@ export default { - + @@ -196,7 +200,7 @@ export default { - + diff --git a/packages/gui/src/view/pages/proxy.vue b/packages/gui/src/view/pages/proxy.vue index 668fcbc..2ddedb1 100644 --- a/packages/gui/src/view/pages/proxy.vue +++ b/packages/gui/src/view/pages/proxy.vue @@ -62,6 +62,7 @@ export default { }, addExcludeIp () { this.excludeIpList.unshift({ key: '', value: 'true' }) + this.focusFirst(this.$refs.excludeIpList) }, delExcludeIp (item, index) { this.excludeIpList.splice(index, 1) @@ -166,7 +167,7 @@ export default { - + diff --git a/packages/gui/src/view/pages/server.vue b/packages/gui/src/view/pages/server.vue index 7ceb64f..cbc8ca1 100644 --- a/packages/gui/src/view/pages/server.vue +++ b/packages/gui/src/view/pages/server.vue @@ -108,6 +108,7 @@ export default { }, addDnsMapping () { this.dnsMappings.unshift({ key: '', value: 'quad9' }) + this.focusFirst(this.$refs.dnsMappings) }, // whiteList @@ -123,6 +124,7 @@ export default { }, addWhiteList () { this.whiteList.unshift({ key: '', value: 'true' }) + this.focusFirst(this.$refs.whiteList) }, deleteWhiteList (item, index) { this.whiteList.splice(index, 1) @@ -144,6 +146,7 @@ export default { }, addSpeedHostname () { this.getSpeedTestConfig().hostnameList.unshift('') + this.focusFirst(this.$refs.hostnameList) }, delSpeedHostname (item, index) { this.getSpeedTestConfig().hostnameList.splice(index, 1) @@ -316,7 +319,7 @@ export default { - + @@ -374,7 +377,7 @@ export default { - + @@ -422,7 +425,7 @@ export default { - + From ac1de5b4d6783a45820321cec50d6378e2d24124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Mon, 10 Mar 2025 15:53:32 +0800 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20SNI=E9=BB=98=E8=AE=A4=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84DNS=EF=BC=8C=E5=8F=AF=E5=9C=A8=20`DNS?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E7=AE=A1=E7=90=86`=20=E4=B8=AD=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/config/index.js | 1 + packages/mitmproxy/src/lib/dns/index.js | 10 ++++++++++ .../src/lib/proxy/mitmproxy/createRequestHandler.js | 6 ++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/core/src/config/index.js b/packages/core/src/config/index.js index c9e8024..b2b0051 100644 --- a/packages/core/src/config/index.js +++ b/packages/core/src/config/index.js @@ -393,6 +393,7 @@ const defaultConfig = { type: 'https', server: 'https://doh.360.cn/dns-query', cacheSize: 1000, + forSNI: true, }, rubyfish: { type: 'https', diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index ee3e40c..50d44ec 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -1,4 +1,5 @@ const matchUtil = require('../../utils/util.match') +const log = require('../../utils/util.log.server') const DNSOverPreSetIpList = require('./preset.js') const DNSOverHTTPS = require('./https.js') const DNSOverTLS = require('./tls.js') @@ -72,10 +73,19 @@ module.exports = { dnsMap[provider] = new DNSOverUDP(provider, conf.cacheSize, preSetIpList, server, port) } } + + if (conf.forSNI || conf.forSni) { + dnsMap.ForSNI = dnsMap[provider] + } } // 创建预设IP的DNS dnsMap.PreSet = new DNSOverPreSetIpList(preSetIpList) + if (dnsMap.ForSNI == null) { + dnsMap.ForSNI = dnsMap.PreSet + } + + log.info(`设置SNI默认使用的DNS为 '${dnsMap.ForSNI.dnsName}'(注:当某个域名配置了SNI但未配置DNS时,将默认使用该DNS)`) return dnsMap }, diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js index b3c27f9..e70c352 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js @@ -116,9 +116,11 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e if (dnsConfig && dnsConfig.dnsMap) { let dns = DnsUtil.hasDnsLookup(dnsConfig, rOptions.hostname) if (!dns && rOptions.servername) { - dns = dnsConfig.dnsMap.quad9 + dns = dnsConfig.dnsMap.ForSNI if (dns) { - log.info(`域名 ${rOptions.hostname} 在dns中未配置,但使用了 sni: ${rOptions.servername}, 必须使用dns,现默认使用 'quad9' DNS.`) + log.info(`域名 ${rOptions.hostname} 在dns中未配置,但使用了 sni: ${rOptions.servername}, 必须使用dns,现默认使用 '${dns.dnsName}' DNS.`) + } else { + log.warn(`域名 ${rOptions.hostname} 在dns中未配置,但使用了 sni: ${rOptions.servername},且DNS服务管理中,也未指定SNI默认使用的DNS。`) } } if (dns) { From fe412ccc248c59cb3dde7c46f902bd80581e8abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Mon, 10 Mar 2025 17:02:41 +0800 Subject: [PATCH 03/13] =?UTF-8?q?optimize:=20`TLS`=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E7=9A=84DNS=EF=BC=8Cservername=E5=B1=9E=E6=80=A7=E4=B9=9F?= =?UTF-8?q?=E5=8F=AF=E9=85=8D=E7=BD=AE=E5=9C=A8sni=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=8C=E4=B8=8E=E6=8B=A6=E6=88=AA=E5=8A=9F=E8=83=BD=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E9=85=8D=E7=BD=AE=E9=94=AE=E4=BF=9D=E6=8C=81=E4=B8=80?= =?UTF-8?q?=E8=87=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/dns/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index 50d44ec..908deb5 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -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) + dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername || conf.sni) } else if (type === 'tcp' || type === 'dns-over-tcp') { // 基于 tcp dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port) From 0edee7b6eb63de4cf39f15547b515fab88ae4428 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 14 Mar 2025 17:12:47 +0800 Subject: [PATCH 04/13] =?UTF-8?q?1=EF=BC=89feature:=20IP=E6=B5=8B=E9=80=9F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E9=92=88=E5=AF=B9=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E7=9A=84=E7=AB=AF=E5=8F=A3=E8=BF=9B=E8=A1=8C=E6=B5=8B=E9=80=9F?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=9B=BA=E5=AE=9A=20`443`=20?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E8=BF=9B=E8=A1=8C=E6=B5=8B=E9=80=9F=EF=BC=9B?= =?UTF-8?q?=202=EF=BC=89bugfix:=20IP=E6=B5=8B=E9=80=9F=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E5=9F=9F=E5=90=8D=E9=95=BF=E6=97=B6=E9=97=B4=E6=9C=AA?= =?UTF-8?q?=E8=AE=BF=E9=97=AE=E6=97=B6=EF=BC=8C=E5=86=8D=E8=AE=BF=E9=97=AE?= =?UTF-8?q?=E5=90=8E=E4=B8=8D=E4=BC=9A=E9=87=8D=E5=90=AF=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=B5=8B=E9=80=9F=E5=8A=9F=E8=83=BD=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9B=203=EF=BC=89optimize:=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96DNS=E8=8E=B7=E5=8F=96=E7=9A=84IP=E5=86=8D=E7=BB=8F?= =?UTF-8?q?=E8=BF=87IpTester=E6=A0=A1=E9=AA=8C=E7=9A=84=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/gui/src/view/pages/server.vue | 4 +- packages/gui/src/view/style/theme/dark.scss | 6 + packages/mitmproxy/src/lib/dns/base.js | 81 +++++-- packages/mitmproxy/src/lib/dns/https.js | 4 +- packages/mitmproxy/src/lib/dns/tcp.js | 4 +- packages/mitmproxy/src/lib/dns/tls.js | 6 +- packages/mitmproxy/src/lib/dns/udp.js | 21 +- .../proxy/mitmproxy/createConnectHandler.js | 2 +- .../proxy/mitmproxy/createRequestHandler.js | 2 +- .../src/lib/proxy/mitmproxy/dnsLookup.js | 63 +++--- .../mitmproxy/src/lib/speed/SpeedTester.js | 207 +++++++++++++----- packages/mitmproxy/src/lib/speed/config.js | 2 +- packages/mitmproxy/src/lib/speed/index.js | 71 +++--- packages/mitmproxy/test/lodashTest.js | 9 + 14 files changed, 352 insertions(+), 130 deletions(-) diff --git a/packages/gui/src/view/pages/server.vue b/packages/gui/src/view/pages/server.vue index cbc8ca1..36d712c 100644 --- a/packages/gui/src/view/pages/server.vue +++ b/packages/gui/src/view/pages/server.vue @@ -455,9 +455,9 @@ export default { - {{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }} + {{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }} diff --git a/packages/gui/src/view/style/theme/dark.scss b/packages/gui/src/view/style/theme/dark.scss index 77f8a2b..a31b81e 100644 --- a/packages/gui/src/view/style/theme/dark.scss +++ b/packages/gui/src/view/style/theme/dark.scss @@ -152,6 +152,12 @@ $dark-input: #777; //输入框:背景色 border-color: #5a5750; color: #cfa572; } + /* 标签:未知 */ + .ant-tag:not(.ant-tag-red, .ant-tag-green, .ant-tag-orange) { + background-color: #5a5a5a; + border-color: #5a5a5a; + color: #ccc; + } /* 按钮 */ .ant-btn:not(.ant-btn-danger, .ant-btn-primary) { diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index fb44844..5cd2825 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -52,13 +52,18 @@ module.exports = class BaseDNS { } } - async lookup (hostname) { + async lookup (hostname, ipChecker) { try { let ipCache = this.cache.get(hostname) if (ipCache) { - if (ipCache.value != null) { - ipCache.doCount(ipCache.value, false) - return ipCache.value + const ip = ipCache.value + if (ip != null) { + if (ipChecker && ipChecker(ip)) { + ipCache.doCount(ip, false) + return ip + } else { + return hostname + } } } else { ipCache = new IpCache(hostname) @@ -66,7 +71,7 @@ module.exports = class BaseDNS { } const t = new Date() - let ipList = await this._lookupInternal(hostname) + let ipList = await this._lookupWithPreSetIpList(hostname) if (ipList == null) { // 没有获取到ipv4地址 ipList = [] @@ -74,28 +79,42 @@ module.exports = class BaseDNS { ipList.push(hostname) // 把原域名加入到统计里去 ipCache.setBackupList(ipList) - 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 + const ip = ipCache.value + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ip} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache)) + + if (ipChecker) { + if (ip != null && ip !== hostname && ipChecker(ip)) { + return ip + } + + for (const ip of ipList) { + if (ip !== hostname && ipChecker(ip)) { + return ip + } + } + } + + return ip != null ? ip : hostname } catch (error) { log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error) return hostname } } - async _lookupInternal (hostname) { + 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() + 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)}`) + log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表: ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`) return hostnamePreSetIpList } } @@ -106,23 +125,59 @@ module.exports = class BaseDNS { async _lookup (hostname) { const start = Date.now() try { + // 执行DNS查询 + log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`) const response = await this._doDnsQuery(hostname) const cost = Date.now() - start + log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response) + 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`) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`) } else { - log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IP地址: ${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 [] } } + + _doDnsQuery (hostname, type) { + return new Promise((resolve, reject) => { + // 设置超时任务 + let isOver = false + const timeout = 6000 + const timeoutId = setTimeout(() => { + if (!isOver) { + reject(new Error('DNS查询超时')) + } + }, timeout) + + try { + this._dnsQueryPromise(hostname, type) + .then((response) => { + isOver = true + clearTimeout(timeoutId) + resolve(response) + }) + .catch((e) => { + isOver = true + clearTimeout(timeoutId) + reject(e) + }) + } catch (e) { + isOver = true + clearTimeout(timeoutId) + reject(e) + } + }) + } } diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index 3ab889c..5ce03b7 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -10,7 +10,7 @@ module.exports = class DNSOverHTTPS extends BaseDNS { this.dnsServer = dnsServer } - async _doDnsQuery (hostname) { - return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }]) + _dnsQueryPromise (hostname, type = 'A') { + return dohQueryAsync({ url: this.dnsServer }, [{ type, name: hostname }]) } } diff --git a/packages/mitmproxy/src/lib/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js index 6141609..0b49445 100644 --- a/packages/mitmproxy/src/lib/dns/tcp.js +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -13,7 +13,7 @@ module.exports = class DNSOverTCP extends BaseDNS { this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort } - _doDnsQuery (hostname) { + _dnsQueryPromise (hostname, type = 'A') { return new Promise((resolve, reject) => { // 构造 DNS 查询报文 const packet = dnsPacket.encode({ @@ -21,7 +21,7 @@ module.exports = class DNSOverTCP extends BaseDNS { type: 'query', id: randi(0x0, 0xFFFF), questions: [{ - type: 'A', + type, name: hostname, }], }) diff --git a/packages/mitmproxy/src/lib/dns/tls.js b/packages/mitmproxy/src/lib/dns/tls.js index b4b5d99..0530dbd 100644 --- a/packages/mitmproxy/src/lib/dns/tls.js +++ b/packages/mitmproxy/src/lib/dns/tls.js @@ -11,7 +11,7 @@ module.exports = class DNSOverTLS extends BaseDNS { this.dnsServerName = dnsServerName } - async _doDnsQuery (hostname) { + _dnsQueryPromise (hostname, type = 'A') { const options = { host: this.dnsServer, port: this.dnsServerPort, @@ -19,9 +19,9 @@ module.exports = class DNSOverTLS extends BaseDNS { name: hostname, klass: 'IN', - type: 'A', + type, } - return await dnstls.query(options) + return dnstls.query(options) } } diff --git a/packages/mitmproxy/src/lib/dns/udp.js b/packages/mitmproxy/src/lib/dns/udp.js index 86d9b30..89dcd0b 100644 --- a/packages/mitmproxy/src/lib/dns/udp.js +++ b/packages/mitmproxy/src/lib/dns/udp.js @@ -15,21 +15,28 @@ module.exports = class DNSOverUDP extends BaseDNS { this.socketType = this.isIPv6 ? 'udp6' : 'udp4' } - _doDnsQuery (hostname) { + _dnsQueryPromise (hostname, type = 'A') { return new Promise((resolve, reject) => { + let isOver = false + const timeout = 5000 + let timeoutId = null + // 构造 DNS 查询报文 const packet = dnsPacket.encode({ flags: dnsPacket.RECURSION_DESIRED, type: 'query', id: randi(0x0, 0xFFFF), questions: [{ - type: 'A', + type, name: hostname, }], }) // 创建客户端 const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => { + isOver = true + clearTimeout(timeoutId) + const response = dnsPacket.decode(msg) resolve(response) udpClient.close() @@ -38,10 +45,20 @@ module.exports = class DNSOverUDP extends BaseDNS { // 发送 UDP 查询 udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => { if (err) { + isOver = true + clearTimeout(timeoutId) reject(err) udpClient.close() } }) + + // 设置超时任务 + timeoutId = setTimeout(() => { + if (!isOver) { + reject(new Error('查询超时')) + udpClient.close() + } + }, timeout) }) } } diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js index 0ac8f4b..bed1845 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js @@ -114,7 +114,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire if (dnsConfig && dnsConfig.dnsMap) { const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname) if (dns) { - options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, isDnsIntercept) + options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, port, isDnsIntercept) } } // 代理连接事件监听 diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js index e70c352..0738f9a 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js @@ -124,7 +124,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e } } if (dns) { - rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept) + rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, rOptions.port, isDnsIntercept) log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`) res.setHeader('DS-DNS', dns.dnsName) } else { diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js index 6668ac0..ddc25e4 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/dnsLookup.js @@ -2,12 +2,35 @@ const defaultDns = require('node:dns') const log = require('../../../utils/util.log.server') const speedTest = require('../../speed') +function createIpChecker (tester) { + if (!tester || tester.backupList == null || tester.backupList.length === 0) { + return null + } + + return (ip) => { + for (let i = 0; i < tester.backupList.length; i++) { + const item = tester.backupList[i] + if (item.host === ip) { + if (item.time > 0) { + return true // IP测速成功 + } + if (item.status === 'failed') { + return false // IP测速失败 + } + break + } + } + + return true // IP测速未知 + } +} + module.exports = { - createLookupFunc (res, dns, action, target, isDnsIntercept) { + createLookupFunc (res, dns, action, target, port, isDnsIntercept) { target = target ? (`, target: ${target}`) : '' return (hostname, options, callback) => { - const tester = speedTest.getSpeedTester(hostname) + const tester = speedTest.getSpeedTester(hostname, port) if (tester) { const aliveIpObj = tester.pickFastAliveIpObj() if (aliveIpObj) { @@ -21,7 +44,10 @@ module.exports = { log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`) } } - dns.lookup(hostname).then((ip) => { + + const ipChecker = createIpChecker(tester) + + dns.lookup(hostname, ipChecker).then((ip) => { if (isDnsIntercept) { isDnsIntercept.dns = dns isDnsIntercept.hostname = hostname @@ -29,35 +55,16 @@ module.exports = { } if (ip !== hostname) { - // 判断是否为测速失败的IP,如果是,则不使用当前IP - let isTestFailedIp = false - if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) { - for (let i = 0; i < tester.backupList.length; i++) { - const item = tester.backupList[i] - if (item.host === ip) { - if (item.time == null) { - isTestFailedIp = true - } - break - } - } - } - if (isTestFailedIp === false) { - log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`) - if (res) { - res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) - } - callback(null, ip, 4) - return - } else { - // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options) + log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`) + if (res) { + res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`) } + callback(null, ip, 4) } else { // 使用默认dns - log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) + log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns) + defaultDns.lookup(hostname, options, callback) } - defaultDns.lookup(hostname, options, callback) }) } }, diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index 59f4995..c6bbb6d 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -1,31 +1,44 @@ -// 1个小时不访问,取消获取 +// const { exec } = require('node:child_process') const net = require('node:net') const _ = require('lodash') const log = require('../../utils/util.log.server') const config = require('./config.js') +// const isWindows = process.platform === 'win32' + const DISABLE_TIMEOUT = 60 * 60 * 1000 + class SpeedTester { - constructor ({ hostname }) { + constructor ({ hostname, port }) { this.dnsMap = config.getConfig().dnsMap + this.hostname = hostname - this.lastReadTime = Date.now() + this.port = port || 443 + this.ready = false this.alive = [] this.backupList = [] - this.keepCheckId = false - - this.loadingIps = false - this.loadingTest = false this.testCount = 0 - this.test() + this.lastReadTime = Date.now() + this.keepCheckIntervalId = false + + this.tryTestCount = 0 + + this.test() // 异步:初始化完成后先测速一次 } pickFastAliveIpObj () { this.touch() + if (this.alive.length === 0) { - this.test() // 异步 + if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) { + this.testBackups() // 异步 + } else if (this.tryTestCount % 10 === 0) { + this.test() // 异步 + } + this.tryTestCount++ + return null } return this.alive[0] @@ -33,26 +46,27 @@ class SpeedTester { touch () { this.lastReadTime = Date.now() - if (!this.keepCheckId) { + if (!this.keepCheckIntervalId) { this.startChecker() } } startChecker () { - if (this.keepCheckId) { - clearInterval(this.keepCheckId) + if (this.keepCheckIntervalId) { + clearInterval(this.keepCheckIntervalId) } - this.keepCheckId = setInterval(() => { + this.keepCheckIntervalId = setInterval(() => { if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) { // 超过很长时间没有访问,取消测试 - clearInterval(this.keepCheckId) + clearInterval(this.keepCheckIntervalId) + this.keepCheckIntervalId = false return } if (this.alive.length > 0) { - this.testBackups() - return + this.testBackups() // 异步 + } else { + this.test() // 异步 } - this.test() }, config.getConfig().interval) } @@ -71,44 +85,56 @@ class SpeedTester { promiseList.push(one) } await Promise.all(promiseList) + const items = [] for (const ip in ips) { - items.push({ host: ip, port: 443, dns: ips[ip].dns }) + items.push({ host: ip, dns: ips[ip].dns }) } return items } async getFromOneDns (dns) { - return await dns._lookupInternal(this.hostname) + return await dns._lookupWithPreSetIpList(this.hostname) } async test () { - const newList = await this.getIpListFromDns(this.dnsMap) - const newBackupList = [...newList, ...this.backupList] - this.backupList = _.unionBy(newBackupList, 'host') this.testCount++ + log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`) - log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList) - await this.testBackups() - if (config.notify) { - config.notify({ key: 'test' }) + try { + const newList = await this.getIpListFromDns(this.dnsMap) + const newBackupList = [...newList, ...this.backupList] + this.backupList = _.unionBy(newBackupList, 'host') + await this.testBackups() + log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`) + if (config.notify) { + config.notify({ key: 'test' }) + } + } catch (e) { + log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e) } } async testBackups () { - const testAll = [] - const aliveList = [] - for (const item of this.backupList) { - testAll.push(this.doTest(item, aliveList)) + if (this.backupList.length > 0) { + const aliveList = [] + + const testAll = [] + for (const item of this.backupList) { + testAll.push(this.doTest(item, aliveList)) + } + await Promise.all(testAll) + this.alive = aliveList } - await Promise.all(testAll) - this.alive = aliveList + this.ready = true } async doTest (item, aliveList) { try { const ret = await this.testOne(item) + item.title = `${ret.by}测速成功:${item.host}` + log.info(`[speed] test success: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}'`) _.merge(item, ret) aliveList.push({ ...ret, ...item }) aliveList.sort((a, b) => a.time - b.time) @@ -125,48 +151,131 @@ class SpeedTester { return a.time - b.time }) } catch (e) { - if (e.message !== 'timeout') { - log.warn('[speed] test error: ', this.hostname, `➜ ${item.host}:${item.port} from DNS '${item.dns}'`, ', errorMsg:', e.message) + if (item.time == null) { + item.title = e.message + item.status = 'failed' + } + if (!e.message.includes('timeout')) { + log.warn(`[speed] test error: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}', errorMsg: ${e.message}`) } } } - testOne (item) { - const timeout = 5000 - const { host, port, dns } = item - const startTime = Date.now() - let isOver = false + testByTCP (item) { return new Promise((resolve, reject) => { + const { host, dns } = item + const startTime = Date.now() + + let isOver = false + const timeout = 5000 let timeoutId = null - const client = net.createConnection({ host, port }, () => { - // 'connect' 监听器 - const connectionTime = Date.now() + + const client = net.createConnection({ host, port: this.port }, () => { isOver = true clearTimeout(timeoutId) - resolve({ status: 'success', time: connectionTime - startTime }) + + const connectionTime = Date.now() + resolve({ status: 'success', by: 'TCP', time: connectionTime - startTime }) client.end() }) - client.on('end', () => { - }) client.on('error', (e) => { - if (e.message !== 'timeout') { - log.warn('[speed] test error: ', this.hostname, `➜ ${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message) - } isOver = true clearTimeout(timeoutId) + + log.warn('[speed] test by TCP error: ', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message) reject(e) + client.end() }) timeoutId = setTimeout(() => { if (isOver) { return } - log.warn('[speed] test timeout:', this.hostname, `➜ ${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`) + + log.warn('[speed] test by TCP timeout:', this.hostname, `➜ ${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`) reject(new Error('timeout')) client.end() }, timeout) }) } + + // 暂不使用 + // testByPing (item) { + // return new Promise((resolve, reject) => { + // const { host, dns } = item + // const startTime = Date.now() + // + // // 设置超时程序 + // let isOver = false + // const timeout = 5000 + // const timeoutId = setTimeout(() => { + // if (!isOver) { + // log.warn('[speed] test by PING timeout:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms`) + // reject(new Error('timeout')) + // } + // }, timeout) + // + // // 协议选择(如强制ping6) + // const usePing6 = !isWindows && host.includes(':') // Windows无ping6命令 + // const cmd = usePing6 + // ? `ping6 -c 2 ${host}` + // : isWindows + // ? `ping -n 2 ${host}` + // : `ping -c 2 ${host}` + // + // log.debug('[speed] test by PING start:', this.hostname, `➜ ${host} from DNS '${dns}'`) + // exec(cmd, (error, stdout, _stderr) => { + // isOver = true + // clearTimeout(timeoutId) + // + // if (error) { + // log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 目标不可达或超时`) + // reject(new Error('目标不可达或超时')) + // return + // } + // + // // 提取延迟数据(正则匹配) + // const regex = /[=<](\d+(?:\.\d*)?)ms/gi // 适配Linux/Windows + // const times = [] + // let match + // // eslint-disable-next-line no-cond-assign + // while ((match = regex.exec(stdout)) !== null) { + // times.push(Number.parseFloat(match[1])) + // } + // + // if (times.length === 0) { + // log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 无法解析延迟`) + // reject(new Error('无法解析延迟')) + // } else { + // // 计算平均延迟 + // const avg = times.reduce((a, b) => a + b, 0) / times.length + // resolve({ status: 'success', by: 'PING', time: Math.round(avg) }) + // } + // }) + // }) + // } + + testOne (item) { + return new Promise((resolve, reject) => { + const thenFun = (ret) => { + resolve(ret) + } + + // 先用TCP测速 + this.testByTCP(item) + .then(thenFun) + .catch((e) => { + // // TCP测速失败,再用 PING 测速 + // this.testByPing(item) + // .then(thenFun) + // .catch((e2) => { + // reject(new Error(`TCP测速失败:${e.message};PING测速失败:${e2.message};`)) + // }) + + reject(new Error(`TCP测速失败:${e.message}`)) + }) + }) + } } module.exports = SpeedTester diff --git a/packages/mitmproxy/src/lib/speed/config.js b/packages/mitmproxy/src/lib/speed/config.js index ce8d3a8..dd7a0a2 100644 --- a/packages/mitmproxy/src/lib/speed/config.js +++ b/packages/mitmproxy/src/lib/speed/config.js @@ -1,9 +1,9 @@ const config = { - notify () {}, dnsMap: {}, } module.exports = { getConfig () { return config }, + notify: null, } diff --git a/packages/mitmproxy/src/lib/speed/index.js b/packages/mitmproxy/src/lib/speed/index.js index 0d31e55..e67071f 100644 --- a/packages/mitmproxy/src/lib/speed/index.js +++ b/packages/mitmproxy/src/lib/speed/index.js @@ -6,6 +6,28 @@ const SpeedTester = require('./SpeedTester.js') const SpeedTestPool = { } +function addSpeedTest (hostname, port) { + if (!port) { + const idx = hostname.indexOf(':') + if (idx > 0 && idx === hostname.lastIndexOf(':')) { + const arr = hostname.split(':') + hostname = arr[0] + port = Number.parseInt(arr[1]) || 443 + } else { + port = 443 + } + } + + // 443端口不拼接在key上 + const key = port === 443 ? hostname : `${hostname}:${port}` + + if (SpeedTestPool[key] == null) { + return SpeedTestPool[key] = new SpeedTester({ hostname, port }) + } + + return SpeedTestPool[key] +} + function initSpeedTest (runtimeConfig) { const { enabled, hostnameList } = runtimeConfig const conf = config.getConfig() @@ -14,45 +36,42 @@ function initSpeedTest (runtimeConfig) { return } _.forEach(hostnameList, (hostname) => { - SpeedTestPool[hostname] = new SpeedTester({ hostname }) + addSpeedTest(hostname) }) - log.info('[speed] enabled') + log.info('[speed] enabled,SpeedTestPool:', SpeedTestPool) } function getAllSpeedTester () { const allSpeed = {} - if (!config.getConfig().enabled) { - return allSpeed + + if (config.getConfig().enabled) { + _.forEach(SpeedTestPool, (item, key) => { + allSpeed[key] = { + hostname: item.hostname, + port: item.port, + alive: item.alive, + backupList: item.backupList, + } + }) } - _.forEach(SpeedTestPool, (item, key) => { - allSpeed[key] = { - hostname: key, - alive: item.alive, - backupList: item.backupList, - } - }) + return allSpeed } -function getSpeedTester (hostname) { +function getSpeedTester (hostname, port) { if (!config.getConfig().enabled) { - return + return null } - let instance = SpeedTestPool[hostname] - if (instance == null) { - instance = new SpeedTester({ hostname }) - SpeedTestPool[hostname] = instance - } - return instance + return addSpeedTest(hostname, port) } -function registerNotify (notify) { - config.notify = notify -} +// function registerNotify (notify) { +// config.notify = notify +// } function reSpeedTest () { - _.forEach(SpeedTestPool, (item, key) => { - item.test() + _.forEach(SpeedTestPool, (item, _key) => { + item.test() // 异步 }) } @@ -68,8 +87,8 @@ module.exports = { SpeedTester, initSpeedTest, getSpeedTester, - getAllSpeedTester, - registerNotify, + // getAllSpeedTester, + // registerNotify, reSpeedTest, action, } diff --git a/packages/mitmproxy/test/lodashTest.js b/packages/mitmproxy/test/lodashTest.js index b9db645..f3a7a4f 100644 --- a/packages/mitmproxy/test/lodashTest.js +++ b/packages/mitmproxy/test/lodashTest.js @@ -26,3 +26,12 @@ assert.strictEqual(isEmpty(0), false) assert.strictEqual(isEmpty(-1), false) assert.strictEqual(isEmpty(''), false) assert.strictEqual(isEmpty('1'), false) + +// test lodash.unionBy +const list = [ + { host: 1, port: 1, dns: 2 }, + { host: 1, port: 1, dns: 3 }, + { host: 1, port: 2, dns: 3 }, + { host: 1, port: 2, dns: 3 }, +] +console.info(lodash.unionBy(list, 'host', 'port')) From d2a4a2028b89332aba14a7fe67149716361ab0df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Mon, 7 Apr 2025 15:19:55 +0800 Subject: [PATCH 05/13] =?UTF-8?q?feat:=20proxy=E5=92=8Credirect=E6=8B=A6?= =?UTF-8?q?=E6=88=AA=E5=99=A8=EF=BC=8C=E6=94=AF=E6=8C=81=E5=91=BD=E5=90=8D?= =?UTF-8?q?=E6=8D=95=E8=8E=B7=E7=BB=84=E5=90=8D=E7=A7=B0=E6=9D=A5=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=8D=A0=E4=BD=8D=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js b/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js index f558471..a6d59a4 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js @@ -11,6 +11,11 @@ function replacePlaceholder (url, rOptions, matched) { for (let i = 0; i < matched.length; i++) { url = url.replace(`\${m[${i}]}`, matched[i] == null ? '' : matched[i]) } + if (matched.groups) { + for (const key in matched.groups) { + url = url.replace(`\${${key}}`, matched.groups[key] == null ? '' : matched.groups[key]) + } + } } // 移除多余的占位符 From 4d3f2240ab8218e1fe84986fbde069f223a322ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 18 Apr 2025 11:57:34 +0800 Subject: [PATCH 06/13] =?UTF-8?q?=E8=AE=A1=E7=AE=97cost=E6=89=80=E9=9C=80?= =?UTF-8?q?=E7=9A=84=E5=AF=B9=E8=B1=A1=EF=BC=8C=E7=94=B1=20`new=20Date()`?= =?UTF-8?q?=20=E8=B0=83=E6=95=B4=E4=B8=BA=20`Date.now()`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/src/lib/dns/base.js | 4 ++-- .../lib/proxy/mitmproxy/createConnectHandler.js | 6 +++--- .../lib/proxy/mitmproxy/createRequestHandler.js | 16 ++++++++-------- packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js | 6 +++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index 5cd2825..291d687 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -70,7 +70,7 @@ module.exports = class BaseDNS { this.cache.set(hostname, ipCache) } - const t = new Date() + const t = Date.now() let ipList = await this._lookupWithPreSetIpList(hostname) if (ipList == null) { // 没有获取到ipv4地址 @@ -81,7 +81,7 @@ module.exports = class BaseDNS { ipCache.setBackupList(ipList) const ip = ipCache.value - log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ip} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache)) + log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname} ➜ ${ip} (${Date.now() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache)) if (ipChecker) { if (ip != null && ip !== hostname && ipChecker(ip)) { diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js index bed1845..3f1e0be 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js @@ -55,7 +55,7 @@ module.exports = function createConnectHandler (sslConnectInterceptor, middlewar function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDirect = false, target = null) { // tunneling https // log.info('connect:', hostname, port) - const start = new Date() + const start = Date.now() const isDnsIntercept = {} const hostport = `${hostname}:${port}` @@ -134,7 +134,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire cltSocket.pipe(proxySocket) }) proxySocket.on('timeout', () => { - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `${isDirect ? '直连' : '代理连接'}超时: ${hostport}, cost: ${cost} ms` log.error(errorMsg) @@ -148,7 +148,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire }) proxySocket.on('error', (e) => { // 连接失败,可能被GFW拦截,或者服务端拥挤 - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `${isDirect ? '直连' : '代理连接'}失败: ${hostport}, cost: ${cost} ms, errorMsg: ${e.message}` log.error(`${errorMsg}\r\n`, e) diff --git a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js index 0738f9a..a8876f6 100644 --- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js +++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js @@ -109,7 +109,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e function onFree () { url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${rOptions.path}` - const start = new Date() + const start = Date.now() log.info('发起代理请求:', url, (rOptions.servername ? `, sni: ${rOptions.servername}` : ''), ', headers:', jsonApi.stringify2(rOptions.headers)) const isDnsIntercept = {} @@ -156,7 +156,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e } proxyReq = (rOptions.protocol === 'https:' ? https : http).request(rOptions, (proxyRes) => { - const cost = new Date() - start + const cost = Date.now() - start if (rOptions.protocol === 'https:') { log.info(`代理请求返回: 【${proxyRes.statusCode}】${url}, cost: ${cost} ms`) } else { @@ -173,7 +173,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e // 代理请求的事件监听 proxyReq.on('timeout', () => { - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `代理请求超时: ${url}, cost: ${cost} ms` log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions)) countSlow(isDnsIntercept, `代理请求超时, cost: ${cost} ms`) @@ -184,7 +184,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e reject(error) }) proxyReq.on('error', (e) => { - const cost = new Date() - start + const cost = Date.now() - start log.error(`代理请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions)) countSlow(isDnsIntercept, `代理请求错误: ${e.message}`) reject(e) @@ -195,7 +195,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e } }) proxyReq.on('aborted', () => { - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `代理请求被取消: ${url}, cost: ${cost} ms` log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions)) @@ -211,7 +211,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e // 原始请求的事件监听 req.on('aborted', () => { - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `请求被取消: ${url}, cost: ${cost} ms` log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions)) proxyReq.abort() @@ -221,12 +221,12 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e reject(new Error(errorMsg)) }) req.on('error', (e, req, res) => { - const cost = new Date() - start + const cost = Date.now() - start log.error(`请求错误: ${url}, cost: ${cost} ms, error:`, e, ', rOptions:', jsonApi.stringify2(rOptions)) reject(e) }) req.on('timeout', () => { - const cost = new Date() - start + const cost = Date.now() - start const errorMsg = `请求超时: ${url}, cost: ${cost} ms` log.error(errorMsg, ', rOptions:', jsonApi.stringify2(rOptions)) reject(new Error(errorMsg)) diff --git a/packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js b/packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js index 2e44900..b8b02e8 100644 --- a/packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js +++ b/packages/mitmproxy/src/lib/proxy/tls/tlsUtils.js @@ -22,8 +22,8 @@ utils.createCA = function (CN) { const keys = pki.rsa.generateKeyPair(2048) const cert = pki.createCertificate() cert.publicKey = keys.publicKey - cert.serialNumber = `${(new Date()).getTime()}` - cert.validity.notBefore = new Date(new Date() - (60 * 60 * 1000)) + cert.serialNumber = `${Date.now()}` + cert.validity.notBefore = new Date(Date.now() - (60 * 60 * 1000)) cert.validity.notAfter = new Date() cert.validity.notAfter.setFullYear(cert.validity.notAfter.getFullYear() + 20) const attrs = [{ @@ -87,7 +87,7 @@ utils.createFakeCertificateByDomain = function (caKey, caCert, domain, mappingHo const cert = pki.createCertificate() cert.publicKey = keys.publicKey - cert.serialNumber = `${(new Date()).getTime()}` + cert.serialNumber = `${Date.now()}` cert.validity.notBefore = new Date() cert.validity.notBefore.setFullYear(cert.validity.notBefore.getFullYear() - 1) cert.validity.notAfter = new Date() From 7697a0f199a27114a7a50c74942058ad11f363af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 9 May 2025 17:06:25 +0800 Subject: [PATCH 07/13] =?UTF-8?q?bugfix:=20=E8=A7=A3=E5=86=B3success?= =?UTF-8?q?=E3=80=81abort=E3=80=81redirect=E6=8B=A6=E6=88=AA=E5=99=A8?= =?UTF-8?q?=E8=B7=A8=E5=9F=9F=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mitmproxy/src/lib/interceptor/impl/req/abort.js | 12 ++++++++++-- .../src/lib/interceptor/impl/req/redirect.js | 12 ++++++++++-- .../src/lib/interceptor/impl/req/success.js | 12 ++++++++++-- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/abort.js b/packages/mitmproxy/src/lib/interceptor/impl/req/abort.js index 73eb827..68c455b 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/abort.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/abort.js @@ -5,10 +5,18 @@ module.exports = { const { rOptions, log } = context if (interceptOpt.abort === true || interceptOpt.abort === 'true') { - res.writeHead(403, { + const headers = { 'Content-Type': 'text/plain; charset=utf-8', 'DS-Interceptor': 'abort', - }) + } + + // headers.Access-Control-Allow-*:避免跨域问题 + if (rOptions.headers.origin) { + headers['Access-Control-Allow-Credentials'] = 'true' + headers['Access-Control-Allow-Origin'] = rOptions.headers.origin + } + + res.writeHead(403, headers) res.write( 'DevSidecar 403: Request abort.\n\n' + ' This request is matched by abort intercept.\n\n' diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js b/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js index 1812abd..5a800cc 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js @@ -9,10 +9,18 @@ module.exports = { // 获取重定向目标地址 const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched) - res.writeHead(302, { + const headers = { 'Location': redirect, 'DS-Interceptor': 'redirect', - }) + } + + // headers.Access-Control-Allow-*:避免跨域问题 + if (rOptions.headers.origin) { + headers['Access-Control-Allow-Credentials'] = 'true' + headers['Access-Control-Allow-Origin'] = rOptions.headers.origin + } + + res.writeHead(302, headers) res.end() const url = `${rOptions.method} ➜ ${rOptions.protocol}//${rOptions.hostname}:${rOptions.port}${req.url}` diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/success.js b/packages/mitmproxy/src/lib/interceptor/impl/req/success.js index 179f745..84cff4a 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/success.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/success.js @@ -5,10 +5,18 @@ module.exports = { const { rOptions, log } = context if (interceptOpt.success === true || interceptOpt.success === 'true') { - res.writeHead(200, { + const headers = { 'Content-Type': 'text/plain; charset=utf-8', 'DS-Interceptor': 'success', - }) + } + + // headers.Access-Control-Allow-*:避免跨域问题 + if (rOptions.headers.origin) { + headers['Access-Control-Allow-Credentials'] = 'true' + headers['Access-Control-Allow-Origin'] = rOptions.headers.origin + } + + res.writeHead(200, headers) res.write( 'DevSidecar 200: Request success.\n\n' + ' This request is matched by success intercept.\n\n' From 84f8802f06846d15a852d43cdc6c4c6556329c0c 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 08/13] =?UTF-8?q?feature:=20DoH=E7=B1=BB=E5=9E=8B=E7=9A=84?= =?UTF-8?q?DNS=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 | 29 ++++--- 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, 131 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 291d687..a71e2e2 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 + } } } @@ -151,6 +153,9 @@ module.exports = class BaseDNS { } _doDnsQuery (hostname, type) { + 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 908deb5..ba0436a 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' || type === 'dns-over-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 0b49445..c431964 100644 --- a/packages/mitmproxy/src/lib/dns/tcp.js +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -9,7 +9,7 @@ const defaultPort = 53 // UDP类型的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 89dcd0b..81b2201 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) + }) +} From c9095ac0afd8fc3724a9bc2e52d41c7d97ae7424 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:50:34 +0800 Subject: [PATCH 09/13] =?UTF-8?q?bugfix:=20=E5=B0=9D=E8=AF=95=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dmacbook=E6=89=93=E5=BC=80DS=EF=BC=8C=E5=9B=BE=E6=A0=87?= =?UTF-8?q?=E5=8F=98=E5=A4=A7=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/gui/extra/icons/512x512-2.png | Bin 0 -> 24621 bytes packages/gui/src/background.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 packages/gui/extra/icons/512x512-2.png diff --git a/packages/gui/extra/icons/512x512-2.png b/packages/gui/extra/icons/512x512-2.png new file mode 100644 index 0000000000000000000000000000000000000000..0702bbd3edef10d5294caacbda1d398457cf49bf GIT binary patch literal 24621 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 literal 0 HcmV?d00001 diff --git a/packages/gui/src/background.js b/packages/gui/src/background.js index 5be8601..b7d5593 100644 --- a/packages/gui/src/background.js +++ b/packages/gui/src/background.js @@ -410,7 +410,7 @@ function registerShowHideShortcut (showHideShortcut) { function initApp () { if (isMac) { app.whenReady().then(() => { - app.dock.setIcon(path.join(__dirname, '../extra/icons/512x512.png')) + app.dock.setIcon(path.join(__dirname, '../extra/icons/512x512-2.png')) }) } From 1dc589531415fd52dc0144a6ace69065a12d1eed 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 17:47:53 +0800 Subject: [PATCH 10/13] =?UTF-8?q?bugfix:=20=E9=81=BF=E5=85=8D=20=E2=80=9C?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E8=A2=AB=E5=8D=A0=E7=94=A8=E2=80=9D=20?= =?UTF-8?q?=E7=9A=84=E6=8F=90=E7=A4=BA=E6=A1=86=E8=BF=9E=E7=BB=AD=E5=BC=B9?= =?UTF-8?q?=E4=B8=A4=E4=B8=AA=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/gui/src/bridge/error/front.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/gui/src/bridge/error/front.js b/packages/gui/src/bridge/error/front.js index ab1efbd..2b11216 100644 --- a/packages/gui/src/bridge/error/front.js +++ b/packages/gui/src/bridge/error/front.js @@ -1,3 +1,5 @@ +let latestConfirmTime = null + function install (app, api) { api.ipc.on('error.core', (event, message) => { console.error('view on error', message) @@ -13,11 +15,20 @@ function install (app, api) { function handleServerStartError (message, err, app, api) { if (message.value === 'EADDRINUSE') { + // 避免重复弹窗 + const now = Date.now() + if (latestConfirmTime != null && now - latestConfirmTime < 1000) { + if (now - latestConfirmTime > 5000) { + latestConfirmTime = null + } + return + } + latestConfirmTime = now + app.$confirm({ title: '端口被占用,代理服务启动失败', content: '是否要杀掉占用进程?您也可以点击取消,然后前往加速服务->基本设置中修改代理端口', onOk () { - // TODO 杀掉进程 api.config.get().then((config) => { console.log('config:', config) api.shell.killByPort({ port: config.server.port }).then((ret) => { From 71094c475879f569df980eece19176b00e05e7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Fri, 16 May 2025 18:10:05 +0800 Subject: [PATCH 11/13] =?UTF-8?q?bugfix:=20DoT=E7=9A=84DNS=EF=BC=8C?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E4=BA=86SNI=E4=BD=86=E6=9C=AA=E7=94=9F?= =?UTF-8?q?=E6=95=88=E7=9A=84=E9=97=AE=E9=A2=98=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/mitmproxy/package.json | 1 - packages/mitmproxy/src/lib/dns/base.js | 2 +- packages/mitmproxy/src/lib/dns/https.js | 2 +- packages/mitmproxy/src/lib/dns/tls.js | 5 +- .../src/lib/dns/util/dns-over-tls.js | 81 +++++++++++++ .../mitmproxy/test/dnsTest-abroad-doh-sni.mjs | 2 +- .../mitmproxy/test/dnsTest-abroad-dot-sni.mjs | 107 ++++++++++++++++++ pnpm-lock.yaml | 10 -- 8 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 packages/mitmproxy/src/lib/dns/util/dns-over-tls.js create mode 100644 packages/mitmproxy/test/dnsTest-abroad-dot-sni.mjs diff --git a/packages/mitmproxy/package.json b/packages/mitmproxy/package.json index b2d53a3..9367be8 100644 --- a/packages/mitmproxy/package.json +++ b/packages/mitmproxy/package.json @@ -18,7 +18,6 @@ "axios": "^1.7.7", "baidu-aip-sdk": "^4.16.16", "dns-over-http": "^0.2.0", - "dns-over-tls": "^0.0.9", "is-browser": "^2.1.0", "json5": "^2.2.3", "lodash": "^4.17.21", diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index a71e2e2..7632fa1 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -159,7 +159,7 @@ module.exports = class BaseDNS { return new Promise((resolve, reject) => { // 设置超时任务 let isOver = false - const timeout = 6000 + const timeout = 8000 const timeoutId = setTimeout(() => { if (!isOver) { reject(new Error('DNS查询超时')) diff --git a/packages/mitmproxy/src/lib/dns/https.js b/packages/mitmproxy/src/lib/dns/https.js index d5e2d76..07f2639 100644 --- a/packages/mitmproxy/src/lib/dns/https.js +++ b/packages/mitmproxy/src/lib/dns/https.js @@ -9,7 +9,7 @@ const dohQueryAsync = promisify(doh.query) function createAgent (dnsServer) { return new (dnsServer.startsWith('https:') ? HttpsAgent : Agent)({ keepAlive: true, - timeout: 20000, + timeout: 4000, }) } diff --git a/packages/mitmproxy/src/lib/dns/tls.js b/packages/mitmproxy/src/lib/dns/tls.js index dfb7024..0194b4a 100644 --- a/packages/mitmproxy/src/lib/dns/tls.js +++ b/packages/mitmproxy/src/lib/dns/tls.js @@ -1,4 +1,4 @@ -const dnstls = require('dns-over-tls') +const dnstls = require('./util/dns-over-tls') const BaseDNS = require('./base') const defaultPort = 853 @@ -16,10 +16,13 @@ module.exports = class DNSOverTLS extends BaseDNS { host: this.dnsServer, port: this.dnsServerPort, servername: this.dnsServerName || this.dnsServer, + rejectUnauthorized: !this.dnsServerName, name: hostname, klass: 'IN', type, + + timeout: 4000, } return dnstls.query(options) diff --git a/packages/mitmproxy/src/lib/dns/util/dns-over-tls.js b/packages/mitmproxy/src/lib/dns/util/dns-over-tls.js new file mode 100644 index 0000000..8194f00 --- /dev/null +++ b/packages/mitmproxy/src/lib/dns/util/dns-over-tls.js @@ -0,0 +1,81 @@ +/** + * 由于组件 `dns-over-tls@0.0.9` 不支持 `rejectUnauthorized` 和 `timeout` 两个参数,所以将源码复制过来,并简化了代码。 + */ +const dnsPacket = require('dns-packet') +const tls_1 = require('node:tls') +const randi = require('random-int') + +const TWO_BYTES = 2 + +function getDnsQuery ({ type, name, klass, id }) { + return { + id, + type: 'query', + flags: dnsPacket.RECURSION_DESIRED, + questions: [{ class: klass, name, type }], + } +} + +function query ({ host, servername, type, name, klass, port, rejectUnauthorized, timeout }) { + return new Promise((resolve, reject) => { + if (!host || !servername || !name) { + throw new Error('At least host, servername and name must be set.') + } + + let response = Buffer.alloc(0) + let packetLength = 0 + const dnsQuery = getDnsQuery({ id: randi(0x0, 0xFFFF), type, name, klass }) + const dnsQueryBuf = dnsPacket.streamEncode(dnsQuery) + const socket = tls_1.connect({ host, port, servername, rejectUnauthorized, timeout }) + + // 超时处理 + let isFinished = false + let interval + if (timeout > 0) { + interval = setInterval(() => { + if (!isFinished) { + socket.destroy((...args) => { + console.info('socket destory callback args:', args) + }) + + reject(new Error('DNS查询超时')) + } + }, timeout) + } + + socket.on('secureConnect', () => socket.write(dnsQueryBuf)) + socket.on('data', (data) => { + if (timeout) { + isFinished = true + clearInterval(interval) + } + + if (response.length === 0) { + packetLength = data.readUInt16BE(0) + if (packetLength < 12) { + reject(new Error('Below DNS minimum packet length (DNS Header is 12 bytes)')) + } + response = Buffer.from(data) + } else { + response = Buffer.concat([response, data]) + } + + if (response.length === packetLength + TWO_BYTES) { + socket.destroy() + resolve(dnsPacket.streamDecode(response)) + } else { + reject(new Error('响应长度不正确')) + } + }) + socket.on('error', (err) => { + if (timeout) { + isFinished = true + clearInterval(interval) + } + reject(err) + }) + }) +} + +exports.query = query +exports.default = { query } diff --git a/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs index 9c98873..39fd2be 100644 --- a/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs +++ b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs @@ -37,7 +37,7 @@ const servers = [ const hostname1 = 'github.com' const sni = 'baidu.com' -console.log(`\n--------------- 测试DoH的SNI功能:共 ${servers.length} 个DoH服务 ---------------\n`) +console.log(`\n--------------- 测试DoH的SNI功能:共 ${servers.length} 个服务,${hostnames.length} 个域名,SNI: ${sni || '无'} ---------------\n`) let n = 0 let success = 0 diff --git a/packages/mitmproxy/test/dnsTest-abroad-dot-sni.mjs b/packages/mitmproxy/test/dnsTest-abroad-dot-sni.mjs new file mode 100644 index 0000000..c6cfddb --- /dev/null +++ b/packages/mitmproxy/test/dnsTest-abroad-dot-sni.mjs @@ -0,0 +1,107 @@ +import DNSOverTLS from "../src/lib/dns/tls.js"; + +// 境外DNS的DoT配置sni测试 +const servers = [ + // 'dot.360.cn', + + '1.1.1.1', // 可直连,无需SNI(有时候可以,有时候不行) + 'one.one.one.one', + 'cloudflare-dns.com', + 'security.cloudflare-dns.com', + 'family.cloudflare-dns.com', + '1dot1dot1dot1.cloudflare-dns.com', + + 'dot.sb', + '185.222.222.222', + '45.11.45.11', + + 'dns.adguard.com', + 'dns.adguard-dns.com', + 'dns-family.adguard.com', + 'family.adguard-dns.com', + 'dns-unfiltered.adguard.com', + 'unfiltered.adguard-dns.com', + 'dns.bebasid.com', + 'unfiltered.dns.bebasid.com', + 'antivirus.bebasid.com', + 'internetsehat.bebasid.com', + 'family-adblock.bebasid.com', + 'oisd.dns.bebasid.com', + 'hagezi.dns.bebasid.com', + 'dns.cfiec.net', + 'dns.opendns.com', + 'familyshield.opendns.com', + 'sandbox.opendns.com', + 'family-filter-dns.cleanbrowsing.org', + 'adult-filter-dns.cleanbrowsing.org', + 'security-filter-dns.cleanbrowsing.org', + 'p0.freedns.controld.com', + 'p1.freedns.controld.com', + 'p2.freedns.controld.com', + 'p3.freedns.controld.com', + 'dns.decloudus.com', + 'getdnsapi.net', + 'dnsovertls.sinodun.com', + 'dnsovertls1.sinodun.com', + 'dns.de.futuredns.eu.org', + 'dns.us.futuredns.eu.org', + 'unicast.censurfridns.dk', +] + +const hostnames = [ + 'github.com', + 'mvnrepository.com', +] +const sni = 'baidu.com' +// const sni = '' + +console.log(`\n--------------- 测试DoT的SNI功能:共 ${servers.length} 个服务,${hostnames.length} 个域名,SNI: ${sni || '无'} ---------------\n`) + +let n = 0 +let success = 0 +let error = 0 +const arr = [] + +function count (isSuccess, hostname, idx, dns, result, cost) { + if (isSuccess) { + success++ + const ipList = [] + for (const answer of result.answers) { + ipList[ipList.length] = answer.data; + } + arr[idx] = `${dns.dnsServer} : ${hostname} -> [ ${ipList.join(', ')} ] , cost: ${cost} ms`; + } else { + error++ + } + + n++ + + if (n === servers.length * hostnames.length) { + console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length * hostnames.length}, 成功:${success},失败:${error}`); + for (const item of arr) { + if (item) { + console.info(item); + } + } + console.info('=============================================================================\n\n') + } +} + +let x = 0; +for (let i = 0; i < servers.length; i++) { + for (const hostname of hostnames) { + const dns = new DNSOverTLS(`dns-${i}-${hostname}`, null, null, servers[i], null, sni) + const start = Date.now() + const idx = x; + dns._doDnsQuery(hostname) + .then((result) => { + console.info(`===> ${dns.dnsServer}: ${hostname} ->`, result.answers, '\n\n') + count(true, hostname, idx, dns, result, Date.now() - start) + }) + .catch((e) => { + console.error(`===> ${dns.dnsServer}: ${hostname} 失败:`, e, '\n\n') + count(false, hostname) + }) + x++; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e46e3cc..6f06d8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -169,9 +169,6 @@ importers: dns-over-http: specifier: ^0.2.0 version: 0.2.0 - dns-over-tls: - specifier: ^0.0.9 - version: 0.0.9 is-browser: specifier: ^2.1.0 version: 2.1.0 @@ -3045,9 +3042,6 @@ packages: dns-over-http@0.2.0: resolution: {integrity: sha512-K+SyN2L3ljxJ2MFtOv/vRS+3/YEMLvOuH7MrmO5ejaubi4w02/DLqzoK1kBGKlQrT9ND57pbapeDf+ue8AElEA==} - dns-over-tls@0.0.9: - resolution: {integrity: sha512-IdI/Qgku2KQPLtUBsC6HDdK6bWIZADQMOYWtqJzRivV5Z+EDKIVAaC+tWE6lJ9vn11qj5L39ZaIaTj/14Lzgkw==} - dns-packet@4.2.0: resolution: {integrity: sha512-bn1AKpfkFbm0MIioOMHZ5qJzl2uypdBwI4nYNsqvhjsegBhcKJUlCrMPWLx6JEezRjxZmxhtIz/FkBEur2l8Cw==} engines: {node: '>=4'} @@ -10553,10 +10547,6 @@ snapshots: transitivePeerDependencies: - supports-color - dns-over-tls@0.0.9: - dependencies: - dns-packet: 5.6.1 - dns-packet@4.2.0: dependencies: ip: 1.1.9 From 867909cbf5e782e8e9d698758c971de65b4fad7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Tue, 20 May 2025 10:19:44 +0800 Subject: [PATCH 12/13] =?UTF-8?q?feature:=20proxy=E3=80=81redirect?= =?UTF-8?q?=E6=8B=A6=E6=88=AA=E5=99=A8=E9=85=8D=E7=BD=AE=EF=BC=8C=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=9F=9F=E5=90=8D=E6=AD=A3=E5=88=99=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E5=8D=A0=E4=BD=8D=E7=AC=A6=E6=9B=BF=E6=8D=A2=E3=80=82=EF=BC=88?= =?UTF-8?q?=E4=B9=8B=E5=89=8D=E4=BB=85=E6=94=AF=E6=8C=81path=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E6=9B=BF=E6=8D=A2=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/lib/interceptor/impl/req/baiduOcr.js | 2 +- .../src/lib/interceptor/impl/req/proxy.js | 40 +++++++++++-------- .../src/lib/interceptor/impl/req/redirect.js | 4 +- packages/mitmproxy/src/options.js | 4 +- packages/mitmproxy/src/utils/util.match.js | 21 +++++++++- 5 files changed, 49 insertions(+), 22 deletions(-) diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/baiduOcr.js b/packages/mitmproxy/src/lib/interceptor/impl/req/baiduOcr.js index 063b811..d7bf308 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/baiduOcr.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/baiduOcr.js @@ -111,7 +111,7 @@ function checkIsLimitConfig (id, api) { module.exports = { name: 'baiduOcr', priority: 131, - requestIntercept (context, interceptOpt, req, res, ssl, next, matched) { + requestIntercept (context, interceptOpt, req, res, ssl, next) { const { rOptions, log } = context const headers = { diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js b/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js index a6d59a4..1e265c7 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js @@ -1,21 +1,29 @@ const url = require('node:url') const lodash = require('lodash') +function replacePlaceholder0 (url, matched, pre) { + if (matched) { + for (let i = 0; i < matched.length; i++) { + url = url.replace(`\${${pre}[${i}]}`, matched[i] || '') + } + if (matched.groups) { + for (const key in matched.groups) { + url = url.replace(`\${${key}}`, matched.groups[key] || '') + } + } + } + return url +} + // 替换占位符 -function replacePlaceholder (url, rOptions, matched) { +function replacePlaceholder (url, rOptions, pathMatched, hostnameMatched) { if (url.includes('${')) { // eslint-disable-next-line no-template-curly-in-string url = url.replace('${host}', rOptions.hostname) - if (matched && url.includes('${')) { - for (let i = 0; i < matched.length; i++) { - url = url.replace(`\${m[${i}]}`, matched[i] == null ? '' : matched[i]) - } - if (matched.groups) { - for (const key in matched.groups) { - url = url.replace(`\${${key}}`, matched.groups[key] == null ? '' : matched.groups[key]) - } - } + if (url.includes('${')) { + url = replacePlaceholder0(url, pathMatched, 'p') + url = replacePlaceholder0(url, hostnameMatched, 'h') } // 移除多余的占位符 @@ -27,7 +35,7 @@ function replacePlaceholder (url, rOptions, matched) { return url } -function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) { +function buildTargetUrl (rOptions, urlConf, interceptOpt, matched, hostnameMatched) { let targetUrl if (interceptOpt && interceptOpt.replace) { const regexp = new RegExp(interceptOpt.replace) @@ -45,7 +53,7 @@ function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) { } // 替换占位符 - targetUrl = replacePlaceholder(targetUrl, rOptions, matched) + targetUrl = replacePlaceholder(targetUrl, rOptions, matched, hostnameMatched) // 拼接协议 targetUrl = targetUrl.indexOf('http:') === 0 || targetUrl.indexOf('https:') === 0 ? targetUrl : `${rOptions.protocol}//${targetUrl}` @@ -53,9 +61,9 @@ function buildTargetUrl (rOptions, urlConf, interceptOpt, matched) { return targetUrl } -function doProxy (proxyConf, rOptions, req, interceptOpt, matched) { +function doProxy (proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched) { // 获取代理目标地址 - const proxyTarget = buildTargetUrl(rOptions, proxyConf, interceptOpt, matched) + const proxyTarget = buildTargetUrl(rOptions, proxyConf, interceptOpt, matched, hostnameMatched) // 替换rOptions的属性 // eslint-disable-next-line node/no-deprecated-api @@ -81,7 +89,7 @@ module.exports = { replacePlaceholder, buildTargetUrl, doProxy, - requestIntercept (context, interceptOpt, req, res, ssl, next, matched) { + requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) { const { rOptions, log, RequestCounter } = context const originHostname = rOptions.hostname @@ -112,7 +120,7 @@ module.exports = { } // 替换 rOptions 中的地址,并返回代理目标地址 - const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched) + const proxyTarget = doProxy(proxyConf, rOptions, req, interceptOpt, matched, hostnameMatched) if (context.requestCount) { log.info('proxy choice:', JSON.stringify(context.requestCount)) diff --git a/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js b/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js index 5a800cc..85f0a68 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js @@ -3,11 +3,11 @@ const proxyApi = require('./proxy') module.exports = { name: 'redirect', priority: 105, - requestIntercept (context, interceptOpt, req, res, ssl, next, matched) { + requestIntercept (context, interceptOpt, req, res, ssl, next, matched, hostnameMatched) { const { rOptions, log } = context // 获取重定向目标地址 - const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched) + const redirect = proxyApi.buildTargetUrl(rOptions, interceptOpt.redirect, interceptOpt, matched, hostnameMatched) const headers = { 'Location': redirect, diff --git a/packages/mitmproxy/src/options.js b/packages/mitmproxy/src/options.js index 6504dee..293a83a 100644 --- a/packages/mitmproxy/src/options.js +++ b/packages/mitmproxy/src/options.js @@ -187,12 +187,12 @@ module.exports = (serverConfig) => { if (impl.requestIntercept) { // req拦截器 interceptor.requestIntercept = (context, req, res, ssl, next) => { - return impl.requestIntercept(context, interceptOpt, req, res, ssl, next, matched) + return impl.requestIntercept(context, interceptOpt, req, res, ssl, next, matched, interceptOpts.matched) } } else if (impl.responseIntercept) { // res拦截器 interceptor.responseIntercept = (context, req, res, proxyReq, proxyRes, ssl, next) => { - return impl.responseIntercept(context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next, matched) + return impl.responseIntercept(context, interceptOpt, req, res, proxyReq, proxyRes, ssl, next, matched, interceptOpts.matched) } } diff --git a/packages/mitmproxy/src/utils/util.match.js b/packages/mitmproxy/src/utils/util.match.js index 46bf246..387f9a2 100644 --- a/packages/mitmproxy/src/utils/util.match.js +++ b/packages/mitmproxy/src/utils/util.match.js @@ -150,10 +150,29 @@ function matchHostnameAll (hostMap, hostname, action) { // } // 正则表达式匹配 - if (hostname.match(regexp)) { + const matched = hostname.match(regexp) + if (matched) { value = hostMap[regexp] log.debug(`matchHostname-one: ${action}: '${hostname}' -> { "${regexp}": ${JSON.stringify(value)} }`) values = merge(values, value) + + // 设置matched + if (matched.length > 1) { + if (values.matched) { + // 合并array + matched.shift() + values.matched = [...values.matched, ...matched] // 拼接上多个matched + + // 合并groups + if (matched.groups) { + values.matched.groups = merge(values.matched.groups, matched.groups) + } else { + values.matched.groups = matched.groups + } + } else { + values.matched = matched + } + } } } From 495f65c92b73cf99c39e8fbca4a76a519a66c3ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E8=89=AF?= <841369634@qq.com> Date: Tue, 26 Aug 2025 14:31:27 +0800 Subject: [PATCH 13/13] =?UTF-8?q?optimize:=20=E6=97=A5=E5=BF=97=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E3=80=81DNS=E7=9B=B8=E5=85=B3=E5=8D=95=E6=B5=8B?= =?UTF-8?q?=E8=B0=83=E6=95=B4=E3=80=81=E9=83=A8=E5=88=86=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E5=B0=8F=E8=B0=83=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/core/src/utils/util.version.js | 2 +- packages/gui/src/bridge/error/front.js | 3 - packages/gui/src/view/pages/server.vue | 2 +- packages/mitmproxy/src/lib/dns/base.js | 22 ++- packages/mitmproxy/src/lib/dns/index.js | 4 +- packages/mitmproxy/src/lib/dns/tcp.js | 2 +- packages/mitmproxy/src/lib/dns/udp.js | 2 +- .../interceptor/impl/res/responseReplace.js | 6 +- .../mitmproxy/src/lib/speed/SpeedTester.js | 8 +- packages/mitmproxy/src/options.js | 3 +- packages/mitmproxy/src/utils/util.match.js | 13 +- .../mitmproxy/test/dnsTest-abroad-doh-sni.mjs | 57 ++++--- packages/mitmproxy/test/dnsTest-abroad.mjs | 149 ++++++++++++++++++ packages/mitmproxy/test/dnsTest.mjs | 94 ++++------- 14 files changed, 249 insertions(+), 118 deletions(-) create mode 100644 packages/mitmproxy/test/dnsTest-abroad.mjs diff --git a/packages/core/src/utils/util.version.js b/packages/core/src/utils/util.version.js index 12a0b8f..265ef34 100644 --- a/packages/core/src/utils/util.version.js +++ b/packages/core/src/utils/util.version.js @@ -14,7 +14,7 @@ function parseVersion (version) { * @param log 日志对象 * @returns {number} 比较线上版本号是否为更新的版本,大于0=是|0=相等|小于0=否|-999=出现异常,比较结果未知 */ -export function isNewVersion (onlineVersion, currentVersion, log = console) { +export function isNewVersion (onlineVersion, currentVersion, log = null) { if (onlineVersion === currentVersion) { return 0 } diff --git a/packages/gui/src/bridge/error/front.js b/packages/gui/src/bridge/error/front.js index 2b11216..ef5d6e3 100644 --- a/packages/gui/src/bridge/error/front.js +++ b/packages/gui/src/bridge/error/front.js @@ -18,9 +18,6 @@ function handleServerStartError (message, err, app, api) { // 避免重复弹窗 const now = Date.now() if (latestConfirmTime != null && now - latestConfirmTime < 1000) { - if (now - latestConfirmTime > 5000) { - latestConfirmTime = null - } return } latestConfirmTime = now diff --git a/packages/gui/src/view/pages/server.vue b/packages/gui/src/view/pages/server.vue index 36d712c..96b2a5f 100644 --- a/packages/gui/src/view/pages/server.vue +++ b/packages/gui/src/view/pages/server.vue @@ -313,7 +313,7 @@ export default {
-
这里配置的域名不会通过代理
+
配置为不代理的域名不会通过代理
diff --git a/packages/mitmproxy/src/lib/dns/base.js b/packages/mitmproxy/src/lib/dns/base.js index 7632fa1..298b73c 100644 --- a/packages/mitmproxy/src/lib/dns/base.js +++ b/packages/mitmproxy/src/lib/dns/base.js @@ -126,10 +126,18 @@ module.exports = class BaseDNS { async _lookup (hostname) { const start = Date.now() + + let response try { // 执行DNS查询 log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`) - const response = await this._doDnsQuery(hostname) + response = await this._doDnsQuery(hostname, 'A', start) + } catch { + // 异常日志在 _doDnsQuery已经打印过,这里就不再打印了 + return [] + } + + try { const cost = Date.now() - start log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response) @@ -147,21 +155,23 @@ module.exports = class BaseDNS { 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) + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] 解读响应失败,response:`, response, ', error:', e) return [] } } - _doDnsQuery (hostname, type) { + _doDnsQuery (hostname, type = 'A', start) { if (start == null) { start = Date.now() } + return new Promise((resolve, reject) => { // 设置超时任务 let isOver = false const timeout = 8000 const timeoutId = setTimeout(() => { if (!isOver) { + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`) reject(new Error('DNS查询超时')) } }, timeout) @@ -176,11 +186,17 @@ module.exports = class BaseDNS { .catch((e) => { isOver = true clearTimeout(timeoutId) + if (e.message === 'DNS查询超时') { + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询超时. hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms`) + } else { + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询错误, hostname: ${hostname}, sni: ${this.dnsServerName || '无'}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e) + } reject(e) }) } catch (e) { isOver = true clearTimeout(timeoutId) + log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS查询异常, hostname: ${hostname}, type: ${type}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}${this.dnsServerPort ? `:${this.dnsServerPort}` : ''}, cost: ${Date.now() - start} ms, error:`, e) reject(e) } }) diff --git a/packages/mitmproxy/src/lib/dns/index.js b/packages/mitmproxy/src/lib/dns/index.js index ba0436a..7a36d6f 100644 --- a/packages/mitmproxy/src/lib/dns/index.js +++ b/packages/mitmproxy/src/lib/dns/index.js @@ -28,7 +28,7 @@ module.exports = { if (type == null) { if (server.startsWith('https://') || server.startsWith('http://')) { type = 'https' - } else if (server.startsWith('tls://')) { + } else if (server.startsWith('tls://') || server.startsWith('dot://')) { type = 'tls' } else if (server.startsWith('tcp://')) { type = 'tcp' @@ -65,7 +65,7 @@ module.exports = { if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') { // 基于 tls dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername) - } else if (type === 'tcp' || type === 'dns-over-tcp') { + } else if (type === 'tcp') { // 基于 tcp dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port) } else { diff --git a/packages/mitmproxy/src/lib/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js index c431964..f3e5f5c 100644 --- a/packages/mitmproxy/src/lib/dns/tcp.js +++ b/packages/mitmproxy/src/lib/dns/tcp.js @@ -4,7 +4,7 @@ const dnsPacket = require('dns-packet') const randi = require('random-int') const BaseDNS = require('./base') -const defaultPort = 53 // UDP类型的DNS服务默认端口号 +const defaultPort = 53 // TCP类型的DNS服务默认端口号 module.exports = class DNSOverTCP extends BaseDNS { constructor (dnsName, cacheSize, preSetIpList, dnsServer, dnsServerPort) { diff --git a/packages/mitmproxy/src/lib/dns/udp.js b/packages/mitmproxy/src/lib/dns/udp.js index 81b2201..cd97a7d 100644 --- a/packages/mitmproxy/src/lib/dns/udp.js +++ b/packages/mitmproxy/src/lib/dns/udp.js @@ -55,7 +55,7 @@ module.exports = class DNSOverUDP extends BaseDNS { // 设置超时任务 timeoutId = setTimeout(() => { if (!isOver) { - reject(new Error('查询超时')) + reject(new Error('DNS查询超时')) udpClient.close() } }, timeout) diff --git a/packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js b/packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js index 559c8be..05b9f4c 100644 --- a/packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js +++ b/packages/mitmproxy/src/lib/interceptor/impl/res/responseReplace.js @@ -92,9 +92,9 @@ module.exports = { // 如果未手动配置需要缓存,则不允许使用缓存 const maxAge = cacheReq.getMaxAge(interceptOpt) if (maxAge == null || maxAge <= 0) { - replaceHeaders['cache-control'] = '[remove]' - replaceHeaders['last-modified'] = '[remove]' - replaceHeaders.expires = '[remove]' + replaceHeaders['cache-control'] = REMOVE + replaceHeaders['last-modified'] = REMOVE + replaceHeaders.expires = REMOVE } actions += `${actions ? ',' : ''}download:${filename}` diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js index c6bbb6d..cbbeaf0 100644 --- a/packages/mitmproxy/src/lib/speed/SpeedTester.js +++ b/packages/mitmproxy/src/lib/speed/SpeedTester.js @@ -133,7 +133,7 @@ class SpeedTester { async doTest (item, aliveList) { try { const ret = await this.testOne(item) - item.title = `${ret.by}测速成功:${item.host}` + item.title = `${ret.by}测速成功:${ret.target}` log.info(`[speed] test success: ${this.hostname} ➜ ${item.host}:${this.port} from DNS '${item.dns}'`) _.merge(item, ret) aliveList.push({ ...ret, ...item }) @@ -175,7 +175,7 @@ class SpeedTester { clearTimeout(timeoutId) const connectionTime = Date.now() - resolve({ status: 'success', by: 'TCP', time: connectionTime - startTime }) + resolve({ status: 'success', by: 'TCP', target: `${host}:${this.port}`, time: connectionTime - startTime }) client.end() }) client.on('error', (e) => { @@ -249,7 +249,7 @@ class SpeedTester { // } else { // // 计算平均延迟 // const avg = times.reduce((a, b) => a + b, 0) / times.length - // resolve({ status: 'success', by: 'PING', time: Math.round(avg) }) + // resolve({ status: 'success', by: 'PING', target: host, time: Math.round(avg) }) // } // }) // }) @@ -272,7 +272,7 @@ class SpeedTester { // reject(new Error(`TCP测速失败:${e.message};PING测速失败:${e2.message};`)) // }) - reject(new Error(`TCP测速失败:${e.message}`)) + reject(new Error(`TCP测速失败:${item.host}:${this.port} ${e.message}`)) }) }) } diff --git a/packages/mitmproxy/src/options.js b/packages/mitmproxy/src/options.js index 293a83a..b9c5f12 100644 --- a/packages/mitmproxy/src/options.js +++ b/packages/mitmproxy/src/options.js @@ -1,7 +1,6 @@ const fs = require('node:fs') const path = require('node:path') const lodash = require('lodash') -const jsonApi = require('./json') const dnsUtil = require('./lib/dns') const interceptorImpls = require('./lib/interceptor') const scriptInterceptor = require('./lib/interceptor/impl/res/script') @@ -110,7 +109,7 @@ module.exports = (serverConfig) => { // 配置了白名单的域名,将跳过代理 const inWhiteList = !!matchUtil.matchHostname(whiteList, hostname, 'in whiteList') if (inWhiteList) { - log.info(`为白名单域名,不拦截: ${hostname}, headers:`, jsonApi.stringify2(req.headers)) + log.info(`为白名单域名,不拦截: ${hostname}`) return false // 不拦截 } diff --git a/packages/mitmproxy/src/utils/util.match.js b/packages/mitmproxy/src/utils/util.match.js index 387f9a2..e990699 100644 --- a/packages/mitmproxy/src/utils/util.match.js +++ b/packages/mitmproxy/src/utils/util.match.js @@ -1,5 +1,6 @@ const lodash = require('lodash') const log = require('./util.log.server') +const mergeApi = require('@docmirror/dev-sidecar/src/merge') function isMatched (url, regexp) { if (regexp === '.*' || regexp === '*' || regexp === 'true' || regexp === true) { @@ -113,16 +114,6 @@ function merge (oldObj, newObj) { } }) } -function deleteNullItems (target) { - lodash.forEach(target, (item, key) => { - if (item == null || item === '[delete]') { - delete target[key] - } - if (lodash.isObject(item)) { - deleteNullItems(item) - } - }) -} function matchHostnameAll (hostMap, hostname, action) { // log.debug('matchHostname-all:', action, hostMap) @@ -197,7 +188,7 @@ function matchHostnameAll (hostMap, hostname, action) { } if (!lodash.isEmpty(values)) { - deleteNullItems(values) + mergeApi.deleteNullItems(values) log.info(`matchHostname-all: ${action}: '${hostname}':`, JSON.stringify(values)) return values } else { diff --git a/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs index 39fd2be..163bb1c 100644 --- a/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs +++ b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs @@ -6,8 +6,6 @@ const servers = [ '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', @@ -30,12 +28,15 @@ const servers = [ 'https://jp.tiarap.org/dns-query', 'https://dns.adguard.com/dns-query', 'https://rubyfish.cn/dns-query', - 'https://i.233py.com/dns-query' - + 'https://i.233py.com/dns-query', ] -const hostname1 = 'github.com' +const hostnames = [ + 'github.com', + 'mvnrepository.com', +] const sni = 'baidu.com' +// const sni = '' console.log(`\n--------------- 测试DoH的SNI功能:共 ${servers.length} 个服务,${hostnames.length} 个域名,SNI: ${sni || '无'} ---------------\n`) @@ -44,15 +45,22 @@ let success = 0 let error = 0 const arr = [] -function count (isSuccess, i, doh, result) { - n++ +function count (isSuccess, hostname, idx, dns, result, cost) { if (isSuccess) { success++ - arr[i] = `${doh.dnsServer} : ${hostname1} -> ${result.answers[0].data}`; - } else error++ + const ipList = [] + for (const answer of result.answers) { + ipList[ipList.length] = answer.data; + } + arr[idx] = `${dns.dnsServer} : ${hostname} -> [ ${ipList.join(', ')} ] , cost: ${cost} ms`; + } else { + error++ + } - if (n === servers.length) { - console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length}, 成功:${success},失败:${error}`); + n++ + + if (n === servers.length * hostnames.length) { + console.info(`\n\n=============================================================================\n全部测完:总计:${servers.length * hostnames.length}, 成功:${success},失败:${error}`); for (const item of arr) { if (item) { console.info(item); @@ -62,16 +70,21 @@ function count (isSuccess, i, doh, result) { } } +let x = 0; 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) - }) + for (const hostname of hostnames) { + const dns = new DNSOverHTTPS(`dns-${i}-${hostname}`, null, null, servers[i], sni) + const start = Date.now() + const idx = x; + dns._doDnsQuery(hostname) + .then((result) => { + console.info(`===> ${dns.dnsServer}: ${hostname} ->`, result.answers, '\n\n') + count(true, hostname, idx, dns, result, Date.now() - start) + }) + .catch((e) => { + console.error(`===> ${dns.dnsServer}: ${hostname} 失败:`, e, '\n\n') + count(false, hostname) + }) + x++; + } } diff --git a/packages/mitmproxy/test/dnsTest-abroad.mjs b/packages/mitmproxy/test/dnsTest-abroad.mjs new file mode 100644 index 0000000..7e8a70e --- /dev/null +++ b/packages/mitmproxy/test/dnsTest-abroad.mjs @@ -0,0 +1,149 @@ +import assert from 'node:assert' +import dns from '../src/lib/dns/index.js' +import matchUtil from '../src/utils/util.match.js' + +const presetIp = '100.100.100.100' +const preSetIpList = matchUtil.domainMapRegexply({ + 'xxx.com': [ + presetIp + ] +}) + +// 境外DNS测试 +const dnsProviders = dns.initDNS({ + // udp + cloudflareUdp: { + server: 'udp://1.1.1.1', + }, + quad9Udp: { + server: 'udp://9.9.9.9', + }, + + // tcp + cloudflareTcp: { + server: 'tcp://1.1.1.1', + }, + quad9Tcp: { + server: 'tcp://9.9.9.9', + }, + + // https + cloudflare: { + server: 'https://1.1.1.1/dns-query', + }, + quad9: { + server: 'https://9.9.9.9/dns-query', + forSNI: true, + }, + rubyfish: { + server: 'https://rubyfish.cn/dns-query', + }, + py233: { + server: ' https://i.233py.com/dns-query', + }, + + // tls + cloudflareTLS: { + type: 'tls', + server: '1.1.1.1', + servername: 'cloudflare-dns.com', + }, + quad9TLS: { + server: 'tls://9.9.9.9', + servername: 'dns.quad9.net', + }, +}, preSetIpList) + + +const hasPresetHostname = 'xxx.com' +const noPresetHostname = 'yyy.com' + +const hostname1 = 'github.com' +const hostname2 = 'api.github.com' +const hostname3 = 'hk.docmirror.cn' +const hostname4 = 'github.docmirror.cn' +const hostname5 = 'gh.docmirror.top' +const hostname6 = 'gh2.docmirror.top' + +let ip + + +console.log('\n--------------- test ForSNI ---------------\n') +console.log(`===> test ForSNI: ${dnsProviders.ForSNI.dnsName}`, '\n\n') +assert.strictEqual(dnsProviders.ForSNI, dnsProviders.quad9) + + +console.log('\n--------------- test PreSet ---------------\n') +ip = await dnsProviders.PreSet.lookup(hasPresetHostname) +console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n') +console.log('\n\n') +assert.strictEqual(ip, presetIp) // 预设过IP,等于预设的IP + +ip = await dnsProviders.PreSet.lookup(noPresetHostname) +console.log(`===> test PreSet: ${noPresetHostname} ->`, ip, '\n\n') +console.log('\n\n') +assert.strictEqual(ip, noPresetHostname) // 未预设IP,等于域名自己 + + +console.log('\n--------------- test udp ---------------\n') +ip = await dnsProviders.cloudflareUdp.lookup(hasPresetHostname) +assert.strictEqual(ip, presetIp) // test preset +console.log('\n\n') + +assert.strictEqual(dnsProviders.cloudflareUdp.dnsType, 'UDP') +ip = await dnsProviders.cloudflareUdp.lookup(hostname1) +console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.quad9Udp.dnsType, 'UDP') +ip = await dnsProviders.quad9Udp.lookup(hostname1) +console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n') + + +console.log('\n--------------- test tcp ---------------\n') +ip = await dnsProviders.cloudflareTcp.lookup(hasPresetHostname) +assert.strictEqual(ip, presetIp) // test preset +console.log('\n\n') + +assert.strictEqual(dnsProviders.cloudflareTcp.dnsType, 'TCP') +ip = await dnsProviders.cloudflareTcp.lookup(hostname1) +console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.quad9Tcp.dnsType, 'TCP') +ip = await dnsProviders.quad9Tcp.lookup(hostname1) +console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n') + + +console.log('\n--------------- test https ---------------\n') +ip = await dnsProviders.cloudflare.lookup(hasPresetHostname) +assert.strictEqual(ip, presetIp) // test preset +console.log('\n\n') + +assert.strictEqual(dnsProviders.cloudflare.dnsType, 'HTTPS') +ip = await dnsProviders.cloudflare.lookup(hostname1) +console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.quad9.dnsType, 'HTTPS') +ip = await dnsProviders.quad9.lookup(hostname1) +console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.rubyfish.dnsType, 'HTTPS') +ip = await dnsProviders.rubyfish.lookup(hostname1) +console.log(`===> test rubyfish: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.py233.dnsType, 'HTTPS') +ip = await dnsProviders.py233.lookup(hostname1) +console.log(`===> test py233: ${hostname1} ->`, ip, '\n\n') + + +console.log('\n--------------- test TLS ---------------\n') +ip = await dnsProviders.cloudflareTLS.lookup(hasPresetHostname) +assert.strictEqual(ip, presetIp) // test preset +console.log('\n\n') + +assert.strictEqual(dnsProviders.cloudflareTLS.dnsType, 'TLS') +ip = await dnsProviders.cloudflareTLS.lookup(hostname1) +console.log(`===> test cloudflareTLS: ${hostname1} ->`, ip, '\n\n') + +assert.strictEqual(dnsProviders.quad9TLS.dnsType, 'TLS') +ip = await dnsProviders.quad9TLS.lookup(hostname1) +console.log(`===> test quad9TLS: ${hostname1} ->`, ip, '\n\n') diff --git a/packages/mitmproxy/test/dnsTest.mjs b/packages/mitmproxy/test/dnsTest.mjs index 75a36b0..65df397 100644 --- a/packages/mitmproxy/test/dnsTest.mjs +++ b/packages/mitmproxy/test/dnsTest.mjs @@ -9,17 +9,9 @@ const preSetIpList = matchUtil.domainMapRegexply({ ] }) +// 常用DNS测试 const dnsProviders = dns.initDNS({ // https - cloudflare: { - type: 'https', - server: 'https://1.1.1.1/dns-query', - cacheSize: 1000, - }, - quad9: { - server: 'https://9.9.9.9/dns-query', - cacheSize: 1000, - }, aliyun: { type: 'https', server: 'https://dns.alidns.com/dns-query', @@ -33,28 +25,10 @@ const dnsProviders = dns.initDNS({ safe360: { server: 'https://doh.360.cn/dns-query', cacheSize: 1000, - }, - rubyfish: { - server: 'https://rubyfish.cn/dns-query', - cacheSize: 1000, - }, - py233: { - server: ' https://i.233py.com/dns-query', - cacheSize: 1000, + forSNI: true, }, // tls - cloudflareTLS: { - type: 'tls', - server: '1.1.1.1', - servername: 'cloudflare-dns.com', - cacheSize: 1000, - }, - quad9TLS: { - server: 'tls://9.9.9.9', - servername: 'dns.quad9.net', - cacheSize: 1000, - }, aliyunTLS: { server: 'tls://223.5.5.5:853', cacheSize: 1000, @@ -93,7 +67,9 @@ const dnsProviders = dns.initDNS({ }, preSetIpList) -const presetHostname = 'xxx.com' +const hasPresetHostname = 'xxx.com' +const noPresetHostname = 'yyy.com' + const hostname1 = 'github.com' const hostname2 = 'api.github.com' const hostname3 = 'hk.docmirror.cn' @@ -104,26 +80,36 @@ const hostname6 = 'gh2.docmirror.top' let ip +console.log('\n--------------- test ForSNI ---------------\n') +console.log(`===> test ForSNI: ${dnsProviders.ForSNI.dnsName}`, '\n\n') +assert.strictEqual(dnsProviders.ForSNI, dnsProviders.safe360) + +const dnsProviders2 = dns.initDNS({ + aliyun: { + server: 'udp://223.5.5.5', + }, +}, {}) +console.log(`===> test ForSNI2: ${dnsProviders2.ForSNI.dnsName}`, '\n\n') +assert.strictEqual(dnsProviders2.ForSNI, dnsProviders2.PreSet) // 未配置forSNI的DNS时,默认使用PreSet作为ForSNI + + console.log('\n--------------- test PreSet ---------------\n') -ip = await dnsProviders.PreSet.lookup(presetHostname) -console.log('===> test PreSet:', ip, '\n\n') +ip = await dnsProviders.PreSet.lookup(hasPresetHostname) +console.log(`===> test PreSet: ${hasPresetHostname} ->`, ip, '\n\n') console.log('\n\n') -assert.strictEqual(ip, presetIp) // test preset +assert.strictEqual(ip, presetIp) // 预设过IP,等于预设的IP + +ip = await dnsProviders.PreSet.lookup(noPresetHostname) +console.log(`===> test PreSet: ${noPresetHostname} ->`, ip, '\n\n') +console.log('\n\n') +assert.strictEqual(ip, noPresetHostname) // 未预设IP,等于域名自己 console.log('\n--------------- test https ---------------\n') -ip = await dnsProviders.cloudflare.lookup(presetHostname) +ip = await dnsProviders.aliyun.lookup(hasPresetHostname) assert.strictEqual(ip, presetIp) // test preset console.log('\n\n') -assert.strictEqual(dnsProviders.cloudflare.dnsType, 'HTTPS') -// ip = await dnsProviders.cloudflare.lookup(hostname1) -// console.log(`===> test cloudflare: ${hostname1} ->`, ip, '\n\n') - -assert.strictEqual(dnsProviders.quad9.dnsType, 'HTTPS') -// ip = await dnsProviders.quad9.lookup(hostname1) -// console.log(`===> test quad9: ${hostname1} ->`, ip, '\n\n') - assert.strictEqual(dnsProviders.aliyun.dnsType, 'HTTPS') ip = await dnsProviders.aliyun.lookup(hostname1) console.log(`===> test aliyun: ${hostname1} ->`, ip, '\n\n') @@ -136,28 +122,12 @@ assert.strictEqual(dnsProviders.safe360.dnsType, 'HTTPS') ip = await dnsProviders.safe360.lookup(hostname1) console.log(`===> test safe360: ${hostname1} ->`, ip, '\n\n') -assert.strictEqual(dnsProviders.rubyfish.dnsType, 'HTTPS') -// ip = await dnsProviders.rubyfish.lookup(hostname1) -// console.log(`===> test rubyfish: ${hostname1} ->`, ip, '\n\n') - -assert.strictEqual(dnsProviders.py233.dnsType, 'HTTPS') -// ip = await dnsProviders.py233.lookup(hostname1) -// console.log(`===> test py233: ${hostname1} ->`, ip, '\n\n') - console.log('\n--------------- test TLS ---------------\n') -ip = await dnsProviders.cloudflareTLS.lookup(presetHostname) +ip = await dnsProviders.aliyunTLS.lookup(hasPresetHostname) assert.strictEqual(ip, presetIp) // test preset console.log('\n\n') -assert.strictEqual(dnsProviders.cloudflareTLS.dnsType, 'TLS') -// ip = await dnsProviders.cloudflareTLS.lookup(hostname1) -// console.log(`===> test cloudflareTLS: ${hostname1} ->`, ip, '\n\n') - -assert.strictEqual(dnsProviders.quad9TLS.dnsType, 'TLS') -// ip = await dnsProviders.quad9TLS.lookup(hostname1) -// console.log(`===> test quad9TLS: ${hostname1} ->`, ip, '\n\n') - assert.strictEqual(dnsProviders.aliyunTLS.dnsType, 'TLS') ip = await dnsProviders.aliyunTLS.lookup(hostname1) console.log(`===> test aliyunTLS: ${hostname1} ->`, ip, '\n\n') @@ -172,7 +142,7 @@ console.log(`===> test safe360TLS: ${hostname1} ->`, ip, '\n\n') console.log('\n--------------- test TCP ---------------\n') -ip = await dnsProviders.googleTCP.lookup(presetHostname) +ip = await dnsProviders.googleTCP.lookup(hasPresetHostname) assert.strictEqual(ip, presetIp) // test preset console.log('\n\n') @@ -186,7 +156,7 @@ console.log(`===> test aliyunTCP: ${hostname1} ->`, ip, '\n\n') console.log('\n--------------- test UDP ---------------\n') -ip = await dnsProviders.googleUDP.lookup(presetHostname) +ip = await dnsProviders.googleUDP.lookup(hasPresetHostname) assert.strictEqual(ip, presetIp) // test preset console.log('\n\n') @@ -200,17 +170,13 @@ console.log(`===> test aliyunUDP: ${hostname1} ->`, ip, '\n\n') dnsProviders.aliyunUDP.lookup(hostname1).then(ip0 => { console.log(`===> test aliyunUDP: ${hostname1} ->`, ip0, '\n\n') - assert.strictEqual(ip0, ip) }) dnsProviders.aliyunUDP.lookup(hostname2).then(ip0 => { console.log(`===> test aliyunUDP: ${hostname2} ->`, ip0, '\n\n') - assert.notStrictEqual(ip0, ip) }) dnsProviders.aliyunUDP.lookup('baidu.com').then(ip0 => { console.log('===> test aliyunUDP: baidu.com ->', ip0, '\n\n') - assert.notStrictEqual(ip0, ip) }) dnsProviders.aliyunUDP.lookup('gitee.com').then(ip0 => { console.log('===> test aliyunUDP: gitee.com ->', ip0, '\n\n') - assert.notStrictEqual(ip0, ip) })