From ab76d03f0b7cec5cb826d4c9e0f2051e503537cf 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] =?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 7b8a7e7..96b2a5f 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'))