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/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/extra/icons/512x512-2.png b/packages/gui/extra/icons/512x512-2.png
new file mode 100644
index 0000000..0702bbd
Binary files /dev/null and b/packages/gui/extra/icons/512x512-2.png differ
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'))
})
}
diff --git a/packages/gui/src/bridge/error/front.js b/packages/gui/src/bridge/error/front.js
index ab1efbd..ef5d6e3 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,17 @@ function install (app, api) {
function handleServerStartError (message, err, app, api) {
if (message.value === 'EADDRINUSE') {
+ // 避免重复弹窗
+ const now = Date.now()
+ if (latestConfirmTime != null && now - latestConfirmTime < 1000) {
+ 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) => {
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 02d2f2f..b720573 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..96b2a5f 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)
@@ -310,13 +313,13 @@ export default {
- 这里配置的域名不会通过代理
+ 配置为不代理的域名不会通过代理
-
+
@@ -374,7 +377,7 @@ export default {
-
+
@@ -422,7 +425,7 @@ export default {
-
+
@@ -452,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/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 fb44844..298b73c 100644
--- a/packages/mitmproxy/src/lib/dns/base.js
+++ b/packages/mitmproxy/src/lib/dns/base.js
@@ -52,21 +52,26 @@ 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)
this.cache.set(hostname, ipCache)
}
- const t = new Date()
- let ipList = await this._lookupInternal(hostname)
+ const t = Date.now()
+ let ipList = await this._lookupWithPreSetIpList(hostname)
if (ipList == null) {
// 没有获取到ipv4地址
ipList = []
@@ -74,29 +79,45 @@ 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} (${Date.now() - 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) {
- // 获取当前域名的预设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)
- }
+ async _lookupWithPreSetIpList (hostname) {
+ 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-${this.dnsType} '${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
+ }
}
}
@@ -105,24 +126,79 @@ 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}`)
+ response = await this._doDnsQuery(hostname, 'A', start)
+ } catch {
+ // 异常日志在 _doDnsQuery已经打印过,这里就不再打印了
+ return []
+ }
+
try {
- 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)
+ log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] 解读响应失败,response:`, response, ', error:', e)
return []
}
}
+
+ _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)
+
+ try {
+ this._dnsQueryPromise(hostname, type)
+ .then((response) => {
+ isOver = true
+ clearTimeout(timeoutId)
+ resolve(response)
+ })
+ .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/https.js b/packages/mitmproxy/src/lib/dns/https.js
index 3ab889c..07f2639 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: 4000,
+ })
+}
+
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
}
- async _doDnsQuery (hostname) {
- return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
+ _dnsQueryPromise (hostname, type = 'A') {
+ // 请求参数
+ 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 ee3e40c..7a36d6f 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')
@@ -27,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'
@@ -47,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
@@ -63,8 +64,8 @@ module.exports = {
if (type === 'tls' || type === 'dot' || type === 'dns-over-tls') {
// 基于 tls
- dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.servername)
- } else if (type === 'tcp' || type === 'dns-over-tcp') {
+ dnsMap[provider] = new DNSOverTLS(provider, conf.cacheSize, preSetIpList, server, port, conf.sni || conf.servername)
+ } else if (type === 'tcp') {
// 基于 tcp
dnsMap[provider] = new DNSOverTCP(provider, conf.cacheSize, preSetIpList, server, port)
} else {
@@ -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/dns/tcp.js b/packages/mitmproxy/src/lib/dns/tcp.js
index 6141609..f3e5f5c 100644
--- a/packages/mitmproxy/src/lib/dns/tcp.js
+++ b/packages/mitmproxy/src/lib/dns/tcp.js
@@ -4,16 +4,16 @@ 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) {
super(dnsName, 'TCP', cacheSize, preSetIpList)
- this.dnsServer = dnsServer
+ this.dnsServer = dnsServer.replace(/\s+/, '')
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..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
@@ -6,22 +6,25 @@ 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
}
- async _doDnsQuery (hostname) {
+ _dnsQueryPromise (hostname, type = 'A') {
const options = {
host: this.dnsServer,
port: this.dnsServerPort,
servername: this.dnsServerName || this.dnsServer,
+ rejectUnauthorized: !this.dnsServerName,
name: hostname,
klass: 'IN',
- type: 'A',
+ type,
+
+ timeout: 4000,
}
- 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..cd97a7d 100644
--- a/packages/mitmproxy/src/lib/dns/udp.js
+++ b/packages/mitmproxy/src/lib/dns/udp.js
@@ -8,28 +8,35 @@ 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(']')
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('DNS查询超时'))
+ udpClient.close()
+ }
+ }, timeout)
})
}
}
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/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/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 f558471..1e265c7 100644
--- a/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js
+++ b/packages/mitmproxy/src/lib/interceptor/impl/req/proxy.js
@@ -1,16 +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 (url.includes('${')) {
+ url = replacePlaceholder0(url, pathMatched, 'p')
+ url = replacePlaceholder0(url, hostnameMatched, 'h')
}
// 移除多余的占位符
@@ -22,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)
@@ -40,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}`
@@ -48,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
@@ -76,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
@@ -107,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 1812abd..85f0a68 100644
--- a/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js
+++ b/packages/mitmproxy/src/lib/interceptor/impl/req/redirect.js
@@ -3,16 +3,24 @@ 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)
- 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'
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/proxy/mitmproxy/createConnectHandler.js b/packages/mitmproxy/src/lib/proxy/mitmproxy/createConnectHandler.js
index 0ac8f4b..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}`
@@ -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)
}
}
// 代理连接事件监听
@@ -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 b3c27f9..a8876f6 100644
--- a/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js
+++ b/packages/mitmproxy/src/lib/proxy/mitmproxy/createRequestHandler.js
@@ -109,20 +109,22 @@ 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 = {}
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) {
- 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 {
@@ -154,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 {
@@ -171,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`)
@@ -182,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)
@@ -193,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))
@@ -209,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()
@@ -219,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/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/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()
diff --git a/packages/mitmproxy/src/lib/speed/SpeedTester.js b/packages/mitmproxy/src/lib/speed/SpeedTester.js
index 59f4995..cbbeaf0 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}测速成功:${ret.target}`
+ 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', target: `${host}:${this.port}`, 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', target: host, 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测速失败:${item.host}:${this.port} ${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/src/options.js b/packages/mitmproxy/src/options.js
index 6504dee..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 // 不拦截
}
@@ -187,12 +186,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..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)
@@ -150,10 +141,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
+ }
+ }
}
}
@@ -178,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
new file mode 100644
index 0000000..163bb1c
--- /dev/null
+++ b/packages/mitmproxy/test/dnsTest-abroad-doh-sni.mjs
@@ -0,0 +1,90 @@
+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://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 hostnames = [
+ 'github.com',
+ 'mvnrepository.com',
+]
+const sni = 'baidu.com'
+// const sni = ''
+
+console.log(`\n--------------- 测试DoH的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 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-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/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)
})
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'))
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