Merge branch 'docmirror:master' into only-edit-wiki
commit
38638e16d2
|
|
@ -393,6 +393,7 @@ const defaultConfig = {
|
|||
type: 'https',
|
||||
server: 'https://doh.360.cn/dns-query',
|
||||
cacheSize: 1000,
|
||||
forSNI: true,
|
||||
},
|
||||
rubyfish: {
|
||||
type: 'https',
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
|
|
@ -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'))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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 ''
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<a-button type="primary" icon="plus" @click="addNoProxyUrl()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of noProxyUrls" :key="index" :gutter="10">
|
||||
<a-row v-for="(item, index) of noProxyUrls" ref="noProxyUrls" :key="index" :gutter="10">
|
||||
<a-col :span="22">
|
||||
<MockInput v-model="item.key" class="mt-2" />
|
||||
</a-col>
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<a-button type="primary" icon="plus" @click="addTarget()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of targets" :key="index" :gutter="10">
|
||||
<a-row v-for="(item, index) of targets" ref="targets" :key="index" :gutter="10">
|
||||
<a-col :span="18">
|
||||
<MockInput v-model="item.key" class="mt-2" />
|
||||
</a-col>
|
||||
|
|
@ -196,7 +200,7 @@ export default {
|
|||
<a-button type="primary" icon="plus" @click="addServer()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of servers" :key="index" :gutter="10">
|
||||
<a-row v-for="(item, index) of servers" ref="servers" :key="index" :gutter="10">
|
||||
<a-col :span="6">
|
||||
<a-input v-model="item.key" :title="item.key" addon-before="域名" placeholder="yourdomain.com" spellcheck="false" />
|
||||
</a-col>
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<a-button type="primary" icon="plus" @click="addExcludeIp()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of excludeIpList" :key="index" :gutter="10" class="fine-tuning">
|
||||
<a-row v-for="(item, index) of excludeIpList" ref="excludeIpList" :key="index" :gutter="10" class="fine-tuning">
|
||||
<a-col :span="17">
|
||||
<MockInput v-model="item.key" class="mt-1" />
|
||||
</a-col>
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
<div v-if="activeTabKey === '4'">
|
||||
<a-row style="margin-top:10px">
|
||||
<a-col span="21">
|
||||
<div>这里配置的域名不会通过代理</div>
|
||||
<div>配置为<code>不代理</code>的域名不会通过代理</div>
|
||||
</a-col>
|
||||
<a-col span="3">
|
||||
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addWhiteList()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of whiteList" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-row v-for="(item, index) of whiteList" ref="whiteList" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-col :span="16">
|
||||
<MockInput v-model="item.key" />
|
||||
</a-col>
|
||||
|
|
@ -374,7 +377,7 @@ export default {
|
|||
<a-button style="margin-left:8px" type="primary" icon="plus" @click="addDnsMapping()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of dnsMappings" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-row v-for="(item, index) of dnsMappings" ref="dnsMappings" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-col :span="15">
|
||||
<MockInput v-model="item.key" />
|
||||
</a-col>
|
||||
|
|
@ -422,7 +425,7 @@ export default {
|
|||
<a-button style="margin-left:10px" type="primary" icon="plus" @click="addSpeedHostname()" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row v-for="(item, index) of getSpeedTestConfig().hostnameList" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-row v-for="(item, index) of getSpeedTestConfig().hostnameList" ref="hostnameList" :key="index" :gutter="10" style="margin-top: 5px">
|
||||
<a-col :span="21">
|
||||
<MockInput v-model="getSpeedTestConfig().hostnameList[index]" />
|
||||
</a-col>
|
||||
|
|
@ -452,9 +455,9 @@ export default {
|
|||
</a>
|
||||
<a-tag
|
||||
v-for="(element, index) of item.backupList" :key="index" style="margin:2px;"
|
||||
:title="element.dns" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : 'red'"
|
||||
:title="element.title || `测速中:${element.host}`" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : (element.title ? 'red' : '')"
|
||||
>
|
||||
{{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }}
|
||||
{{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }}
|
||||
</a-tag>
|
||||
</a-card>
|
||||
</a-col>
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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}`
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const config = {
|
||||
notify () {},
|
||||
dnsMap: {},
|
||||
}
|
||||
module.exports = {
|
||||
getConfig () {
|
||||
return config
|
||||
},
|
||||
notify: null,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
@ -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')
|
||||
|
|
@ -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)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue